20029: Fix test
[arvados-workbench2.git] / src / services / collection-service / collection-service.test.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
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, emptyCollectionUuid } from './collection-service';
11
12 describe('collection-service', () => {
13     let collectionService: CollectionService;
14     let serverApi: AxiosInstance;
15     let axiosMock: MockAdapter;
16     let webdavClient: any;
17     let authService;
18     let actions;
19
20     beforeEach(() => {
21         serverApi = axios.create();
22         axiosMock = new MockAdapter(serverApi);
23         webdavClient = {
24             delete: jest.fn(),
25             upload: jest.fn(),
26             mkdir: jest.fn(),
27         } as any;
28         authService = {} as AuthService;
29         actions = {
30             progressFn: jest.fn(),
31             errorFn: jest.fn(),
32         } as any;
33         collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
34         collectionService.update = jest.fn();
35     });
36
37     describe('get', () => {
38         it('should make a request with default selected fields', async () => {
39             serverApi.get = jest.fn(() => Promise.resolve(
40                 { data: { items: [{}] } }
41             ));
42             const uuid = 'zzzzz-4zz18-0123456789abcde'
43             await collectionService.get(uuid);
44             expect(serverApi.get).toHaveBeenCalledWith(
45                 `/collections/${uuid}`, {
46                     params: {
47                         select: JSON.stringify(defaultCollectionSelectedFields.map(snakeCase)),
48                     },
49                 }
50             );
51         });
52
53         it('should be able to request specific fields', async () => {
54             serverApi.get = jest.fn(() => Promise.resolve(
55                 { data: { items: [{}] } }
56             ));
57             const uuid = 'zzzzz-4zz18-0123456789abcde'
58             await collectionService.get(uuid, undefined, ['manifestText']);
59             expect(serverApi.get).toHaveBeenCalledWith(
60                 `/collections/${uuid}`, {
61                     params: {
62                         select: `["manifest_text"]`
63                     },
64                 }
65             );
66         });
67     });
68
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> = {
73                 name: 'foo',
74             };
75             const expected = {
76                 collection: {
77                     ...data,
78                     preserve_version: true,
79                 },
80                 select: ['uuid', 'name', 'version', 'modified_at'],
81             }
82             collectionService = new CollectionService(serverApi, webdavClient, authService, actions);
83             await collectionService.update('uuid', data);
84             expect(serverApi.put).toHaveBeenCalledWith('/collections/uuid', expected);
85         });
86     });
87
88     describe('uploadFiles', () => {
89         it('should skip if no files to upload files', async () => {
90             // given
91             const files: File[] = [];
92             const collectionUUID = '';
93
94             // when
95             await collectionService.uploadFiles(collectionUUID, files);
96
97             // then
98             expect(webdavClient.upload).not.toHaveBeenCalled();
99         });
100
101         it('should upload files', async () => {
102             // given
103             const files: File[] = [{name: 'test-file1'} as File];
104             const collectionUUID = 'zzzzz-4zz18-0123456789abcde';
105
106             // when
107             await collectionService.uploadFiles(collectionUUID, files);
108
109             // then
110             expect(webdavClient.upload).toHaveBeenCalledTimes(1);
111             expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789abcde/test-file1");
112         });
113
114         it('should upload files with custom uplaod target', async () => {
115             // given
116             const files: File[] = [{name: 'test-file1'} as File];
117             const collectionUUID = 'zzzzz-4zz18-0123456789abcde';
118             const customTarget = 'zzzzz-4zz18-0123456789adddd/test-path/'
119
120             // when
121             await collectionService.uploadFiles(collectionUUID, files, undefined, customTarget);
122
123             // then
124             expect(webdavClient.upload).toHaveBeenCalledTimes(1);
125             expect(webdavClient.upload.mock.calls[0][0]).toEqual("c=zzzzz-4zz18-0123456789adddd/test-path/test-file1");
126         });
127     });
128
129     describe('deleteFiles', () => {
130         it('should remove no files', async () => {
131             // given
132             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
133             const filePaths: string[] = [];
134             const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
135
136             // when
137             await collectionService.deleteFiles(collectionUUID, filePaths);
138
139             // then
140             expect(serverApi.put).toHaveBeenCalledTimes(1);
141             expect(serverApi.put).toHaveBeenCalledWith(
142                 `/collections/${collectionUUID}`, {
143                     collection: {
144                         preserve_version: true
145                     },
146                     replace_files: {},
147                 }
148             );
149         });
150
151         it('should remove only root files', async () => {
152             // given
153             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
154             const filePaths: string[] = ['/root/1', '/root/1/100', '/root/1/100/test.txt', '/root/2', '/root/2/200', '/root/3/300/test.txt'];
155             const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
156
157             // when
158             await collectionService.deleteFiles(collectionUUID, filePaths);
159
160             // then
161             expect(serverApi.put).toHaveBeenCalledTimes(1);
162             expect(serverApi.put).toHaveBeenCalledWith(
163                 `/collections/${collectionUUID}`, {
164                     collection: {
165                         preserve_version: true
166                     },
167                     replace_files: {
168                         '/root/3/300/test.txt': '',
169                         '/root/2': '',
170                         '/root/1': '',
171                     },
172                 }
173             );
174         });
175
176         it('should remove files with uuid prefix', async () => {
177             // given
178             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
179             const filePaths: string[] = ['/root/1'];
180             const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
181
182             // when
183             await collectionService.deleteFiles(collectionUUID, filePaths);
184
185             // then
186             expect(serverApi.put).toHaveBeenCalledTimes(1);
187             expect(serverApi.put).toHaveBeenCalledWith(
188                 `/collections/${collectionUUID}`, {
189                     collection: {
190                         preserve_version: true
191                     },
192                     replace_files: {
193                         '/root/1': '',
194                     },
195                 }
196             );
197         });
198
199         it('should batch remove files', async () => {
200             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
201             // given
202             const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
203             const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
204
205             // when
206             await collectionService.deleteFiles(collectionUUID, filePaths);
207
208             // then
209             expect(serverApi.put).toHaveBeenCalledTimes(1);
210             expect(serverApi.put).toHaveBeenCalledWith(
211                 `/collections/${collectionUUID}`, {
212                     collection: {
213                         preserve_version: true
214                     },
215                     replace_files: {
216                         '/root/1': '',
217                         '/secondFile': '',
218                         '/barefile.txt': '',
219                     },
220                 }
221             );
222         });
223     });
224
225     describe('renameFile', () => {
226         it('should rename file', async () => {
227             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
228             const collectionUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
229             const collectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
230             const oldPath = '/old/path';
231             const newPath = '/new/filename';
232
233             await collectionService.renameFile(collectionUuid, collectionPdh, oldPath, newPath);
234
235             expect(serverApi.put).toHaveBeenCalledTimes(1);
236             expect(serverApi.put).toHaveBeenCalledWith(
237                 `/collections/${collectionUuid}`, {
238                     collection: {
239                         preserve_version: true
240                     },
241                     replace_files: {
242                         [newPath]: `${collectionPdh}${oldPath}`,
243                         [oldPath]: '',
244                     },
245                 }
246             );
247         });
248     });
249
250     describe('copyFiles', () => {
251         it('should batch copy files', async () => {
252             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
253             const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
254             const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
255
256             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
257             const destinationPath = '/destinationPath';
258
259             // when
260             await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
261
262             // then
263             expect(serverApi.put).toHaveBeenCalledTimes(1);
264             expect(serverApi.put).toHaveBeenCalledWith(
265                 `/collections/${destinationUuid}`, {
266                     collection: {
267                         preserve_version: true
268                     },
269                     replace_files: {
270                         [`${destinationPath}/1`]: `${sourcePdh}/root/1`,
271                         [`${destinationPath}/secondFile`]: `${sourcePdh}/secondFile`,
272                         [`${destinationPath}/barefile.txt`]: `${sourcePdh}/barefile.txt`,
273                     },
274                 }
275             );
276         });
277
278         it('should copy files from rooth', async () => {
279             // Test copying from root paths
280             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
281             const filePaths: string[] = ['/'];
282             const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
283
284             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
285             const destinationPath = '/destinationPath';
286
287             await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
288
289             expect(serverApi.put).toHaveBeenCalledTimes(1);
290             expect(serverApi.put).toHaveBeenCalledWith(
291                 `/collections/${destinationUuid}`, {
292                     collection: {
293                         preserve_version: true
294                     },
295                     replace_files: {
296                         [`${destinationPath}`]: `${sourcePdh}/`,
297                     },
298                 }
299             );
300         });
301
302         it('should copy files to root path', async () => {
303             // Test copying to root paths
304             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
305             const filePaths: string[] = ['/'];
306             const sourcePdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
307
308             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
309             const destinationPath = '/';
310
311             await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
312
313             expect(serverApi.put).toHaveBeenCalledTimes(1);
314             expect(serverApi.put).toHaveBeenCalledWith(
315                 `/collections/${destinationUuid}`, {
316                     collection: {
317                         preserve_version: true
318                     },
319                     replace_files: {
320                         "/": `${sourcePdh}/`,
321                     },
322                 }
323             );
324         });
325     });
326
327     describe('moveFiles', () => {
328         it('should batch move files', async () => {
329             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
330             // given
331             const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
332             const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
333             const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
334
335             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
336             const destinationPath = '/destinationPath';
337
338             // when
339             await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath);
340
341             // then
342             expect(serverApi.put).toHaveBeenCalledTimes(2);
343             // Verify copy
344             expect(serverApi.put).toHaveBeenCalledWith(
345                 `/collections/${destinationUuid}`, {
346                     collection: {
347                         preserve_version: true
348                     },
349                     replace_files: {
350                         [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`,
351                         [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`,
352                         [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
353                         [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
354                     },
355                 }
356             );
357             // Verify delete
358             expect(serverApi.put).toHaveBeenCalledWith(
359                 `/collections/${srcCollectionUUID}`, {
360                     collection: {
361                         preserve_version: true
362                     },
363                     replace_files: {
364                         "/rootFile": "",
365                         "/secondFile": "",
366                         "/subpath/subfile": "",
367                         "/barefile.txt": "",
368                     },
369                 }
370             );
371         });
372
373         it('should batch move files within collection', async () => {
374             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
375             // given
376             const filePaths: string[] = ['/one', '/two', '/subpath/subfile', 'barefile.txt'];
377             const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
378             const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
379
380             const destinationPath = '/destinationPath';
381
382             // when
383             await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, srcCollectionUUID, destinationPath);
384
385             // then
386             expect(serverApi.put).toHaveBeenCalledTimes(1);
387             // Verify copy
388             expect(serverApi.put).toHaveBeenCalledWith(
389                 `/collections/${srcCollectionUUID}`, {
390                     collection: {
391                         preserve_version: true
392                     },
393                     replace_files: {
394                         [`${destinationPath}/one`]: `${srcCollectionPdh}/one`,
395                         ['/one']: '',
396                         [`${destinationPath}/two`]: `${srcCollectionPdh}/two`,
397                         ['/two']: '',
398                         [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
399                         ['/subpath/subfile']: '',
400                         [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
401                         ['/barefile.txt']: '',
402                     },
403                 }
404             );
405         });
406
407         it('should abort batch move when copy fails', async () => {
408             // Simulate failure to copy
409             serverApi.put = jest.fn(() => Promise.reject({
410                 data: {},
411                 response: {
412                     "errors": ["error getting snapshot of \"rootFile\" from \"8cd9ce1dfa21c635b620b1bfee7aaa08+180\": file does not exist"]
413                 }
414             }));
415             // given
416             const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
417             const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
418             const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
419
420             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
421             const destinationPath = '/destinationPath';
422
423             // when
424             try {
425                 await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath);
426             } catch {}
427
428             // then
429             expect(serverApi.put).toHaveBeenCalledTimes(1);
430             // Verify copy
431             expect(serverApi.put).toHaveBeenCalledWith(
432                 `/collections/${destinationUuid}`, {
433                     collection: {
434                         preserve_version: true
435                     },
436                     replace_files: {
437                         [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`,
438                         [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`,
439                         [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
440                         [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
441                     },
442                 }
443             );
444         });
445     });
446
447     describe('createDirectory', () => {
448         it('creates empty directory', async () => {
449             // given
450             const directoryNames = [
451                 {in: 'newDir', out: 'newDir'},
452                 {in: '/fooDir', out: 'fooDir'},
453                 {in: '/anotherPath/', out: 'anotherPath'},
454                 {in: 'trailingSlash/', out: 'trailingSlash'},
455             ];
456             const collectionUuid = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
457
458             for (var i = 0; i < directoryNames.length; i++) {
459                 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
460                 // when
461                 await collectionService.createDirectory(collectionUuid, directoryNames[i].in);
462                 // then
463                 expect(serverApi.put).toHaveBeenCalledTimes(1);
464                 expect(serverApi.put).toHaveBeenCalledWith(
465                     `/collections/${collectionUuid}`, {
466                         collection: {
467                             preserve_version: true
468                         },
469                         replace_files: {
470                             ["/" + directoryNames[i].out]: emptyCollectionUuid,
471                         },
472                     }
473                 );
474             }
475         });
476     });
477
478 });