All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 15m0s
185 lines
6.7 KiB
TypeScript
185 lines
6.7 KiB
TypeScript
// src/services/flyerFileHandler.server.test.ts
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { Job } from 'bullmq';
|
|
import type { Dirent } from 'node:fs';
|
|
import sharp from 'sharp';
|
|
import { FlyerFileHandler, ICommandExecutor, IFileSystem } from './flyerFileHandler.server';
|
|
import { ImageConversionError, PdfConversionError, UnsupportedFileTypeError } from './processingErrors';
|
|
import { logger } from './logger.server';
|
|
import type { FlyerJobData } from '../types/job-data';
|
|
|
|
// Mock dependencies
|
|
vi.mock('sharp', () => {
|
|
const mockSharpInstance = {
|
|
jpeg: vi.fn().mockReturnThis(),
|
|
png: vi.fn().mockReturnThis(),
|
|
toFile: vi.fn().mockResolvedValue({}),
|
|
};
|
|
return {
|
|
__esModule: true,
|
|
default: vi.fn(() => mockSharpInstance),
|
|
};
|
|
});
|
|
|
|
vi.mock('./logger.server', () => ({
|
|
logger: {
|
|
info: vi.fn(),
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
debug: vi.fn(),
|
|
child: vi.fn().mockReturnThis(),
|
|
},
|
|
}));
|
|
|
|
const createMockJob = (data: Partial<FlyerJobData>): Job<FlyerJobData> => {
|
|
return {
|
|
id: 'job-1',
|
|
data: {
|
|
filePath: '/tmp/flyer.jpg',
|
|
originalFileName: 'flyer.jpg',
|
|
checksum: 'checksum-123',
|
|
...data,
|
|
},
|
|
updateProgress: vi.fn(),
|
|
} as unknown as Job<FlyerJobData>;
|
|
};
|
|
|
|
describe('FlyerFileHandler', () => {
|
|
let service: FlyerFileHandler;
|
|
let mockFs: IFileSystem;
|
|
let mockExec: ICommandExecutor;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mockFs = {
|
|
readdir: vi.fn().mockResolvedValue([]),
|
|
unlink: vi.fn(),
|
|
};
|
|
mockExec = vi.fn().mockResolvedValue({ stdout: 'success', stderr: '' });
|
|
|
|
service = new FlyerFileHandler(mockFs, mockExec);
|
|
});
|
|
|
|
it('should convert a PDF and return image paths', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.pdf' });
|
|
vi.mocked(mockFs.readdir).mockResolvedValue([
|
|
{ name: 'flyer-1.jpg' },
|
|
{ name: 'flyer-2.jpg' },
|
|
] as Dirent[]);
|
|
|
|
const { imagePaths, createdImagePaths } = await service.prepareImageInputs(
|
|
'/tmp/flyer.pdf',
|
|
job,
|
|
logger,
|
|
);
|
|
|
|
expect(mockExec).toHaveBeenCalledWith('pdftocairo -jpeg -r 150 "/tmp/flyer.pdf" "/tmp/flyer"');
|
|
expect(imagePaths).toHaveLength(2);
|
|
expect(imagePaths[0].path).toContain('flyer-1.jpg');
|
|
expect(createdImagePaths).toHaveLength(2);
|
|
});
|
|
|
|
it('should throw PdfConversionError if PDF conversion yields no images', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.pdf' });
|
|
vi.mocked(mockFs.readdir).mockResolvedValue([]); // No images found
|
|
|
|
await expect(service.prepareImageInputs('/tmp/flyer.pdf', job, logger)).rejects.toThrow(
|
|
PdfConversionError,
|
|
);
|
|
});
|
|
|
|
it('should convert convertible image types to PNG', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.gif' });
|
|
const mockSharpInstance = sharp('/tmp/flyer.gif');
|
|
vi.mocked(mockSharpInstance.toFile).mockResolvedValue({} as any);
|
|
|
|
const { imagePaths, createdImagePaths } = await service.prepareImageInputs(
|
|
'/tmp/flyer.gif',
|
|
job,
|
|
logger,
|
|
);
|
|
|
|
expect(sharp).toHaveBeenCalledWith('/tmp/flyer.gif');
|
|
expect(mockSharpInstance.png).toHaveBeenCalled();
|
|
expect(mockSharpInstance.toFile).toHaveBeenCalledWith('/tmp/flyer-converted.png');
|
|
expect(imagePaths).toEqual([{ path: '/tmp/flyer-converted.png', mimetype: 'image/png' }]);
|
|
expect(createdImagePaths).toEqual(['/tmp/flyer-converted.png']);
|
|
});
|
|
|
|
it('should throw UnsupportedFileTypeError for unsupported types', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/document.txt' });
|
|
await expect(service.prepareImageInputs('/tmp/document.txt', job, logger)).rejects.toThrow(
|
|
UnsupportedFileTypeError,
|
|
);
|
|
});
|
|
|
|
describe('Image Processing', () => {
|
|
it('should process a JPEG to strip EXIF data', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.jpg' });
|
|
const mockSharpInstance = sharp('/tmp/flyer.jpg');
|
|
vi.mocked(mockSharpInstance.toFile).mockResolvedValue({} as any);
|
|
|
|
const { imagePaths, createdImagePaths } = await service.prepareImageInputs(
|
|
'/tmp/flyer.jpg',
|
|
job,
|
|
logger,
|
|
);
|
|
|
|
expect(sharp).toHaveBeenCalledWith('/tmp/flyer.jpg');
|
|
expect(mockSharpInstance.jpeg).toHaveBeenCalledWith({ quality: 90 });
|
|
expect(mockSharpInstance.toFile).toHaveBeenCalledWith('/tmp/flyer-processed.jpeg');
|
|
expect(imagePaths).toEqual([{ path: '/tmp/flyer-processed.jpeg', mimetype: 'image/jpeg' }]);
|
|
expect(createdImagePaths).toEqual(['/tmp/flyer-processed.jpeg']);
|
|
});
|
|
|
|
it('should process a PNG to strip metadata', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.png' });
|
|
const mockSharpInstance = sharp('/tmp/flyer.png');
|
|
vi.mocked(mockSharpInstance.toFile).mockResolvedValue({} as any);
|
|
|
|
const { imagePaths, createdImagePaths } = await service.prepareImageInputs(
|
|
'/tmp/flyer.png',
|
|
job,
|
|
logger,
|
|
);
|
|
|
|
expect(sharp).toHaveBeenCalledWith('/tmp/flyer.png');
|
|
expect(mockSharpInstance.png).toHaveBeenCalledWith({ quality: 90 });
|
|
expect(mockSharpInstance.toFile).toHaveBeenCalledWith('/tmp/flyer-processed.png');
|
|
expect(imagePaths).toEqual([{ path: '/tmp/flyer-processed.png', mimetype: 'image/png' }]);
|
|
expect(createdImagePaths).toEqual(['/tmp/flyer-processed.png']);
|
|
});
|
|
|
|
it('should handle other supported image types (e.g. webp) directly without processing', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.webp' });
|
|
const { imagePaths, createdImagePaths } = await service.prepareImageInputs(
|
|
'/tmp/flyer.webp',
|
|
job,
|
|
logger,
|
|
);
|
|
|
|
expect(imagePaths).toEqual([{ path: '/tmp/flyer.webp', mimetype: 'image/webp' }]);
|
|
expect(createdImagePaths).toEqual([]);
|
|
expect(sharp).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw ImageConversionError if sharp fails during JPEG processing', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.jpg' });
|
|
const sharpError = new Error('Sharp failed');
|
|
const mockSharpInstance = sharp('/tmp/flyer.jpg');
|
|
vi.mocked(mockSharpInstance.toFile).mockRejectedValue(sharpError);
|
|
|
|
await expect(service.prepareImageInputs('/tmp/flyer.jpg', job, logger)).rejects.toThrow(ImageConversionError);
|
|
});
|
|
|
|
it('should throw ImageConversionError if sharp fails during PNG processing', async () => {
|
|
const job = createMockJob({ filePath: '/tmp/flyer.png' });
|
|
const sharpError = new Error('Sharp failed');
|
|
const mockSharpInstance = sharp('/tmp/flyer.png');
|
|
vi.mocked(mockSharpInstance.toFile).mockRejectedValue(sharpError);
|
|
|
|
await expect(service.prepareImageInputs('/tmp/flyer.png', job, logger)).rejects.toThrow(ImageConversionError);
|
|
});
|
|
});
|
|
}); |