4649437ab7769711a76c230c65c3b4e9ff6781aa
[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 } 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             const filePaths: string[] = [];
133             const collectionUUID = '';
134
135             // when
136             await collectionService.deleteFiles(collectionUUID, filePaths);
137
138             // then
139             expect(webdavClient.delete).not.toHaveBeenCalled();
140         });
141
142         it('should remove only root files', async () => {
143             // given
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 = '';
146
147             // when
148             await collectionService.deleteFiles(collectionUUID, filePaths);
149
150             // then
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");
155         });
156
157         it('should remove files with uuid prefix', async () => {
158             // given
159             const filePaths: string[] = ['/root/1'];
160             const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
161
162             // when
163             await collectionService.deleteFiles(collectionUUID, filePaths);
164
165             // then
166             expect(webdavClient.delete).toHaveBeenCalledTimes(1);
167             expect(webdavClient.delete).toHaveBeenCalledWith("c=zzzzz-tpzed-5o5tg0l9a57gxxx/root/1");
168         });
169     });
170
171     describe('batch file operations', () => {
172         it('should batch remove files', async () => {
173             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
174             // given
175             const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
176             const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
177
178             // when
179             await collectionService.batchFileDelete(collectionUUID, filePaths);
180
181             // then
182             expect(serverApi.put).toHaveBeenCalledTimes(1);
183             expect(serverApi.put).toHaveBeenCalledWith(
184                 `/collections/${collectionUUID}`, {
185                     collection: {
186                         preserve_version: true
187                     },
188                     replace_files: {
189                         '/root/1': '',
190                         '/secondFile': '',
191                         '/barefile.txt': '',
192                     },
193                 }
194             );
195         });
196
197         it('should batch copy files', async () => {
198             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
199             // given
200             const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
201             // const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
202             const collectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
203
204             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
205             const destinationPath = '/destinationPath';
206
207             // when
208             await collectionService.batchFileCopy(collectionPDH, filePaths, destinationUuid, destinationPath);
209
210             // then
211             expect(serverApi.put).toHaveBeenCalledTimes(1);
212             expect(serverApi.put).toHaveBeenCalledWith(
213                 `/collections/${destinationUuid}`, {
214                     collection: {
215                         preserve_version: true
216                     },
217                     replace_files: {
218                         [`${destinationPath}/1`]: `${collectionPDH}/root/1`,
219                         [`${destinationPath}/secondFile`]: `${collectionPDH}/secondFile`,
220                         [`${destinationPath}/barefile.txt`]: `${collectionPDH}/barefile.txt`,
221                     },
222                 }
223             );
224         });
225
226         it('should batch move files', async () => {
227             serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
228             // given
229             const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
230             const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
231             const srcCollectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
232
233             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
234             const destinationPath = '/destinationPath';
235
236             // when
237             await collectionService.batchFileMove(srcCollectionUUID, srcCollectionPDH, filePaths, destinationUuid, destinationPath);
238
239             // then
240             expect(serverApi.put).toHaveBeenCalledTimes(2);
241             // Verify copy
242             expect(serverApi.put).toHaveBeenCalledWith(
243                 `/collections/${destinationUuid}`, {
244                     collection: {
245                         preserve_version: true
246                     },
247                     replace_files: {
248                         [`${destinationPath}/rootFile`]: `${srcCollectionPDH}/rootFile`,
249                         [`${destinationPath}/secondFile`]: `${srcCollectionPDH}/secondFile`,
250                         [`${destinationPath}/subfile`]: `${srcCollectionPDH}/subpath/subfile`,
251                         [`${destinationPath}/barefile.txt`]: `${srcCollectionPDH}/barefile.txt`,
252                     },
253                 }
254             );
255             // Verify delete
256             expect(serverApi.put).toHaveBeenCalledWith(
257                 `/collections/${srcCollectionUUID}`, {
258                     collection: {
259                         preserve_version: true
260                     },
261                     replace_files: {
262                         "/rootFile": "",
263                         "/secondFile": "",
264                         "/subpath/subfile": "",
265                         "/barefile.txt": "",
266                     },
267                 }
268             );
269         });
270
271         it('should abort batch move when copy fails', async () => {
272             // Simulate failure to copy
273             serverApi.put = jest.fn(() => Promise.reject({
274                 data: {},
275                 response: {
276                     "errors": ["error getting snapshot of \"rootFile\" from \"8cd9ce1dfa21c635b620b1bfee7aaa08+180\": file does not exist"]
277                 }
278             }));
279             // given
280             const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
281             const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
282             const srcCollectionPDH = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
283
284             const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
285             const destinationPath = '/destinationPath';
286
287             // when
288             try {
289                 await collectionService.batchFileMove(srcCollectionUUID, srcCollectionPDH, filePaths, destinationUuid, destinationPath);
290             } catch {}
291
292             // then
293             expect(serverApi.put).toHaveBeenCalledTimes(1);
294             // Verify copy
295             expect(serverApi.put).toHaveBeenCalledWith(
296                 `/collections/${destinationUuid}`, {
297                     collection: {
298                         preserve_version: true
299                     },
300                     replace_files: {
301                         [`${destinationPath}/rootFile`]: `${srcCollectionPDH}/rootFile`,
302                         [`${destinationPath}/secondFile`]: `${srcCollectionPDH}/secondFile`,
303                         [`${destinationPath}/subfile`]: `${srcCollectionPDH}/subpath/subfile`,
304                         [`${destinationPath}/barefile.txt`]: `${srcCollectionPDH}/barefile.txt`,
305                     },
306                 }
307             );
308         });
309     });
310
311     describe('createDirectory', () => {
312         it('creates empty directory', async () => {
313             // given
314             const directoryNames = {
315                 'newDir': 'newDir',
316                 '/fooDir': 'fooDir',
317                 '/anotherPath/': 'anotherPath',
318                 'trailingSlash/': 'trailingSlash',
319             };
320             const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
321
322             Object.keys(directoryNames).map(async (path) => {
323                 // when
324                 await collectionService.createDirectory(collectionUUID, path);
325                 // then
326                 expect(webdavClient.mkdir).toHaveBeenCalledWith(`c=${collectionUUID}/${directoryNames[path]}`);
327             });
328         });
329     });
330
331 });