X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/0afc40ccf18f9909ef1bab6cd8a41350a9789610..43567d1d96d5a86d81f6c30e411bad1deaafdda6:/src/services/collection-service/collection-service.test.ts diff --git a/src/services/collection-service/collection-service.test.ts b/src/services/collection-service/collection-service.test.ts index fb18add1..304cbfd3 100644 --- a/src/services/collection-service/collection-service.test.ts +++ b/src/services/collection-service/collection-service.test.ts @@ -2,69 +2,454 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { AxiosInstance } from 'axios'; -import { WebDAV } from '~/common/webdav'; -import { ApiActions } from '../api/api-actions'; +import axios, { AxiosInstance } from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import { snakeCase } from 'lodash'; +import { CollectionResource, defaultCollectionSelectedFields } from 'models/collection'; import { AuthService } from '../auth-service/auth-service'; -import { CollectionService } from './collection-service'; +import { CollectionService, emptyCollectionPdh } from './collection-service'; describe('collection-service', () => { let collectionService: CollectionService; - let serverApi; + let serverApi: AxiosInstance; + let axiosMock: MockAdapter; let webdavClient: any; let authService; let actions; beforeEach(() => { - serverApi = {} as AxiosInstance; + serverApi = axios.create(); + axiosMock = new MockAdapter(serverApi); webdavClient = { delete: jest.fn(), + upload: jest.fn(), + mkdir: jest.fn(), } as any; authService = {} as AuthService; - actions = {} as ApiActions; + actions = { + progressFn: jest.fn(), + errorFn: jest.fn(), + } as any; collectionService = new CollectionService(serverApi, webdavClient, authService, actions); collectionService.update = jest.fn(); }); + describe('get', () => { + it('should make a request with default selected fields', async () => { + serverApi.get = jest.fn(() => Promise.resolve( + { data: { items: [{}] } } + )); + const uuid = 'zzzzz-4zz18-0123456789abcde' + await collectionService.get(uuid); + expect(serverApi.get).toHaveBeenCalledWith( + `/collections/${uuid}`, { + params: { + select: JSON.stringify(defaultCollectionSelectedFields.map(snakeCase)), + }, + } + ); + }); + + it('should be able to request specific fields', async () => { + serverApi.get = jest.fn(() => Promise.resolve( + { data: { items: [{}] } } + )); + const uuid = 'zzzzz-4zz18-0123456789abcde' + await collectionService.get(uuid, undefined, ['manifestText']); + expect(serverApi.get).toHaveBeenCalledWith( + `/collections/${uuid}`, { + params: { + select: `["manifest_text"]` + }, + } + ); + }); + }); + + describe('update', () => { + it('should call put selecting updated fields + others', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + const data: Partial = { + name: 'foo', + }; + const expected = { + collection: { + ...data, + preserve_version: true, + }, + select: ['uuid', 'name', 'version', 'modified_at'], + } + collectionService = new CollectionService(serverApi, webdavClient, authService, actions); + await collectionService.update('uuid', data); + expect(serverApi.put).toHaveBeenCalledWith('/collections/uuid', expected); + }); + }); + + describe('uploadFiles', () => { + it('should skip if no files to upload files', async () => { + // given + const files: File[] = []; + const collectionUUID = ''; + + // when + await collectionService.uploadFiles(collectionUUID, files); + + // then + expect(webdavClient.upload).not.toHaveBeenCalled(); + }); + + it('should upload files', async () => { + // given + const files: File[] = [{name: 'test-file1'} as File]; + const collectionUUID = 'zzzzz-4zz18-0123456789abcde'; + + // when + await collectionService.uploadFiles(collectionUUID, files); + + // then + expect(webdavClient.upload).toHaveBeenCalledTimes(1); + expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789abcde/test-file1"); + }); + + it('should upload files with custom uplaod target', async () => { + // given + const files: File[] = [{name: 'test-file1'} as File]; + const collectionUUID = 'zzzzz-4zz18-0123456789abcde'; + const customTarget = 'zzzzz-4zz18-0123456789adddd/test-path/' + + // when + await collectionService.uploadFiles(collectionUUID, files, undefined, customTarget); + + // then + expect(webdavClient.upload).toHaveBeenCalledTimes(1); + expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789adddd/test-path/test-file1"); + }); + }); + describe('deleteFiles', () => { it('should remove no files', async () => { // given + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); const filePaths: string[] = []; - const collectionUUID = ''; + const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx'; // when await collectionService.deleteFiles(collectionUUID, filePaths); // then - expect(webdavClient.delete).not.toHaveBeenCalled(); + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${collectionUUID}`, { + collection: { + preserve_version: true + }, + replace_files: {}, + } + ); }); it('should remove only root files', async () => { // given + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); const filePaths: string[] = ['/root/1', '/root/1/100', '/root/1/100/test.txt', '/root/2', '/root/2/200', '/root/3/300/test.txt']; - const collectionUUID = ''; + const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx'; // when await collectionService.deleteFiles(collectionUUID, filePaths); // then - expect(webdavClient.delete).toHaveBeenCalledTimes(3); - expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/3/300/test.txt"); - expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/2"); - expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/1"); + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${collectionUUID}`, { + collection: { + preserve_version: true + }, + replace_files: { + '/root/3/300/test.txt': '', + '/root/2': '', + '/root/1': '', + }, + } + ); }); - it('should remove files with uuid prefix', async () => { + it('should batch remove files', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); // given - const filePaths: string[] = ['/root/1']; - const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx'; + const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt']; + const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx'; // when await collectionService.deleteFiles(collectionUUID, filePaths); // then - expect(webdavClient.delete).toHaveBeenCalledTimes(1); - expect(webdavClient.delete).toHaveBeenCalledWith("c=zzzzz-tpzed-5o5tg0l9a57gxxx/root/1"); + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${collectionUUID}`, { + collection: { + preserve_version: true + }, + replace_files: { + '/root/1': '', + '/secondFile': '', + '/barefile.txt': '', + }, + } + ); }); }); -}); \ No newline at end of file + + describe('renameFile', () => { + it('should rename file', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + const collectionUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const collectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + const oldPath = '/old/path'; + const newPath = '/new/filename'; + + await collectionService.renameFile(collectionUuid, collectionPdh, oldPath, newPath); + + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${collectionUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + [newPath]: `${collectionPdh}${oldPath}`, + [oldPath]: '', + }, + } + ); + }); + }); + + describe('copyFiles', () => { + it('should batch copy files', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt']; + const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const destinationPath = '/destinationPath'; + + // when + await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath); + + // then + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${destinationUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + [`${destinationPath}/1`]: `${sourcePdh}/root/1`, + [`${destinationPath}/secondFile`]: `${sourcePdh}/secondFile`, + [`${destinationPath}/barefile.txt`]: `${sourcePdh}/barefile.txt`, + }, + } + ); + }); + + it('should copy files from rooth', async () => { + // Test copying from root paths + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + const filePaths: string[] = ['/']; + const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const destinationPath = '/destinationPath'; + + await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath); + + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${destinationUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + [`${destinationPath}`]: `${sourcePdh}/`, + }, + } + ); + }); + + it('should copy files to root path', async () => { + // Test copying to root paths + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + const filePaths: string[] = ['/']; + const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const destinationPath = '/'; + + await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath); + + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${destinationUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + "/": `${sourcePdh}/`, + }, + } + ); + }); + }); + + describe('moveFiles', () => { + it('should batch move files', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + // given + const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt']; + const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx'; + const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const destinationPath = '/destinationPath'; + + // when + await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath); + + // then + expect(serverApi.put).toHaveBeenCalledTimes(2); + // Verify copy + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${destinationUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`, + [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`, + [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`, + [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`, + }, + } + ); + // Verify delete + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${srcCollectionUUID}`, { + collection: { + preserve_version: true + }, + replace_files: { + "/rootFile": "", + "/secondFile": "", + "/subpath/subfile": "", + "/barefile.txt": "", + }, + } + ); + }); + + it('should batch move files within collection', async () => { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + // given + const filePaths: string[] = ['/one', '/two', '/subpath/subfile', 'barefile.txt']; + const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx'; + const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationPath = '/destinationPath'; + + // when + await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, srcCollectionUUID, destinationPath); + + // then + expect(serverApi.put).toHaveBeenCalledTimes(1); + // Verify copy + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${srcCollectionUUID}`, { + collection: { + preserve_version: true + }, + replace_files: { + [`${destinationPath}/one`]: `${srcCollectionPdh}/one`, + ['/one']: '', + [`${destinationPath}/two`]: `${srcCollectionPdh}/two`, + ['/two']: '', + [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`, + ['/subpath/subfile']: '', + [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`, + ['/barefile.txt']: '', + }, + } + ); + }); + + it('should abort batch move when copy fails', async () => { + // Simulate failure to copy + serverApi.put = jest.fn(() => Promise.reject({ + data: {}, + response: { + "errors": ["error getting snapshot of \"rootFile\" from \"8cd9ce1dfa21c635b620b1bfee7aaa08+180\": file does not exist"] + } + })); + // given + const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt']; + const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx'; + const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180'; + + const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq'; + const destinationPath = '/destinationPath'; + + // when + try { + await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath); + } catch {} + + // then + expect(serverApi.put).toHaveBeenCalledTimes(1); + // Verify copy + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${destinationUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`, + [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`, + [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`, + [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`, + }, + } + ); + }); + }); + + describe('createDirectory', () => { + it('creates empty directory', async () => { + // given + const directoryNames = [ + {in: 'newDir', out: 'newDir'}, + {in: '/fooDir', out: 'fooDir'}, + {in: '/anotherPath/', out: 'anotherPath'}, + {in: 'trailingSlash/', out: 'trailingSlash'}, + ]; + const collectionUuid = 'zzzzz-tpzed-5o5tg0l9a57gxxx'; + + for (var i = 0; i < directoryNames.length; i++) { + serverApi.put = jest.fn(() => Promise.resolve({ data: {} })); + // when + await collectionService.createDirectory(collectionUuid, directoryNames[i].in); + // then + expect(serverApi.put).toHaveBeenCalledTimes(1); + expect(serverApi.put).toHaveBeenCalledWith( + `/collections/${collectionUuid}`, { + collection: { + preserve_version: true + }, + replace_files: { + ["/" + directoryNames[i].out]: emptyCollectionPdh, + }, + } + ); + } + }); + }); + +});