Files
gridpilot.gg/core/media/application/use-cases/RequestAvatarGenerationUseCase.test.ts
2026-01-08 15:34:51 +01:00

165 lines
5.4 KiB
TypeScript

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<RequestAvatarGenerationResult, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
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<RequestAvatarGenerationResult, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
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<RequestAvatarGenerationResult, ApplicationErrorCode<RequestAvatarGenerationErrorCode, { message: string }>> =
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();
});
});