import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { IFileRepository } from '../../../domain/repositories/file.repository.interface.js'; import type { IFileStorage } from '../../../domain/ports/file-storage.port.js'; import type { IFileTypeDetector } from '../../../domain/ports/file-type-detector.port.js'; import { StoredFile } from '../../../domain/entities/stored-file.entity.js'; import { UploadFileUseCase } from './upload-file.use-case.js'; import { UploadFileCommand } from './upload-file.command.js'; import { mockFileRepository, mockFileStorage } from '../test-helpers.js'; vi.mock('node:fs', () => ({ createReadStream: vi.fn(() => 'stream') })); describe('UploadFileUseCase', () => { let fileRepository: IFileRepository; let fileStorage: IFileStorage; let detector: IFileTypeDetector; let useCase: UploadFileUseCase; beforeEach(() => { fileRepository = mockFileRepository(); fileStorage = mockFileStorage(); detector = { detect: vi.fn() }; useCase = new UploadFileUseCase(fileRepository, fileStorage, detector); vi.mocked(fileRepository.create).mockImplementation(async (file: StoredFile) => StoredFile.reconstitute({ id: 'file-1', storageKey: file.storageKey, fileName: file.fileName, mimeType: file.mimeType, size: file.size, uploaderId: file.uploaderId, createdAt: file.createdAt, }), ); }); it('stores the file under a generated key with the sniffed MIME type', async () => { vi.mocked(detector.detect).mockResolvedValue('image/png'); const dto = await useCase.execute(new UploadFileCommand('user-1', '/tmp/upload', 'photo.png', 10)); const [key, , meta] = vi.mocked(fileStorage.put).mock.calls[0]!; expect(key).toMatch(/^files\/[0-9a-f-]{36}$/); expect(meta).toEqual({ contentType: 'image/png', size: 10 }); expect(dto).toEqual({ id: 'file-1', fileName: 'photo.png', mimeType: 'image/png', size: 10, uploaderId: 'user-1', createdAt: expect.any(Date), }); expect(dto).not.toHaveProperty('storageKey'); }); it('falls back to octet-stream for unrecognized content, ignoring the name', async () => { vi.mocked(detector.detect).mockResolvedValue(null); const dto = await useCase.execute(new UploadFileCommand('user-1', '/tmp/upload', 'evil.html', 10)); expect(dto.mimeType).toBe('application/octet-stream'); }); it('removes the stored object when saving the row fails', async () => { vi.mocked(detector.detect).mockResolvedValue('image/png'); vi.mocked(fileRepository.create).mockRejectedValue(new Error('db down')); await expect(useCase.execute(new UploadFileCommand('user-1', '/tmp/upload', 'photo.png', 10))).rejects.toThrow( 'db down', ); const [key] = vi.mocked(fileStorage.put).mock.calls[0]!; expect(fileStorage.delete).toHaveBeenCalledWith(key); }); });