1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import axios, { AxiosInstance } from 'axios';
6 import MockAdapter from 'axios-mock-adapter';
7 import { snakeCase } from 'lodash';
8 import { CollectionResource, defaultCollectionSelectedFields } from 'models/collection';
9 import { AuthService } from '../auth-service/auth-service';
10 import { CollectionService } from './collection-service';
12 describe('collection-service', () => {
13 let collectionService: CollectionService;
14 let serverApi: AxiosInstance;
15 let axiosMock: MockAdapter;
16 let webdavClient: any;
21 serverApi = axios.create();
22 axiosMock = new MockAdapter(serverApi);
28 authService = {} as AuthService;
30 progressFn: jest.fn(),
33 collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
34 collectionService.update = jest.fn();
37 describe('get', () => {
38 it('should make a request with default selected fields', async () => {
39 serverApi.get = jest.fn(() => Promise.resolve(
40 { data: { items: [{}] } }
42 const uuid = 'zzzzz-4zz18-0123456789abcde'
43 await collectionService.get(uuid);
44 expect(serverApi.get).toHaveBeenCalledWith(
45 `/collections/${uuid}`, {
47 select: JSON.stringify(defaultCollectionSelectedFields.map(snakeCase)),
53 it('should be able to request specific fields', async () => {
54 serverApi.get = jest.fn(() => Promise.resolve(
55 { data: { items: [{}] } }
57 const uuid = 'zzzzz-4zz18-0123456789abcde'
58 await collectionService.get(uuid, undefined, ['manifestText']);
59 expect(serverApi.get).toHaveBeenCalledWith(
60 `/collections/${uuid}`, {
62 select: `["manifest_text"]`
69 describe('update', () => {
70 it('should call put selecting updated fields + others', async () => {
71 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
72 const data: Partial<CollectionResource> = {
78 preserve_version: true,
80 select: ['uuid', 'name', 'version', 'modified_at'],
82 collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
83 await collectionService.update('uuid', data);
84 expect(serverApi.put).toHaveBeenCalledWith('/collections/uuid', expected);
88 describe('uploadFiles', () => {
89 it('should skip if no files to upload files', async () => {
91 const files: File[] = [];
92 const collectionUUID = '';
95 await collectionService.uploadFiles(collectionUUID, files);
98 expect(webdavClient.upload).not.toHaveBeenCalled();
101 it('should upload files', async () => {
103 const files: File[] = [{name: 'test-file1'} as File];
104 const collectionUUID = 'zzzzz-4zz18-0123456789abcde';
107 await collectionService.uploadFiles(collectionUUID, files);
110 expect(webdavClient.upload).toHaveBeenCalledTimes(1);
111 expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789abcde/test-file1");
114 it('should upload files with custom uplaod target', async () => {
116 const files: File[] = [{name: 'test-file1'} as File];
117 const collectionUUID = 'zzzzz-4zz18-0123456789abcde';
118 const customTarget = 'zzzzz-4zz18-0123456789adddd/test-path/'
121 await collectionService.uploadFiles(collectionUUID, files, undefined, customTarget);
124 expect(webdavClient.upload).toHaveBeenCalledTimes(1);
125 expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789adddd/test-path/test-file1");
129 describe('deleteFiles', () => {
130 it('should remove no files', async () => {
132 const filePaths: string[] = [];
133 const collectionUUID = '';
136 await collectionService.deleteFiles(collectionUUID, filePaths);
139 expect(webdavClient.delete).not.toHaveBeenCalled();
142 it('should remove only root files', async () => {
144 const filePaths: string[] = ['/root/1', '/root/1/100', '/root/1/100/test.txt', '/root/2', '/root/2/200', '/root/3/300/test.txt'];
145 const collectionUUID = '';
148 await collectionService.deleteFiles(collectionUUID, filePaths);
151 expect(webdavClient.delete).toHaveBeenCalledTimes(3);
152 expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/3/300/test.txt");
153 expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/2");
154 expect(webdavClient.delete).toHaveBeenCalledWith("c=/root/1");
157 it('should remove files with uuid prefix', async () => {
159 const filePaths: string[] = ['/root/1'];
160 const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
163 await collectionService.deleteFiles(collectionUUID, filePaths);
166 expect(webdavClient.delete).toHaveBeenCalledTimes(1);
167 expect(webdavClient.delete).toHaveBeenCalledWith("c=zzzzz-tpzed-5o5tg0l9a57gxxx/root/1");
171 describe('batch file operations', () => {
172 it('should batch remove files', async () => {
173 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
175 const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
176 const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
179 await collectionService.batchFileDelete(collectionUUID, filePaths);
182 expect(serverApi.put).toHaveBeenCalledTimes(1);
183 expect(serverApi.put).toHaveBeenCalledWith(
184 `/collections/${collectionUUID}`, {
186 preserve_version: true
197 it('should batch copy files', async () => {
198 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
200 const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
201 // const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
202 const collectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
204 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
205 const destinationPath = '/destinationPath';
208 await collectionService.batchFileCopy(collectionPDH, filePaths, destinationUuid, destinationPath);
211 expect(serverApi.put).toHaveBeenCalledTimes(1);
212 expect(serverApi.put).toHaveBeenCalledWith(
213 `/collections/${destinationUuid}`, {
215 preserve_version: true
218 [`${destinationPath}/1`]: `${collectionPDH}/root/1`,
219 [`${destinationPath}/secondFile`]: `${collectionPDH}/secondFile`,
220 [`${destinationPath}/barefile.txt`]: `${collectionPDH}/barefile.txt`,
226 it('should batch move files', async () => {
227 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
229 const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
230 const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
231 const srcCollectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
233 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
234 const destinationPath = '/destinationPath';
237 await collectionService.batchFileMove(srcCollectionUUID, srcCollectionPDH, filePaths, destinationUuid, destinationPath);
240 expect(serverApi.put).toHaveBeenCalledTimes(2);
242 expect(serverApi.put).toHaveBeenCalledWith(
243 `/collections/${destinationUuid}`, {
245 preserve_version: true
248 [`${destinationPath}/rootFile`]: `${srcCollectionPDH}/rootFile`,
249 [`${destinationPath}/secondFile`]: `${srcCollectionPDH}/secondFile`,
250 [`${destinationPath}/subfile`]: `${srcCollectionPDH}/subpath/subfile`,
251 [`${destinationPath}/barefile.txt`]: `${srcCollectionPDH}/barefile.txt`,
256 expect(serverApi.put).toHaveBeenCalledWith(
257 `/collections/${srcCollectionUUID}`, {
259 preserve_version: true
264 "/subpath/subfile": "",
271 it('should abort batch move when copy fails', async () => {
272 // Simulate failure to copy
273 serverApi.put = jest.fn(() => Promise.reject({
276 "errors": ["error getting snapshot of \"rootFile\" from \"8cd9ce1dfa21c635b620b1bfee7aaa08+180\": file does not exist"]
280 const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
281 const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
282 const srcCollectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
284 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
285 const destinationPath = '/destinationPath';
289 await collectionService.batchFileMove(srcCollectionUUID, srcCollectionPDH, filePaths, destinationUuid, destinationPath);
293 expect(serverApi.put).toHaveBeenCalledTimes(1);
295 expect(serverApi.put).toHaveBeenCalledWith(
296 `/collections/${destinationUuid}`, {
298 preserve_version: true
301 [`${destinationPath}/rootFile`]: `${srcCollectionPDH}/rootFile`,
302 [`${destinationPath}/secondFile`]: `${srcCollectionPDH}/secondFile`,
303 [`${destinationPath}/subfile`]: `${srcCollectionPDH}/subpath/subfile`,
304 [`${destinationPath}/barefile.txt`]: `${srcCollectionPDH}/barefile.txt`,
311 describe('createDirectory', () => {
312 it('creates empty directory', async () => {
314 const directoryNames = {
317 '/anotherPath/': 'anotherPath',
318 'trailingSlash/': 'trailingSlash',
320 const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
322 Object.keys(directoryNames).map(async (path) => {
324 await collectionService.createDirectory(collectionUUID, path);
326 expect(webdavClient.mkdir).toHaveBeenCalledWith(`c=${collectionUUID}/${directoryNames[path]}`);