Skip to content

Commit

Permalink
BC-5758 - link element copy preview image (#4569)
Browse files Browse the repository at this point in the history
When a LinkElement is
copied: if it has a preview-image, it gets copied and the internal imageUrl is being updated
deleted: the file-storage will deleted associated files
  • Loading branch information
hoeppner-dataport authored Nov 23, 2023
1 parent 391aab8 commit e563729
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { FileRecordParentType } from '@infra/rabbitmq';
import { EntityManager } from '@mikro-orm/mongodb';
import { FileDto, FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { ContextExternalToolService } from '@modules/tool/context-external-tool/service';
import { Test, TestingModule } from '@nestjs/testing';
import { FileRecordParentType } from '@infra/rabbitmq';
import {
columnBoardFactory,
columnFactory,
Expand Down Expand Up @@ -171,6 +171,14 @@ describe(RecursiveDeleteVisitor.name, () => {
expect(em.remove).toHaveBeenCalledWith(em.getReference(linkElement.constructor, linkElement.id));
expect(em.remove).toHaveBeenCalledWith(em.getReference(childLinkElement.constructor, childLinkElement.id));
});

it('should call deleteFilesOfParent', async () => {
const { linkElement } = setup();

await service.visitLinkElementAsync(linkElement);

expect(filesStorageClientAdapterService.deleteFilesOfParent).toHaveBeenCalledWith(linkElement.id);
});
});

describe('visitSubmissionContainerElementAsync', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class RecursiveDeleteVisitor implements BoardCompositeVisitorAsync {
}

async visitLinkElementAsync(linkElement: LinkElement): Promise<void> {
await this.filesStorageClientAdapterService.deleteFilesOfParent(linkElement.id);
this.deleteNode(linkElement);

await this.visitChildrenAsync(linkElement);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { LinkElement } from '@shared/domain';
import { linkElementFactory, setupEntities } from '@shared/testing';
import { CopyFileDto } from '@src/modules/files-storage-client/dto';

import { RecursiveCopyVisitor } from './recursive-copy.visitor';
import { SchoolSpecificFileCopyServiceFactory } from './school-specific-file-copy-service.factory';
import { SchoolSpecificFileCopyService } from './school-specific-file-copy.interface';

describe(RecursiveCopyVisitor.name, () => {
let module: TestingModule;
let fileCopyServiceFactory: DeepMocked<SchoolSpecificFileCopyServiceFactory>;

beforeAll(async () => {
module = await Test.createTestingModule({
providers: [
{
provide: SchoolSpecificFileCopyServiceFactory,
useValue: createMock<SchoolSpecificFileCopyServiceFactory>(),
},
RecursiveCopyVisitor,
],
}).compile();

fileCopyServiceFactory = module.get(SchoolSpecificFileCopyServiceFactory);

await setupEntities();
});

describe('visitLinkElementAsync', () => {
const setup = (options: { withFileCopy: boolean } = { withFileCopy: false }) => {
const fileCopyServiceMock = createMock<SchoolSpecificFileCopyService>();
fileCopyServiceFactory.build.mockReturnValue(fileCopyServiceMock);
const sourceFileId = 'abe223e22134';
const imageUrl = `https://abc.de/file/${sourceFileId}`;

let newFileId: string | undefined;
let copyResultMock: CopyFileDto[] = [];
if (options?.withFileCopy) {
newFileId = 'bbbbbbb123';
copyResultMock = [
{
sourceId: sourceFileId,
id: newFileId,
name: 'myfile.jpg',
},
];
}

fileCopyServiceMock.copyFilesOfParent.mockResolvedValueOnce(copyResultMock);
return { fileCopyServiceMock, imageUrl, newFileId };
};

describe('when copying a LinkElement without preview url', () => {
it('should not call fileCopyService', async () => {
const { fileCopyServiceMock } = setup();

const linkElement = linkElementFactory.build();
const visitor = new RecursiveCopyVisitor(fileCopyServiceMock);

await visitor.visitLinkElementAsync(linkElement);

expect(fileCopyServiceMock.copyFilesOfParent).not.toHaveBeenCalled();
});
});

describe('when copying a LinkElement with preview image', () => {
it('should call fileCopyService', async () => {
const { fileCopyServiceMock, imageUrl } = setup({ withFileCopy: true });

const linkElement = linkElementFactory.build({ imageUrl });
const visitor = new RecursiveCopyVisitor(fileCopyServiceMock);

await visitor.visitLinkElementAsync(linkElement);

expect(fileCopyServiceMock.copyFilesOfParent).toHaveBeenCalledWith(
expect.objectContaining({ sourceParentId: linkElement.id })
);
});

it('should replace fileId in imageUrl', async () => {
const { fileCopyServiceMock, imageUrl, newFileId } = setup({ withFileCopy: true });

const linkElement = linkElementFactory.build({ imageUrl });
const visitor = new RecursiveCopyVisitor(fileCopyServiceMock);

await visitor.visitLinkElementAsync(linkElement);
const copy = visitor.copyMap.get(linkElement.id) as LinkElement;

expect(copy.imageUrl).toEqual(expect.stringContaining(newFileId as string));
});
});

describe('when copying a LinkElement with an unmatched image url', () => {
it('should remove the imageUrl', async () => {
const { fileCopyServiceMock } = setup({ withFileCopy: true });

const linkElement = linkElementFactory.build({ imageUrl: `https://abc.de/file/unknown-file-id` });
const visitor = new RecursiveCopyVisitor(fileCopyServiceMock);

await visitor.visitLinkElementAsync(linkElement);
const copy = visitor.copyMap.get(linkElement.id) as LinkElement;

expect(copy.imageUrl).toEqual('');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FileRecordParentType } from '@infra/rabbitmq';
import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper';
import {
AnyBoardDo,
BoardCompositeVisitorAsync,
Expand All @@ -12,8 +14,6 @@ import {
SubmissionItem,
} from '@shared/domain';
import { LinkElement } from '@shared/domain/domainobject/board/link-element.do';
import { FileRecordParentType } from '@infra/rabbitmq';
import { CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper';
import { ObjectId } from 'bson';
import { SchoolSpecificFileCopyService } from './school-specific-file-copy.interface';

Expand Down Expand Up @@ -133,11 +133,38 @@ export class RecursiveCopyVisitor implements BoardCompositeVisitorAsync {
createdAt: new Date(),
updatedAt: new Date(),
});
this.resultMap.set(original.id, {

const result: CopyStatus = {
copyEntity: copy,
type: CopyElementType.LINK_ELEMENT,
status: CopyStatusEnum.SUCCESS,
});
};

if (original.imageUrl) {
const fileCopy = await this.fileCopyService.copyFilesOfParent({
sourceParentId: original.id,
targetParentId: copy.id,
parentType: FileRecordParentType.BoardNode,
});
fileCopy.forEach((copyFileDto) => {
if (copyFileDto.id) {
if (copy.imageUrl.includes(copyFileDto.sourceId)) {
copy.imageUrl = copy.imageUrl.replace(copyFileDto.sourceId, copyFileDto.id);
} else {
copy.imageUrl = '';
}
}
});
const fileCopyStatus = fileCopy.map((copyFileDto) => {
return {
type: CopyElementType.FILE,
status: copyFileDto.id ? CopyStatusEnum.SUCCESS : CopyStatusEnum.FAIL,
title: copyFileDto.name ?? `(old fileid: ${copyFileDto.sourceId})`,
};
});
result.elements = fileCopyStatus;
}
this.resultMap.set(original.id, result);
this.copyMap.set(original.id, copy);

return Promise.resolve();
Expand Down

0 comments on commit e563729

Please sign in to comment.