From 30abe5bbc07a40af50cf5ace5781174d93f2144f Mon Sep 17 00:00:00 2001 From: Max Bischof Date: Thu, 2 Nov 2023 15:57:59 +0100 Subject: [PATCH 1/6] Generate preview for pdf --- .../files-storage/interface/preview-input-mime-types.enum.ts | 1 + .../infra/preview-generator/preview-generator.service.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts b/apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts index 495096c8359..20c24b65fd1 100644 --- a/apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts +++ b/apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts @@ -8,4 +8,5 @@ export enum PreviewInputMimeTypes { IMAGE_HEIF = 'image/heif', IMAGE_TIFF = 'image/tiff', IMAGE_WEBP = 'image/webp', + APPLICATION_PDF = 'application/pdf', } diff --git a/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts b/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts index 72dac25f076..a12fed2517d 100644 --- a/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts +++ b/apps/server/src/shared/infra/preview-generator/preview-generator.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import { GetFile, S3ClientAdapter } from '@shared/infra/s3-client'; import { Logger } from '@src/core/logger'; +import { PreviewInputMimeTypes } from '@src/modules/files-storage/interface'; import { subClass } from 'gm'; import { PassThrough } from 'stream'; import { PreviewFileOptions, PreviewOptions, PreviewResponseMessage } from './interface'; @@ -45,6 +46,10 @@ export class PreviewGeneratorService { const preview = this.imageMagick(original.data); + if (original.contentType === PreviewInputMimeTypes.APPLICATION_PDF) { + preview.selectFrame(0); + } + if (width) { preview.resize(width, undefined, '>'); } From 19f4eda0e48b0e574f83767cae880371ff500609 Mon Sep 17 00:00:00 2001 From: Max Bischof Date: Mon, 13 Nov 2023 10:52:44 +0100 Subject: [PATCH 2/6] Move PreviewInputMimeTypes to preview generator module --- apps/server/src/infra/preview-generator/interface/index.ts | 1 + .../interface/preview-input-mime-types.enum.ts | 0 .../src/infra/preview-generator/preview-generator.service.ts | 5 ++--- .../modules/files-storage/entity/filerecord.entity.spec.ts | 2 +- .../src/modules/files-storage/entity/filerecord.entity.ts | 2 +- apps/server/src/modules/files-storage/interface/index.ts | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) rename apps/server/src/{modules/files-storage => infra/preview-generator}/interface/preview-input-mime-types.enum.ts (100%) diff --git a/apps/server/src/infra/preview-generator/interface/index.ts b/apps/server/src/infra/preview-generator/interface/index.ts index 45799160cd5..e2b9cf3f7c2 100644 --- a/apps/server/src/infra/preview-generator/interface/index.ts +++ b/apps/server/src/infra/preview-generator/interface/index.ts @@ -1 +1,2 @@ export * from './preview'; +export * from './preview-input-mime-types.enum'; diff --git a/apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts b/apps/server/src/infra/preview-generator/interface/preview-input-mime-types.enum.ts similarity index 100% rename from apps/server/src/modules/files-storage/interface/preview-input-mime-types.enum.ts rename to apps/server/src/infra/preview-generator/interface/preview-input-mime-types.enum.ts diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.ts b/apps/server/src/infra/preview-generator/preview-generator.service.ts index 97e0a18d4ff..795fd0f4ddf 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.ts @@ -1,10 +1,9 @@ -import { Injectable } from '@nestjs/common'; import { GetFile, S3ClientAdapter } from '@infra/s3-client'; +import { Injectable } from '@nestjs/common'; import { Logger } from '@src/core/logger'; -import { PreviewInputMimeTypes } from '@src/modules/files-storage/interface'; import { subClass } from 'gm'; import { PassThrough } from 'stream'; -import { PreviewFileOptions, PreviewOptions, PreviewResponseMessage } from './interface'; +import { PreviewFileOptions, PreviewInputMimeTypes, PreviewOptions, PreviewResponseMessage } from './interface'; import { PreviewActionsLoggable } from './loggable/preview-actions.loggable'; import { PreviewGeneratorBuilder } from './preview-generator.builder'; diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts index f497ff39a6c..e66c471dcc8 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.spec.ts @@ -1,8 +1,8 @@ +import { PreviewInputMimeTypes } from '@infra/preview-generator'; import { ObjectId } from '@mikro-orm/mongodb'; import { BadRequestException } from '@nestjs/common'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { ErrorType } from '../error'; -import { PreviewInputMimeTypes } from '../interface'; import { FileRecord, FileRecordParentType, diff --git a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts index 26f2807924c..73f1c04de34 100644 --- a/apps/server/src/modules/files-storage/entity/filerecord.entity.ts +++ b/apps/server/src/modules/files-storage/entity/filerecord.entity.ts @@ -1,3 +1,4 @@ +import { PreviewInputMimeTypes } from '@infra/preview-generator'; import { Embeddable, Embedded, Entity, Enum, Index, Property } from '@mikro-orm/core'; import { ObjectId } from '@mikro-orm/mongodb'; import { BadRequestException } from '@nestjs/common'; @@ -5,7 +6,6 @@ import { BaseEntityWithTimestamps, EntityId } from '@shared/domain'; import path from 'path'; import { v4 as uuid } from 'uuid'; import { ErrorType } from '../error'; -import { PreviewInputMimeTypes } from '../interface/preview-input-mime-types.enum'; export enum ScanStatus { PENDING = 'pending', diff --git a/apps/server/src/modules/files-storage/interface/index.ts b/apps/server/src/modules/files-storage/interface/index.ts index 4938ae20233..6ec2a5720f9 100644 --- a/apps/server/src/modules/files-storage/interface/index.ts +++ b/apps/server/src/modules/files-storage/interface/index.ts @@ -1,4 +1,3 @@ export * from './interfaces'; -export * from './preview-input-mime-types.enum'; export * from './preview-output-mime-types.enum'; export * from './preview-width.enum'; From b51dc037256fea6695e1afe9e851ec55ea66e750 Mon Sep 17 00:00:00 2001 From: Max Bischof Date: Mon, 13 Nov 2023 13:06:15 +0100 Subject: [PATCH 3/6] Check if preview can be generated for mime type --- .../preview-generator.service.spec.ts | 163 ++++++++++++------ .../preview-generator.service.ts | 15 +- 2 files changed, 126 insertions(+), 52 deletions(-) diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts index 016c261b122..6c5a3eb036d 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts @@ -1,6 +1,7 @@ import { DeepMocked, createMock } from '@golevelup/ts-jest'; -import { Test, TestingModule } from '@nestjs/testing'; import { GetFile, S3ClientAdapter } from '@infra/s3-client'; +import { UnprocessableEntityException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; import { Logger } from '@src/core/logger'; import { Readable } from 'node:stream'; import { PreviewGeneratorService } from './preview-generator.service'; @@ -16,13 +17,13 @@ jest.mock('gm', () => { }; }); -const createFile = (contentRange?: string): GetFile => { +const createFile = (contentRange?: string, contentType?: string): GetFile => { const text = 'testText'; const readable = Readable.from(text); const fileResponse = { data: readable, - contentType: 'image/jpeg', + contentType, contentLength: text.length, contentRange, etag: 'testTag', @@ -68,76 +69,136 @@ describe('PreviewGeneratorService', () => { }); describe('generatePreview', () => { - const setup = (width = 500) => { - const params = { - originFilePath: 'file/test.jpeg', - previewFilePath: 'preview/text.webp', - previewOptions: { - format: 'webp', - width, - }, - }; - const originFile = createFile(); - s3ClientAdapter.get.mockResolvedValueOnce(originFile); + describe('WHEN download of original and preview file is successful', () => { + describe('WHEN preview is possible', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, 'image/jpeg'); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); - const data = Readable.from('text'); - streamMock.mockReturnValueOnce(data); + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); - const expectedFileData = { - data, - mimeType: params.previewOptions.format, - }; + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; - return { params, originFile, expectedFileData }; - }; + return { params, originFile, expectedFileData }; + }; - describe('WHEN download of original and preview file is successful', () => { - it('should call storageClient get method with originFilePath', async () => { - const { params } = setup(); + it('should call storageClient get method with originFilePath', async () => { + const { params } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(s3ClientAdapter.get).toBeCalledWith(params.originFilePath); - }); + expect(s3ClientAdapter.get).toBeCalledWith(params.originFilePath); + }); - it('should call imagemagicks resize method', async () => { - const { params } = setup(); + it('should call imagemagicks resize method', async () => { + const { params } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(resizeMock).toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); - expect(resizeMock).toHaveBeenCalledTimes(1); - }); + expect(resizeMock).toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); + expect(resizeMock).toHaveBeenCalledTimes(1); + }); - it('should call imagemagicks stream method', async () => { - const { params } = setup(); + it('should call imagemagicks stream method', async () => { + const { params } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(streamMock).toHaveBeenCalledWith(params.previewOptions.format); - expect(streamMock).toHaveBeenCalledTimes(1); - }); + expect(streamMock).toHaveBeenCalledWith(params.previewOptions.format); + expect(streamMock).toHaveBeenCalledTimes(1); + }); - it('should call S3ClientAdapters create method', async () => { - const { params, expectedFileData } = setup(); + it('should call S3ClientAdapters create method', async () => { + const { params, expectedFileData } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(s3ClientAdapter.create).toHaveBeenCalledWith(params.previewFilePath, expectedFileData); - expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); - }); + expect(s3ClientAdapter.create).toHaveBeenCalledWith(params.previewFilePath, expectedFileData); + expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + }); - it('should should return values', async () => { - const { params } = setup(); - const expectedValue = { previewFilePath: params.previewFilePath, status: true }; + it('should should return values', async () => { + const { params } = setup(); + const expectedValue = { previewFilePath: params.previewFilePath, status: true }; - const result = await service.generatePreview(params); + const result = await service.generatePreview(params); - expect(result).toEqual(expectedValue); + expect(result).toEqual(expectedValue); + }); + }); + + describe('WHEN preview is not possible', () => { + const setup = (mimeType?: string, width = 500) => { + const params = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, mimeType); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + return { params, originFile }; + }; + + describe('WHEN mimeType is undefined', () => { + it('should throw UnprocessableEntityException', async () => { + const { params } = setup(); + + const error = new UnprocessableEntityException(); + await expect(service.generatePreview(params)).rejects.toThrowError(error); + }); + }); + + describe('WHEN mimeType is text/plain ', () => { + it('should throw UnprocessableEntityException', async () => { + const { params } = setup('text/plain'); + + const error = new UnprocessableEntityException(); + await expect(service.generatePreview(params)).rejects.toThrowError(error); + }); + }); }); }); describe('WHEN previewParams.width not set', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, 'image/jpeg'); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); + + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; + + return { params, originFile, expectedFileData }; + }; + it('should not call imagemagicks resize method', async () => { const { params } = setup(0); diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.ts b/apps/server/src/infra/preview-generator/preview-generator.service.ts index 795fd0f4ddf..d9c2135ceb0 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.ts @@ -1,5 +1,5 @@ import { GetFile, S3ClientAdapter } from '@infra/s3-client'; -import { Injectable } from '@nestjs/common'; +import { Injectable, UnprocessableEntityException } from '@nestjs/common'; import { Logger } from '@src/core/logger'; import { subClass } from 'gm'; import { PassThrough } from 'stream'; @@ -20,6 +20,9 @@ export class PreviewGeneratorService { const { originFilePath, previewFilePath, previewOptions } = params; const original = await this.downloadOriginFile(originFilePath); + + this.checkIfPreviewPossible(original, params); + const preview = this.resizeAndConvert(original, previewOptions); const file = PreviewGeneratorBuilder.buildFile(preview, params.previewOptions); @@ -34,6 +37,16 @@ export class PreviewGeneratorService { }; } + private checkIfPreviewPossible(original: GetFile, params: PreviewFileOptions): void | UnprocessableEntityException { + const isPreviewPossible = + original.contentType && Object.values(PreviewInputMimeTypes).includes(original.contentType); + + if (!isPreviewPossible) { + this.logger.warning(new PreviewActionsLoggable('PreviewGeneratorService.previewNotPossible', params)); + throw new UnprocessableEntityException(); + } + } + private async downloadOriginFile(pathToFile: string): Promise { const file = await this.storageClient.get(pathToFile); From 3cf308eb314598312e07da79ee5664473419593f Mon Sep 17 00:00:00 2001 From: Max Bischof Date: Mon, 13 Nov 2023 13:19:15 +0100 Subject: [PATCH 4/6] Add more tests --- .../preview-generator.service.spec.ts | 129 +++++++++++------- 1 file changed, 83 insertions(+), 46 deletions(-) diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts index 6c5a3eb036d..5b3a397fa1a 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts @@ -8,8 +8,9 @@ import { PreviewGeneratorService } from './preview-generator.service'; const streamMock = jest.fn(); const resizeMock = jest.fn(); +const selectFrameMock = jest.fn(); const imageMagickMock = () => { - return { stream: streamMock, resize: resizeMock, data: Readable.from('text') }; + return { stream: streamMock, resize: resizeMock, selectFrame: selectFrameMock, data: Readable.from('text') }; }; jest.mock('gm', () => { return { @@ -71,71 +72,107 @@ describe('PreviewGeneratorService', () => { describe('generatePreview', () => { describe('WHEN download of original and preview file is successful', () => { describe('WHEN preview is possible', () => { - const setup = (width = 500) => { - const params = { - originFilePath: 'file/test.jpeg', - previewFilePath: 'preview/text.webp', - previewOptions: { - format: 'webp', - width, - }, + describe('WHEN mime type is jpeg', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.jpeg', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, 'image/jpeg'); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); + + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; + + return { params, originFile, expectedFileData }; }; - const originFile = createFile(undefined, 'image/jpeg'); - s3ClientAdapter.get.mockResolvedValueOnce(originFile); - const data = Readable.from('text'); - streamMock.mockReturnValueOnce(data); + it('should call storageClient get method with originFilePath', async () => { + const { params } = setup(); - const expectedFileData = { - data, - mimeType: params.previewOptions.format, - }; + await service.generatePreview(params); - return { params, originFile, expectedFileData }; - }; + expect(s3ClientAdapter.get).toBeCalledWith(params.originFilePath); + }); - it('should call storageClient get method with originFilePath', async () => { - const { params } = setup(); + it('should call imagemagicks resize method', async () => { + const { params } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(s3ClientAdapter.get).toBeCalledWith(params.originFilePath); - }); + expect(resizeMock).toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); + expect(resizeMock).toHaveBeenCalledTimes(1); + }); - it('should call imagemagicks resize method', async () => { - const { params } = setup(); + it('should call imagemagicks stream method', async () => { + const { params } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(resizeMock).toHaveBeenCalledWith(params.previewOptions.width, undefined, '>'); - expect(resizeMock).toHaveBeenCalledTimes(1); - }); + expect(streamMock).toHaveBeenCalledWith(params.previewOptions.format); + expect(streamMock).toHaveBeenCalledTimes(1); + }); - it('should call imagemagicks stream method', async () => { - const { params } = setup(); + it('should call S3ClientAdapters create method', async () => { + const { params, expectedFileData } = setup(); - await service.generatePreview(params); + await service.generatePreview(params); - expect(streamMock).toHaveBeenCalledWith(params.previewOptions.format); - expect(streamMock).toHaveBeenCalledTimes(1); - }); + expect(s3ClientAdapter.create).toHaveBeenCalledWith(params.previewFilePath, expectedFileData); + expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + }); - it('should call S3ClientAdapters create method', async () => { - const { params, expectedFileData } = setup(); + it('should should return values', async () => { + const { params } = setup(); + const expectedValue = { previewFilePath: params.previewFilePath, status: true }; - await service.generatePreview(params); + const result = await service.generatePreview(params); - expect(s3ClientAdapter.create).toHaveBeenCalledWith(params.previewFilePath, expectedFileData); - expect(s3ClientAdapter.create).toHaveBeenCalledTimes(1); + expect(result).toEqual(expectedValue); + }); }); - it('should should return values', async () => { - const { params } = setup(); - const expectedValue = { previewFilePath: params.previewFilePath, status: true }; + describe('WHEN mime type is pdf', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.pdf', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, 'application/pdf'); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); + + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; + + return { params, originFile, expectedFileData }; + }; + + it('should call imagemagicks resize method', async () => { + const { params } = setup(); - const result = await service.generatePreview(params); + await service.generatePreview(params); - expect(result).toEqual(expectedValue); + expect(selectFrameMock).toHaveBeenCalledWith(0); + expect(resizeMock).toHaveBeenCalledTimes(1); + }); }); }); From 4fcdf8c75c0da3d0719ee2e3f5e9ad64b3cb7ebd Mon Sep 17 00:00:00 2001 From: SevenWaysDP Date: Thu, 16 Nov 2023 18:25:48 +0100 Subject: [PATCH 5/6] BC-5159 - add options --- .../src/infra/preview-generator/preview-generator.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.ts b/apps/server/src/infra/preview-generator/preview-generator.service.ts index 83dca461a2f..3810472b91a 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@nestjs/common'; import { GetFile, S3ClientAdapter } from '@infra/s3-client'; +import { Injectable } from '@nestjs/common'; import { Logger } from '@src/core/logger'; import { subClass } from 'gm'; import { PassThrough } from 'stream'; @@ -44,6 +44,7 @@ export class PreviewGeneratorService { const { format, width } = previewParams; const preview = this.imageMagick(original.data); + preview.coalesce(); if (width) { preview.resize(width, undefined, '>'); From 2891bf53f92e47272fe02ec78802f19943170b42 Mon Sep 17 00:00:00 2001 From: SevenWaysDP Date: Fri, 17 Nov 2023 15:10:38 +0100 Subject: [PATCH 6/6] BC-5159 - add tests --- .../preview-generator.service.spec.ts | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts index 5b3a397fa1a..203e4aa9b56 100644 --- a/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts +++ b/apps/server/src/infra/preview-generator/preview-generator.service.spec.ts @@ -8,9 +8,16 @@ import { PreviewGeneratorService } from './preview-generator.service'; const streamMock = jest.fn(); const resizeMock = jest.fn(); +const coalesceMock = jest.fn(); const selectFrameMock = jest.fn(); const imageMagickMock = () => { - return { stream: streamMock, resize: resizeMock, selectFrame: selectFrameMock, data: Readable.from('text') }; + return { + stream: streamMock, + resize: resizeMock, + selectFrame: selectFrameMock, + coalesce: coalesceMock, + data: Readable.from('text'), + }; }; jest.mock('gm', () => { return { @@ -165,7 +172,7 @@ describe('PreviewGeneratorService', () => { return { params, originFile, expectedFileData }; }; - it('should call imagemagicks resize method', async () => { + it('should call imagemagicks selectFrameMock method', async () => { const { params } = setup(); await service.generatePreview(params); @@ -174,6 +181,40 @@ describe('PreviewGeneratorService', () => { expect(resizeMock).toHaveBeenCalledTimes(1); }); }); + + describe('WHEN mime type is gif', () => { + const setup = (width = 500) => { + const params = { + originFilePath: 'file/test.gif', + previewFilePath: 'preview/text.webp', + previewOptions: { + format: 'webp', + width, + }, + }; + const originFile = createFile(undefined, 'image/gif'); + s3ClientAdapter.get.mockResolvedValueOnce(originFile); + + const data = Readable.from('text'); + streamMock.mockReturnValueOnce(data); + + const expectedFileData = { + data, + mimeType: params.previewOptions.format, + }; + + return { params, originFile, expectedFileData }; + }; + + it('should call imagemagicks coalesce method', async () => { + const { params } = setup(); + + await service.generatePreview(params); + + expect(coalesceMock).toHaveBeenCalledTimes(1); + expect(resizeMock).toHaveBeenCalledTimes(1); + }); + }); }); describe('WHEN preview is not possible', () => {