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, emptyCollectionUuid } 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 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
133 const filePaths: string[] = [];
134 const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
137 await collectionService.deleteFiles(collectionUUID, filePaths);
140 expect(serverApi.put).toHaveBeenCalledTimes(1);
141 expect(serverApi.put).toHaveBeenCalledWith(
142 `/collections/${collectionUUID}`, {
144 preserve_version: true
151 it('should remove only root files', async () => {
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';
158 await collectionService.deleteFiles(collectionUUID, filePaths);
161 expect(serverApi.put).toHaveBeenCalledTimes(1);
162 expect(serverApi.put).toHaveBeenCalledWith(
163 `/collections/${collectionUUID}`, {
165 preserve_version: true
168 '/root/3/300/test.txt': '',
176 it('should remove files with uuid prefix', async () => {
178 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
179 const filePaths: string[] = ['/root/1'];
180 const collectionUUID = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
183 await collectionService.deleteFiles(collectionUUID, filePaths);
186 expect(serverApi.put).toHaveBeenCalledTimes(1);
187 expect(serverApi.put).toHaveBeenCalledWith(
188 `/collections/${collectionUUID}`, {
190 preserve_version: true
199 it('should batch remove files', async () => {
200 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
202 const filePaths: string[] = ['/root/1', '/secondFile', 'barefile.txt'];
203 const collectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
206 await collectionService.deleteFiles(collectionUUID, filePaths);
209 expect(serverApi.put).toHaveBeenCalledTimes(1);
210 expect(serverApi.put).toHaveBeenCalledWith(
211 `/collections/${collectionUUID}`, {
213 preserve_version: true
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';
233 await collectionService.renameFile(collectionUuid, collectionPdh, oldPath, newPath);
235 expect(serverApi.put).toHaveBeenCalledTimes(1);
236 expect(serverApi.put).toHaveBeenCalledWith(
237 `/collections/${collectionUuid}`, {
239 preserve_version: true
242 [newPath]: `${collectionPdh}${oldPath}`,
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';
256 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
257 const destinationPath = '/destinationPath';
260 await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
263 expect(serverApi.put).toHaveBeenCalledTimes(1);
264 expect(serverApi.put).toHaveBeenCalledWith(
265 `/collections/${destinationUuid}`, {
267 preserve_version: true
270 [`${destinationPath}/1`]: `${sourcePdh}/root/1`,
271 [`${destinationPath}/secondFile`]: `${sourcePdh}/secondFile`,
272 [`${destinationPath}/barefile.txt`]: `${sourcePdh}/barefile.txt`,
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';
284 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
285 const destinationPath = '/destinationPath';
287 await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
289 expect(serverApi.put).toHaveBeenCalledTimes(1);
290 expect(serverApi.put).toHaveBeenCalledWith(
291 `/collections/${destinationUuid}`, {
293 preserve_version: true
296 [`${destinationPath}`]: `${sourcePdh}/`,
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';
308 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
309 const destinationPath = '/';
311 await collectionService.copyFiles(sourcePdh, filePaths, destinationUuid, destinationPath);
313 expect(serverApi.put).toHaveBeenCalledTimes(1);
314 expect(serverApi.put).toHaveBeenCalledWith(
315 `/collections/${destinationUuid}`, {
317 preserve_version: true
320 "/": `${sourcePdh}/`,
327 describe('moveFiles', () => {
328 it('should batch move files', async () => {
329 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
331 const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
332 const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
333 const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
335 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
336 const destinationPath = '/destinationPath';
339 await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath);
342 expect(serverApi.put).toHaveBeenCalledTimes(2);
344 expect(serverApi.put).toHaveBeenCalledWith(
345 `/collections/${destinationUuid}`, {
347 preserve_version: true
350 [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`,
351 [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`,
352 [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
353 [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
358 expect(serverApi.put).toHaveBeenCalledWith(
359 `/collections/${srcCollectionUUID}`, {
361 preserve_version: true
366 "/subpath/subfile": "",
373 it('should batch move files within collection', async () => {
374 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
376 const filePaths: string[] = ['/one', '/two', '/subpath/subfile', 'barefile.txt'];
377 const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
378 const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
380 const destinationPath = '/destinationPath';
383 await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, srcCollectionUUID, destinationPath);
386 expect(serverApi.put).toHaveBeenCalledTimes(1);
388 expect(serverApi.put).toHaveBeenCalledWith(
389 `/collections/${srcCollectionUUID}`, {
391 preserve_version: true
394 [`${destinationPath}/one`]: `${srcCollectionPdh}/one`,
396 [`${destinationPath}/two`]: `${srcCollectionPdh}/two`,
398 [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
399 ['/subpath/subfile']: '',
400 [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
401 ['/barefile.txt']: '',
407 it('should abort batch move when copy fails', async () => {
408 // Simulate failure to copy
409 serverApi.put = jest.fn(() => Promise.reject({
412 "errors": ["error getting snapshot of \"rootFile\" from \"8cd9ce1dfa21c635b620b1bfee7aaa08+180\": file does not exist"]
416 const filePaths: string[] = ['/rootFile', '/secondFile', '/subpath/subfile', 'barefile.txt'];
417 const srcCollectionUUID = 'zzzzz-4zz18-5o5tg0l9a57gxxx';
418 const srcCollectionPdh = '8cd9ce1dfa21c635b620b1bfee7aaa08+180';
420 const destinationUuid = 'zzzzz-4zz18-ywq0rvhwwhkjnfq';
421 const destinationPath = '/destinationPath';
425 await collectionService.moveFiles(srcCollectionUUID, srcCollectionPdh, filePaths, destinationUuid, destinationPath);
429 expect(serverApi.put).toHaveBeenCalledTimes(1);
431 expect(serverApi.put).toHaveBeenCalledWith(
432 `/collections/${destinationUuid}`, {
434 preserve_version: true
437 [`${destinationPath}/rootFile`]: `${srcCollectionPdh}/rootFile`,
438 [`${destinationPath}/secondFile`]: `${srcCollectionPdh}/secondFile`,
439 [`${destinationPath}/subfile`]: `${srcCollectionPdh}/subpath/subfile`,
440 [`${destinationPath}/barefile.txt`]: `${srcCollectionPdh}/barefile.txt`,
447 describe('createDirectory', () => {
448 it('creates empty directory', async () => {
450 const directoryNames = [
451 {in: 'newDir', out: 'newDir'},
452 {in: '/fooDir', out: 'fooDir'},
453 {in: '/anotherPath/', out: 'anotherPath'},
454 {in: 'trailingSlash/', out: 'trailingSlash'},
456 const collectionUuid = 'zzzzz-tpzed-5o5tg0l9a57gxxx';
458 for (var i = 0; i < directoryNames.length; i++) {
459 serverApi.put = jest.fn(() => Promise.resolve({ data: {} }));
461 await collectionService.createDirectory(collectionUuid, directoryNames[i].in);
463 expect(serverApi.put).toHaveBeenCalledTimes(1);
464 expect(serverApi.put).toHaveBeenCalledWith(
465 `/collections/${collectionUuid}`, {
467 preserve_version: true
470 ["/" + directoryNames[i].out]: emptyCollectionUuid,