/*!
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see .
*/
import { TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { of, throwError, Subject, Observable } from 'rxjs';
import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
import { DocumentListService, NodeAction } from '@alfresco/adf-content-services';
import { NodeActionsService } from './node-actions.service';
import { Node, NodeChildAssociationEntry, NodeEntry } from '@alfresco/js-api';
import { AppTestingModule } from '../testing/app-testing.module';
import { ContentApiService } from '@alfresco/aca-shared';
class TestNode {
entry: Node;
constructor(id?: string, isFile?: boolean, name?: string, permission?: string[], nodeType?: string, properties?: any) {
this.entry = {} as any;
this.entry.id = id || 'node-id';
this.entry.isFile = isFile;
this.entry.isFolder = !isFile;
this.entry.nodeType = nodeType ? nodeType : isFile ? 'content' : 'folder';
this.entry.name = name;
if (permission) {
this.entry['allowableOperations'] = permission;
}
if (properties) {
this.entry.properties = properties;
}
}
}
describe('NodeActionsService', () => {
const actionIsForbidden = true;
const isFile = true;
const folderDestinationId = 'folder-destination-id';
const fileId = 'file-to-be-copied-id';
const conflictError = new Error(JSON.stringify({ error: { statusCode: 409 } }));
const permissionError = new Error(JSON.stringify({ error: { statusCode: 403 } }));
const badRequestError = new Error(JSON.stringify({ error: { statusCode: 400 } }));
const emptyChildrenList = { list: { entries: [] } };
let service: any;
let apiService: AlfrescoApiService;
let nodesApi: any;
let spyOnSuccess: jasmine.Spy;
let spyOnError: jasmine.Spy;
let contentApi: ContentApiService;
let dialog: MatDialog;
const helper = {
fakeCopyNode:
(isForbidden: boolean = false, nameExistingOnDestination?: string) =>
(_entryId, options) =>
new Promise((resolve, reject) => {
if (isForbidden) {
reject(permissionError);
} else if (nameExistingOnDestination && options && options.name === nameExistingOnDestination) {
reject(conflictError);
} else {
resolve('');
}
}),
fakeGetNodeChildren:
(familyNodes: { parentNodeId: string; nodeChildren: any[] }[], isForbidden: boolean = false) =>
(parentId) =>
new Promise((resolve, reject) => {
if (isForbidden) {
reject(permissionError);
} else {
const node = familyNodes.filter((familyNode) => familyNode.parentNodeId === parentId);
resolve(node.length > 0 ? { list: { entries: node[0].nodeChildren } } : emptyChildrenList);
}
})
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule, MatDialogModule]
});
spyOnSuccess = jasmine.createSpy('spyOnSuccess');
spyOnError = jasmine.createSpy('spyOnError');
contentApi = TestBed.inject(ContentApiService);
service = TestBed.inject(NodeActionsService);
apiService = TestBed.inject(AlfrescoApiService);
dialog = TestBed.inject(MatDialog);
apiService.reset();
nodesApi = service['nodesApi'];
});
describe('ContentNodeSelector configuration', () => {
it('should validate selection when allowableOperation has `create`', () => {
spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
const isSelectionValid = dialog.open['calls'].argsFor(0)[1].data.isSelectionValid({
name: 'some-folder-template',
isFile: false,
isFolder: true,
path: { elements: [{}, {}] },
allowableOperations: ['create']
});
expect(isSelectionValid).toBe(true);
});
it('should invalidate selection when allowableOperation does not have `create`', () => {
spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
const isSelectionValid = dialog.open['calls'].argsFor(0)[1].data.isSelectionValid({
name: 'some-folder-template',
isFile: false,
isFolder: true,
path: { elements: [{}, {}] },
allowableOperations: ['any']
});
expect(isSelectionValid).toBe(false);
});
it('should invalidate selection if isSite', () => {
spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
const isSelectionValid = dialog.open['calls'].argsFor(0)[1].data.isSelectionValid({
name: 'some-folder-template',
isFile: false,
isFolder: true,
path: { elements: [{}, {}] },
nodeType: 'st:site',
allowableOperations: ['create']
});
expect(isSelectionValid).toBe(false);
});
it('should validate selection if not a Site', () => {
spyOn(dialog, 'open').and.returnValue({
afterClosed: of
} as MatDialogRef);
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
service.getContentNodeSelection(NodeAction.CHOOSE, contentEntities as NodeEntry[]);
const isSelectionValid = dialog.open['calls'].argsFor(0)[1].data.isSelectionValid({
name: 'some-folder-template',
isFile: false,
isFolder: true,
path: { elements: [{}, {}] },
nodeType: 'cm:folder',
allowableOperations: ['create']
});
expect(isSelectionValid).toBe(true);
});
});
describe('doBatchOperation', () => {
it('should throw error if "contentEntities" required parameter is missing', (done) => {
const contentEntities = undefined;
const doCopyBatchOperation = service.copyNodes(contentEntities).asObservable();
doCopyBatchOperation
.toPromise()
.then(
() => spyOnSuccess(),
(error) => spyOnError(error)
)
.then(() => {
expect(spyOnSuccess).not.toHaveBeenCalled();
expect(spyOnError).toHaveBeenCalled();
done();
});
});
it('should throw error if "contentEntities" is not an array of entry entities', (done) => {
const contentEntities = [new TestNode(), {}];
const doCopyBatchOperation = service.copyNodes(contentEntities).asObservable();
doCopyBatchOperation
.toPromise()
.then(
() => spyOnSuccess(),
(error) => spyOnError(error)
)
.then(() => {
expect(spyOnSuccess).not.toHaveBeenCalled();
expect(spyOnError).toHaveBeenCalledWith(badRequestError);
done();
});
});
it('should throw error if an entry in "contentEntities" does not have id nor nodeId property', (done) => {
const contentEntities = [new TestNode(), { entry: {} }];
const doCopyBatchOperation = service.copyNodes(contentEntities).asObservable();
doCopyBatchOperation
.toPromise()
.then(
() => spyOnSuccess(),
(error) => spyOnError(error)
)
.then(() => {
expect(spyOnSuccess).not.toHaveBeenCalled();
expect(spyOnError).toHaveBeenCalledWith(badRequestError);
done();
});
});
it('should not throw error if entry in "contentEntities" does not have id, but has nodeId property', () => {
const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }];
const subject = new Subject();
spyOn(service, 'getContentNodeSelection').and.returnValue(subject);
spyOn(service, 'copyNodeAction').and.returnValue(of({}));
service.copyNodes(contentEntities).subscribe(spyOnSuccess, spyOnError);
subject.next([new TestNode().entry]);
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalledWith(badRequestError);
});
});
describe('getEntryParentId', () => {
it('should return the parentId, if that exists on the node entry', () => {
const parentID = 'parent-id';
const entry: any = { nodeId: '1234', parentId: parentID };
expect(service.getEntryParentId(entry)).toBe(parentID);
});
it('should give the last element in path property, if parentId is missing and path exists on the node entry', () => {
const firstParentId = 'parent-0-id';
const entry: any = {
nodeId: '1234',
path: { elements: [{ id: 'parent-1-id' }, { id: firstParentId }] }
};
expect(service.getEntryParentId(entry)).toBe(firstParentId);
});
});
describe('rowFilter', () => {
let fileToCopy: TestNode;
let folderToCopy: TestNode;
let dialogData: any;
beforeEach(() => {
fileToCopy = new TestNode(fileId, isFile, 'file-name');
folderToCopy = new TestNode();
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data;
return { componentInstance: {}, afterClosed: of } as MatDialogRef;
});
service.copyNodes([fileToCopy, folderToCopy]);
});
it('should filter destination nodes and not show files', () => {
const file = new TestNode('a-file', isFile);
expect(dialogData.data.rowFilter({ node: file })).toBe(false);
});
it('should filter destination nodes and not show the symlinks', () => {
const symlinkDestinationFolder = new TestNode('symlink-id', !isFile, 'symlink-name', [], 'app:folderlink');
expect(
dialogData.data.rowFilter({
node: symlinkDestinationFolder
})
).toBe(false);
});
it('should filter destination nodes and show folders', () => {
const destinationFolder = new TestNode(folderDestinationId);
expect(
dialogData.data.rowFilter({
node: destinationFolder
})
).toBe(true);
});
});
describe('copyNodes', () => {
let fileToCopy: TestNode;
let folderToCopy: TestNode;
let destinationFolder: TestNode;
let translationService: TranslationService;
beforeEach(() => {
fileToCopy = new TestNode(fileId, isFile, 'file-name');
folderToCopy = new TestNode();
destinationFolder = new TestNode(folderDestinationId);
translationService = TestBed.inject(TranslationService);
spyOn(translationService, 'instant').and.callFake((key) => key);
});
it('should be called', () => {
const subject = new Subject();
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
spyOn(service, 'getContentNodeSelection').and.returnValue(subject);
spyOn(service, 'copyNodeAction').and.returnValue(of({}));
service.copyNodes([fileToCopy, folderToCopy]);
subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation.calls.count()).toEqual(1);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined, undefined);
});
it('should use the custom data object with custom rowFilter & imageResolver & title with destination picker', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let dialogData = null;
const spyOnDialog = spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data;
return { componentInstance: {}, afterClosed: of } as MatDialogRef;
});
service.copyNodes([fileToCopy, folderToCopy]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.COPY, [fileToCopy, folderToCopy], undefined, undefined);
expect(spyOnDestinationPicker.calls.count()).toEqual(1);
expect(spyOnDialog.calls.count()).toEqual(1);
expect(dialogData).toBeDefined();
expect(
dialogData.data.rowFilter({
node: destinationFolder
})
).toBeDefined();
expect(
dialogData.data.imageResolver({
node: destinationFolder
})
).toBeDefined();
expect(dialogData.data.title).toBe('NODE_SELECTOR.COPY_ITEMS');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEMS', {
name: '',
number: 2
});
destinationFolder.entry['allowableOperations'] = ['update'];
expect(
dialogData.data.imageResolver({
node: destinationFolder
})
).toBeDefined();
});
it('should use the ContentNodeSelectorComponentData object with file name in title', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let dialogData: any;
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data;
return { componentInstance: {}, afterClosed: of } as MatDialogRef;
});
service.copyNodes([{ entry: { id: 'entry-id', name: 'entry-name' } }]);
expect(spyOnBatchOperation).toHaveBeenCalled();
expect(dialogData).toBeDefined();
expect(dialogData.data.title).toBe('NODE_SELECTOR.COPY_ITEM');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEM', {
name: 'entry-name',
number: 1
});
});
it('should use the ContentNodeSelectorComponentData object without file name in title, if no name exists', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let dialogData = null;
spyOn(dialog, 'open').and.callFake((_contentNodeSelectorComponent: any, data: any) => {
dialogData = data;
return { componentInstance: {}, afterClosed: of } as MatDialogRef;
});
service.copyNodes([{ entry: { id: 'entry-id' } }]);
expect(spyOnBatchOperation).toHaveBeenCalled();
expect(dialogData).toBeDefined();
expect(dialogData.data.title).toBe('NODE_SELECTOR.COPY_ITEMS');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEMS', {
name: '',
number: 1
});
});
});
describe('copyNodeAction', () => {
it('should copy one folder node to destination', () => {
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
const folderToCopy = new TestNode();
const folderDestination = new TestNode(folderDestinationId);
service.copyNodeAction(folderToCopy.entry, folderDestination.entry.id);
expect(nodesApi.copyNode).toHaveBeenCalledWith(folderToCopy.entry.id, {
targetParentId: folderDestination.entry.id,
name: undefined
});
});
it('should copy one file node to destination', () => {
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode());
const fileToCopy = new TestNode(fileId, isFile, 'file-name');
const folderDestination = new TestNode(folderDestinationId);
service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
expect(nodesApi.copyNode).toHaveBeenCalledWith(fileToCopy.entry.id, {
targetParentId: folderDestination.entry.id,
name: 'file-name'
});
});
it('should fail to copy folder node if action is forbidden', (done) => {
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(actionIsForbidden));
const folderToCopy = new TestNode();
const folderDestination = new TestNode(folderDestinationId);
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
const copyObservable = service.copyNodeAction(folderToCopy.entry, folderDestination.entry.id);
copyObservable
.toPromise()
.then(
(response) => spyOnSuccess(response),
() => {
spyOnError();
expect(spyContentAction.calls.count()).toEqual(0);
expect(spyFolderAction.calls.count()).toEqual(1);
expect(nodesApi.copyNode).toHaveBeenCalledWith(folderToCopy.entry.id, {
targetParentId: folderDestination.entry.id,
name: undefined
});
}
)
.then(() => {
expect(spyOnSuccess.calls.count()).toEqual(1);
expect(spyOnSuccess).toHaveBeenCalledWith(permissionError);
expect(spyOnError.calls.count()).toEqual(0);
done();
});
});
it('should fail to copy file node if action is forbidden', (done) => {
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(actionIsForbidden));
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
const spyFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
const fileToCopy = new TestNode(fileId, isFile, 'test-name');
const folderDestination = new TestNode(folderDestinationId);
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
copyObservable
.toPromise()
.then(
(response) => spyOnSuccess(response),
() => spyOnError()
)
.then(() => {
expect(spyOnSuccess).toHaveBeenCalledWith(permissionError);
expect(spyOnError).not.toHaveBeenCalled();
expect(spyContentAction).toHaveBeenCalled();
expect(spyFolderAction).not.toHaveBeenCalled();
expect(nodesApi.copyNode).toHaveBeenCalledWith(fileToCopy.entry.id, {
targetParentId: folderDestination.entry.id,
name: 'test-name'
});
done();
});
});
it('should copy one file node to same destination and autoRename it', (done) => {
spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(!actionIsForbidden, 'file-name'));
const spyContentAction = spyOn(service, 'copyContentAction').and.callThrough();
const fileToCopy = new TestNode(fileId, isFile, 'file-name');
const folderDestination = new TestNode(folderDestinationId);
const copyObservable = service.copyNodeAction(fileToCopy.entry, folderDestination.entry.id);
copyObservable
.toPromise()
.then(
() => spyOnSuccess(),
() => spyOnError()
)
.then(() => {
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
expect(spyContentAction.calls.count()).toEqual(2);
expect(nodesApi.copyNode).toHaveBeenCalledWith(fileToCopy.entry.id, {
targetParentId: folderDestination.entry.id,
name: 'file-name-1'
});
done();
});
});
describe('should copy content of folder-to-copy to folder with same name from destination folder', () => {
let folderToCopy: TestNode;
let fileChildOfFolderToCopy: TestNode;
let folderParentAndDestination: TestNode;
let existingFolder: NodeChildAssociationEntry;
let spy: jasmine.Spy;
let spyOnContentAction: jasmine.Spy;
let spyOnFolderAction: jasmine.Spy;
let copyObservable: Observable;
let subject: Subject;
beforeEach(() => {
subject = new Subject();
folderToCopy = new TestNode('folder-to-copy-id', !isFile, 'conflicting-name');
fileChildOfFolderToCopy = new TestNode(fileId, isFile, 'file-name');
folderParentAndDestination = new TestNode(folderDestinationId);
existingFolder = new TestNode('existing-folder-id', !isFile, 'conflicting-name') as NodeChildAssociationEntry;
spy = spyOn(nodesApi, 'copyNode').and.callFake(helper.fakeCopyNode(!actionIsForbidden, 'conflicting-name'));
spyOnContentAction = spyOn(service, 'copyContentAction').and.callThrough();
spyOnFolderAction = spyOn(service, 'copyFolderAction').and.callThrough();
copyObservable = service.copyNodeAction(folderToCopy.entry, folderParentAndDestination.entry.id);
});
it('when folder to copy has a file as content', (done) => {
const testFamilyNodes = [
{
parentNodeId: folderToCopy.entry.id,
nodeChildren: [fileChildOfFolderToCopy]
},
{
parentNodeId: folderParentAndDestination.entry.id,
nodeChildren: [existingFolder]
}
];
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes));
spyOn(service, 'getChildByName').and.returnValue(of(existingFolder) as any);
copyObservable
.toPromise()
.then(
() => spyOnSuccess(),
() => spyOnError()
)
.then(() => {
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
expect(spyOnContentAction).toHaveBeenCalled();
expect(spyOnFolderAction).toHaveBeenCalled();
expect(spy.calls.allArgs()).toEqual([
[
folderToCopy.entry.id,
{
targetParentId: folderParentAndDestination.entry.id,
name: 'conflicting-name'
}
],
[
fileChildOfFolderToCopy.entry.id,
{
targetParentId: existingFolder.entry.id,
name: 'file-name'
}
]
]);
done();
});
});
it('when folder to copy is empty', (done) => {
const testFamilyNodes = [
{
parentNodeId: folderToCopy.entry.id,
nodeChildren: []
},
{
parentNodeId: folderParentAndDestination.entry.id,
nodeChildren: [existingFolder]
}
];
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes));
spyOn(service, 'getChildByName').and.returnValue(of({}) as any);
copyObservable
.toPromise()
.then(
() => spyOnSuccess(),
() => spyOnError()
)
.then(() => {
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
expect(spyOnContentAction).not.toHaveBeenCalled();
expect(spyOnFolderAction).toHaveBeenCalled();
expect(spy.calls.allArgs()).toEqual([
[
folderToCopy.entry.id,
{
targetParentId: folderParentAndDestination.entry.id,
name: 'conflicting-name'
}
]
]);
done();
});
subject.next(existingFolder);
});
it('when folder to copy has another folder as child', (done) => {
const folderChild = new TestNode('folder-child-id');
const testFamilyNodes = [
{
parentNodeId: folderToCopy.entry.id,
nodeChildren: [folderChild]
},
{
parentNodeId: folderParentAndDestination.entry.id,
nodeChildren: [existingFolder]
}
];
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes));
spyOn(service, 'getChildByName').and.returnValue(of(existingFolder) as any);
copyObservable
.toPromise()
.then(
() => spyOnSuccess(),
() => spyOnError()
)
.then(() => {
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
expect(spyOnContentAction).not.toHaveBeenCalled();
expect(spyOnFolderAction.calls.count()).toEqual(2);
expect(spy.calls.allArgs()).toEqual([
[
folderToCopy.entry.id,
{
targetParentId: folderParentAndDestination.entry.id,
name: 'conflicting-name'
}
],
[
folderChild.entry.id,
{
targetParentId: existingFolder.entry.id,
name: undefined
}
]
]);
done();
});
});
});
});
describe('moveNodes', () => {
const permissionToMove = 'delete';
let fileToMove: TestNode;
let folderToMove: TestNode;
let destinationFolder: TestNode;
let spyOnBatchOperation: jasmine.Spy;
let documentListService: DocumentListService;
let subject: Subject;
beforeEach(() => {
subject = new Subject();
fileToMove = new TestNode('file-to-be-moved', isFile, 'file-name');
folderToMove = new TestNode('fid', !isFile, 'folder-name');
destinationFolder = new TestNode(folderDestinationId);
documentListService = TestBed.inject(DocumentListService);
spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
});
it('should allow to select destination for nodes that have permission to be moved', () => {
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.returnValue(subject);
spyOn(service, 'moveContentAction').and.returnValue(of({}));
spyOn(service, 'moveFolderAction').and.returnValue(of({}));
fileToMove.entry['allowableOperations'] = [permissionToMove];
folderToMove.entry['allowableOperations'] = [permissionToMove];
service.moveNodes([fileToMove, folderToMove], permissionToMove);
subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove, undefined);
expect(spyOnDestinationPicker).toHaveBeenCalled();
});
it('should not allow to select destination for nodes that do not have permission to be moved', () => {
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.returnValue(subject);
fileToMove.entry['allowableOperations'] = [];
folderToMove.entry['allowableOperations'] = [];
service.moveNodes([fileToMove, folderToMove], permissionToMove);
subject.next([destinationFolder.entry]);
expect(spyOnBatchOperation).toHaveBeenCalledWith(NodeAction.MOVE, [fileToMove, folderToMove], permissionToMove, undefined);
expect(spyOnDestinationPicker).not.toHaveBeenCalled();
});
it('should call the documentListService moveNode directly for moving a file that has permission to be moved', () => {
spyOn(service, 'getContentNodeSelection').and.returnValue(subject);
fileToMove.entry['allowableOperations'] = [permissionToMove];
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.returnValue(of(fileToMove));
spyOn(service, 'moveNodeAction');
service.moveNodes([fileToMove], permissionToMove);
subject.next([destinationFolder.entry]);
expect(service.moveNodeAction).not.toHaveBeenCalled();
expect(moveNodeSpy).toHaveBeenCalled();
});
describe('moveContentAction', () => {
it('should not throw error on conflict, to be able to show message in case of partial move of files', (done) => {
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.returnValue(throwError(conflictError));
const moveContentActionObservable = service.moveContentAction(fileToMove.entry, folderDestinationId);
moveContentActionObservable
.toPromise()
.then(
(value) => spyOnSuccess(value),
(error) => spyOnError(error)
)
.then(() => {
expect(moveNodeSpy).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalledWith(conflictError);
expect(spyOnError).not.toHaveBeenCalledWith(conflictError);
done();
});
});
it('should not throw permission error, to be able to show message in case of partial move of files', (done) => {
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.returnValue(throwError(permissionError));
const moveContentActionObservable = service.moveContentAction(fileToMove.entry, folderDestinationId);
moveContentActionObservable
.toPromise()
.then(
(value) => spyOnSuccess(value),
(error) => spyOnError(error)
)
.then(() => {
expect(moveNodeSpy).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalledWith(permissionError);
expect(spyOnError).not.toHaveBeenCalledWith(permissionError);
done();
});
});
it('in case of success, should return also the initial parent id of the moved node', (done) => {
const parentID = 'parent-id';
fileToMove.entry['parentId'] = parentID;
fileToMove.entry['allowableOperations'] = [permissionToMove];
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.returnValue(of(fileToMove));
service
.moveContentAction(fileToMove.entry, folderDestinationId)
.toPromise()
.then(
(value) => spyOnSuccess(value),
(error) => spyOnError(error)
)
.then(() => {
expect(moveNodeSpy).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalledWith({
itemMoved: fileToMove,
initialParentId: parentID
});
expect(spyOnError).not.toHaveBeenCalledWith(permissionError);
done();
});
});
});
describe('moveFolderAction', () => {
it('should not throw permission error in case it occurs on folder move', (done) => {
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.returnValue(throwError(permissionError));
service
.moveFolderAction(folderToMove.entry, folderDestinationId)
.toPromise()
.then(
(value) => spyOnSuccess(value),
(error) => spyOnError(error)
)
.then(() => {
expect(moveNodeSpy).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalledWith(permissionError);
expect(spyOnError).not.toHaveBeenCalled();
done();
});
});
it('should not throw error on conflict in case it occurs on folder move', () => {
const newDestination = new TestNode('new-destination', !isFile, folderToMove.entry.name) as NodeChildAssociationEntry;
spyOn(documentListService, 'moveNode').and.returnValue(throwError(conflictError));
const subject$ = new Subject();
spyOn(service, 'getChildByName').and.returnValue(subject$);
spyOn(nodesApi, 'listNodeChildren').and.returnValue(of(emptyChildrenList));
service.moveFolderAction(folderToMove.entry, folderDestinationId).subscribe(spyOnSuccess, spyOnError);
subject$.next(newDestination);
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalledWith(conflictError);
});
it('should try to move children nodes of a folder to already existing folder with same name', () => {
const parentFolderToMove = new TestNode('parent-folder', !isFile, 'conflicting-name');
const moveNodeSpy = spyOn(documentListService, 'moveNode').and.callFake((nodeId: string) => {
if (nodeId === parentFolderToMove.entry.id) {
return throwError(conflictError);
}
return of({} as NodeEntry);
});
spyOn(service, 'moveContentAction').and.returnValue(of({}));
const newDestination = new TestNode('new-destination', !isFile, 'conflicting-name') as NodeChildAssociationEntry;
const subject$ = new Subject();
spyOn(service, 'getChildByName').and.returnValue(subject$);
const childrenNodes = [fileToMove, folderToMove];
spyOn(nodesApi, 'listNodeChildren').and.returnValue(of({ list: { entries: childrenNodes } }));
service.moveFolderAction(parentFolderToMove.entry, folderDestinationId).subscribe(spyOnSuccess, spyOnError);
subject$.next(newDestination);
expect(moveNodeSpy).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalledWith(conflictError);
});
});
describe('moveNodeAction', () => {
describe('on moving folder to a destination where a folder with the same name exists', () => {
let parentFolderToMove: TestNode;
let spyOnDelete: jasmine.Spy;
let subject$: Subject;
beforeEach(() => {
subject$ = new Subject();
parentFolderToMove = new TestNode('parent-folder', !isFile, 'conflicting-name');
spyOnDelete = spyOn(contentApi, 'deleteNode').and.returnValue(of(null));
});
it('should take no extra delete action, if folder was moved to the same location', (done) => {
spyOn(service, 'moveFolderAction').and.returnValue(of(null));
parentFolderToMove.entry.parentId = folderDestinationId;
const moveNodeActionPromise = service.moveNodeAction(parentFolderToMove.entry, folderDestinationId).toPromise();
moveNodeActionPromise
.then(
() => spyOnSuccess(),
(error) => spyOnError(error)
)
.then(() => {
expect(spyOnDelete).not.toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
done();
});
});
it('should take no extra delete action, if its children were partially moved', (done) => {
const movedChildrenNodes = [fileToMove, folderToMove];
spyOn(service, 'moveFolderAction').and.returnValue(of(movedChildrenNodes));
spyOn(service, 'processResponse').and.returnValue({
succeeded: [fileToMove],
failed: [folderToMove],
partiallySucceeded: []
});
parentFolderToMove.entry.parentId = `not-${folderDestinationId}`;
const moveNodeActionPromise = service.moveNodeAction(parentFolderToMove.entry, folderDestinationId).toPromise();
moveNodeActionPromise
.then(
() => spyOnSuccess(),
(error) => spyOnError(error)
)
.then(() => {
expect(spyOnDelete).not.toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
done();
});
});
it('should take extra delete action, if children successfully moved and folder is still on location', () => {
const movedChildrenNodes = [fileToMove, folderToMove];
spyOn(service, 'moveFolderAction').and.returnValue(of(movedChildrenNodes));
spyOn(service, 'processResponse').and.returnValue({
succeeded: [movedChildrenNodes],
failed: [],
partiallySucceeded: []
});
const folderOnLocation = parentFolderToMove;
spyOn(service, 'getChildByName').and.returnValue(subject$);
parentFolderToMove.entry.parentId = `not-${folderDestinationId}`;
service.moveNodeAction(parentFolderToMove.entry, folderDestinationId).subscribe(spyOnSuccess, spyOnError);
subject$.next(folderOnLocation);
expect(spyOnDelete).toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
});
it('should take no extra delete action, if folder is no longer on location', () => {
const movedChildrenNodes = [fileToMove, folderToMove];
spyOn(service, 'moveFolderAction').and.returnValue(of(movedChildrenNodes));
spyOn(service, 'processResponse').and.returnValue({
succeeded: [movedChildrenNodes],
failed: [],
partiallySucceeded: []
});
spyOn(service, 'getChildByName').and.returnValue(subject$);
parentFolderToMove.entry.parentId = `not-${folderDestinationId}`;
service.moveNodeAction(parentFolderToMove.entry, folderDestinationId).subscribe(spyOnSuccess, spyOnError);
subject$.next(null);
expect(spyOnDelete).not.toHaveBeenCalled();
expect(spyOnSuccess).toHaveBeenCalled();
expect(spyOnError).not.toHaveBeenCalled();
});
});
});
});
describe('getChildByName', () => {
let testFamilyNodes: Array<{ parentNodeId: string; nodeChildren: TestNode[] }>;
let notChildNode: TestNode;
let childNode: TestNode;
beforeEach(() => {
childNode = new TestNode(fileId, isFile, 'child-name');
const parentNode = new TestNode();
notChildNode = new TestNode('not-child-id', !isFile, 'not-child-name');
testFamilyNodes = [
{
parentNodeId: parentNode.entry.id,
nodeChildren: [childNode]
},
{
parentNodeId: notChildNode.entry.id,
nodeChildren: []
}
];
});
it('emits child node with specified name, when it exists in folder', (done) => {
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes));
service.getChildByName(testFamilyNodes[0].parentNodeId, childNode.entry.name).subscribe((value) => {
expect(value).toEqual(childNode);
done();
});
});
it('emits null value when child with specified name is not found in folder', (done) => {
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes));
service.getChildByName(testFamilyNodes[0].parentNodeId, notChildNode.entry.name).subscribe((value) => {
expect(value).toEqual(null);
done();
});
});
it('emits error when permission error occurs', (done) => {
spyOn(nodesApi, 'listNodeChildren').and.callFake(helper.fakeGetNodeChildren(testFamilyNodes, actionIsForbidden));
service.getChildByName(testFamilyNodes[0].parentNodeId, notChildNode.entry.name).subscribe(
() => {},
(err) => {
expect(err.message).toBe('{"error":{"statusCode":403}}');
done();
}
);
});
});
describe('getNewNameFrom', () => {
const testData: Array<{ name: string; baseName: string; expected: string }> = [
{
name: 'noExtension',
baseName: 'noExtension',
expected: 'noExtension-1'
},
{
name: 'withExtension.txt',
baseName: 'withExtension.txt',
expected: 'withExtension-1.txt'
},
{
name: 'with-lineStringSuffix.txt',
baseName: 'with-lineStringSuffix.txt',
expected: 'with-lineStringSuffix-1.txt'
},
{
name: 'noExtension-1',
baseName: 'noExtension-1',
expected: 'noExtension-1-1'
},
{
name: 'with-lineNumberSuffix-1.txt',
baseName: 'with-lineNumberSuffix-1.txt',
expected: 'with-lineNumberSuffix-1-1.txt'
},
{
name: 'with-lineNumberSuffix.txt',
baseName: undefined,
expected: 'with-lineNumberSuffix-1.txt'
},
{
name: 'noExtension-1',
baseName: 'noExtension',
expected: 'noExtension-2'
},
{
name: 'noExtension-7',
baseName: undefined,
expected: 'noExtension-8'
},
{
name: 'noExtension-007',
baseName: undefined,
expected: 'noExtension-007-1'
}
];
testData.forEach((data) => {
it(`new name should be \'${data.expected}\' for given name: \'${data.name}\', and baseName: \'${data.baseName}\'`, () => {
const result = service.getNewNameFrom(data.name, data.baseName);
expect(result).toBe(data.expected);
});
});
});
describe('flatten', () => {
const testNode1 = new TestNode('node1-id', isFile, 'node1-name');
const testNode2 = new TestNode('node2-id', !isFile, 'node2-name');
const testData = [
{
nDimArray: [testNode1],
expected: [testNode1]
},
{
nDimArray: [[testNode1], [testNode2]],
expected: [testNode1, testNode2]
},
{
nDimArray: [[[[testNode1]], testNode2]],
expected: [testNode2, testNode1]
}
];
testData.forEach((data) => {
it(`flattened array should be \'${data.expected}\' for given data: \'${data.nDimArray}\'`, () => {
const result = service.flatten(data.nDimArray);
expect(result.length).toBe(data.expected.length);
expect(JSON.stringify(result)).toEqual(JSON.stringify(data.expected));
});
});
});
describe('processResponse', () => {
const testNode1 = new TestNode('node1-id', isFile, 'node1-name');
const testNode2 = new TestNode('node2-id', !isFile, 'node2-name');
const parentID = 'patent-1-id';
testNode1.entry['parentId'] = parentID;
const testData = [
{
data: [testNode1],
expected: {
succeeded: [testNode1],
failed: [],
partiallySucceeded: []
}
},
{
data: [[{ itemMoved: testNode1, initialParentId: parentID }]],
expected: {
succeeded: [[{ itemMoved: testNode1, initialParentId: parentID }]],
failed: [],
partiallySucceeded: []
}
},
{
data: [conflictError],
expected: {
succeeded: [],
failed: [conflictError],
partiallySucceeded: []
}
},
{
data: [conflictError, testNode2],
expected: {
succeeded: [testNode2],
failed: [conflictError],
partiallySucceeded: []
}
},
{
data: [conflictError, [testNode2, conflictError]],
expected: {
succeeded: [],
failed: [conflictError],
partiallySucceeded: [[testNode2, conflictError]]
}
},
{
data: [conflictError, [{}, conflictError]],
expected: {
succeeded: [],
failed: [conflictError, [{}, conflictError]],
partiallySucceeded: []
}
},
{
data: {},
expected: {
succeeded: [],
failed: [{}],
partiallySucceeded: []
}
},
{
data: testNode1,
expected: {
succeeded: [testNode1],
failed: [],
partiallySucceeded: []
}
},
{
data: { itemMoved: testNode1, initialParentId: parentID },
expected: {
succeeded: [{ itemMoved: testNode1, initialParentId: parentID }],
failed: [],
partiallySucceeded: []
}
}
];
testData.forEach((response) => {
it(`processed response should be \'${response.expected}\' for given input: \'${response.data}\'`, () => {
const result = service.processResponse(response.data);
expect(JSON.stringify(result)).toEqual(JSON.stringify(response.expected));
});
});
});
});