import { describe, it, expect, vi, type Mock } from 'vitest'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { RequestAvatarGenerationUseCase, type RequestAvatarGenerationInput, type RequestAvatarGenerationErrorCode, type RequestAvatarGenerationResult, } from './RequestAvatarGenerationUseCase'; import type { IAvatarGenerationRepository } from '../../domain/repositories/IAvatarGenerationRepository'; import type { FaceValidationPort } from '../ports/FaceValidationPort'; import type { AvatarGenerationPort } from '../ports/AvatarGenerationPort'; vi.mock('uuid', () => ({ v4: () => 'request-1', })); describe('RequestAvatarGenerationUseCase', () => { let avatarRepo: { save: Mock }; let faceValidation: { validateFacePhoto: Mock }; let avatarGeneration: { generateAvatars: Mock }; let logger: Logger; let useCase: RequestAvatarGenerationUseCase; beforeEach(() => { avatarRepo = { save: vi.fn(), }; faceValidation = { validateFacePhoto: vi.fn(), }; avatarGeneration = { generateAvatars: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; useCase = new RequestAvatarGenerationUseCase( avatarRepo as unknown as IAvatarGenerationRepository, faceValidation as unknown as FaceValidationPort, avatarGeneration as unknown as AvatarGenerationPort, logger, ); }); it('returns RequestAvatarGenerationResult on success', async () => { faceValidation.validateFacePhoto.mockResolvedValue({ isValid: true, hasFace: true, faceCount: 1, }); avatarGeneration.generateAvatars.mockResolvedValue({ success: true, avatars: [{ url: 'https://example.com/a.png' }, { url: 'https://example.com/b.png' }], }); const input: RequestAvatarGenerationInput = { userId: 'user-1', facePhotoData: 'data:image/png;base64,abc', suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'], style: 'cartoon', }; const result = await useCase.execute(input); expect(result).toBeInstanceOf(Result); expect(result.isOk()).toBe(true); expect(faceValidation.validateFacePhoto).toHaveBeenCalledWith(input.facePhotoData); expect(avatarGeneration.generateAvatars).toHaveBeenCalled(); expect(avatarRepo.save).toHaveBeenCalledTimes(4); const successResult = result.unwrap(); expect(successResult).toEqual({ requestId: 'request-1', status: 'completed', avatarUrls: ['https://example.com/a.png', 'https://example.com/b.png'], }); }); it('returns FACE_VALIDATION_FAILED when face validation fails', async () => { faceValidation.validateFacePhoto.mockResolvedValue({ isValid: false, hasFace: true, faceCount: 1, errorMessage: 'Bad image', }); const input: RequestAvatarGenerationInput = { userId: 'user-1', facePhotoData: 'data:image/png;base64,abc', suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'], }; const result: Result> = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr(); expect(err.code).toBe('FACE_VALIDATION_FAILED'); expect(err.details?.message).toBe('Bad image'); expect(avatarGeneration.generateAvatars).not.toHaveBeenCalled(); expect(avatarRepo.save).toHaveBeenCalledTimes(3); }); it('returns GENERATION_FAILED when avatar generation fails', async () => { faceValidation.validateFacePhoto.mockResolvedValue({ isValid: true, hasFace: true, faceCount: 1, }); avatarGeneration.generateAvatars.mockResolvedValue({ success: false, errorMessage: 'Generation service down', avatars: [], }); const input: RequestAvatarGenerationInput = { userId: 'user-1', facePhotoData: 'data:image/png;base64,abc', suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'], }; const result: Result> = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr(); expect(err.code).toBe('GENERATION_FAILED'); expect(err.details?.message).toBe('Generation service down'); expect(avatarRepo.save).toHaveBeenCalledTimes(4); }); it('returns REPOSITORY_ERROR when repository throws', async () => { avatarRepo.save.mockRejectedValueOnce(new Error('DB error')); const input: RequestAvatarGenerationInput = { userId: 'user-1', facePhotoData: 'data:image/png;base64,abc', suitColor: 'red' as unknown as RequestAvatarGenerationInput['suitColor'], }; const result: Result> = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr(); expect(err.code).toBe('REPOSITORY_ERROR'); expect(err.details?.message).toBe('DB error'); expect((logger.error as unknown as Mock)).toHaveBeenCalled(); }); });