import { describe, it, expect, vi, type Mock } from 'vitest'; import { DeleteMediaUseCase, type DeleteMediaInput, type DeleteMediaResult, type DeleteMediaErrorCode, } from './DeleteMediaUseCase'; import type { IMediaRepository } from '../../domain/repositories/IMediaRepository'; import type { MediaStoragePort } from '../ports/MediaStoragePort'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { Media } from '../../domain/entities/Media'; interface TestOutputPort extends UseCaseOutputPort { present: Mock; result?: DeleteMediaResult; } describe('DeleteMediaUseCase', () => { let mediaRepo: { findById: Mock; delete: Mock; }; let mediaStorage: { deleteMedia: Mock; }; let logger: Logger; let output: TestOutputPort; let useCase: DeleteMediaUseCase; beforeEach(() => { mediaRepo = { findById: vi.fn(), delete: vi.fn(), } as unknown as IMediaRepository as any; mediaStorage = { deleteMedia: vi.fn(), } as unknown as MediaStoragePort as any; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; output = { present: vi.fn((result: DeleteMediaResult) => { output.result = result; }), } as unknown as TestOutputPort; useCase = new DeleteMediaUseCase( mediaRepo as unknown as IMediaRepository, mediaStorage as unknown as MediaStoragePort, output, logger, ); }); it('returns MEDIA_NOT_FOUND when media is not found', async () => { mediaRepo.findById.mockResolvedValue(null); const input: DeleteMediaInput = { mediaId: 'missing' }; const result = await useCase.execute(input); expect(mediaRepo.findById).toHaveBeenCalledWith('missing'); expect(result).toBeInstanceOf(Result); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< DeleteMediaErrorCode, { message: string } >; expect(err.code).toBe('MEDIA_NOT_FOUND'); expect(output.present).not.toHaveBeenCalled(); }); it('deletes media from storage and repository on success', async () => { const media = Media.create({ id: 'media-1', filename: 'file.png', originalName: 'file.png', mimeType: 'image/png', size: 123, url: 'https://example.com/file.png', type: 'image', uploadedBy: 'user-1', }); mediaRepo.findById.mockResolvedValue(media); const input: DeleteMediaInput = { mediaId: 'media-1' }; const result = await useCase.execute(input); expect(result.isOk()).toBe(true); expect(mediaRepo.findById).toHaveBeenCalledWith('media-1'); expect(mediaStorage.deleteMedia).toHaveBeenCalledWith(media.url.value); expect(mediaRepo.delete).toHaveBeenCalledWith('media-1'); expect(output.present).toHaveBeenCalledWith({ mediaId: 'media-1', deleted: true, }); }); it('handles repository errors by returning REPOSITORY_ERROR', async () => { mediaRepo.findById.mockRejectedValue(new Error('DB error')); const input: DeleteMediaInput = { mediaId: 'media-1' }; const result = await useCase.execute(input); expect((logger.error as unknown as Mock)).toHaveBeenCalled(); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode< DeleteMediaErrorCode, { message: string } >; expect(err.code).toBe('REPOSITORY_ERROR'); expect(output.present).not.toHaveBeenCalled(); }); });