Files
gridpilot.gg/apps/website/lib/services/media/AvatarService.test.ts
2025-12-17 22:17:02 +01:00

259 lines
8.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AvatarService } from './AvatarService';
import type { MediaApiClient } from '../../api/media/MediaApiClient';
import type { AvatarPresenter } from '../../presenters/AvatarPresenter';
import type {
RequestAvatarGenerationInputDto,
RequestAvatarGenerationOutputDto,
GetAvatarOutputDto,
UpdateAvatarInputDto,
UpdateAvatarOutputDto
} from '../../dtos';
import type {
RequestAvatarGenerationViewModel,
AvatarViewModel,
UpdateAvatarViewModel
} from '../../view-models';
describe('AvatarService', () => {
let service: AvatarService;
let mockApiClient: MediaApiClient;
let mockPresenter: AvatarPresenter;
beforeEach(() => {
mockApiClient = {
requestAvatarGeneration: vi.fn(),
getAvatar: vi.fn(),
updateAvatar: vi.fn(),
} as unknown as MediaApiClient;
mockPresenter = {
presentRequestGeneration: vi.fn(),
presentAvatar: vi.fn(),
presentUpdate: vi.fn(),
} as unknown as AvatarPresenter;
service = new AvatarService(mockApiClient, mockPresenter);
});
describe('constructor', () => {
it('should create instance with injected dependencies', () => {
expect(service).toBeInstanceOf(AvatarService);
});
});
describe('requestAvatarGeneration', () => {
it('should request avatar generation and transform via presenter', async () => {
// Arrange
const input: RequestAvatarGenerationInputDto = {
driverId: 'driver-123',
style: 'realistic',
};
const mockDto: RequestAvatarGenerationOutputDto = {
success: true,
avatarUrl: 'https://example.com/avatar/generated.jpg',
};
const mockViewModel: RequestAvatarGenerationViewModel = {
success: true,
avatarUrl: 'https://example.com/avatar/generated.jpg',
};
vi.mocked(mockApiClient.requestAvatarGeneration).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentRequestGeneration).mockReturnValue(mockViewModel);
// Act
const result = await service.requestAvatarGeneration(input);
// Assert
expect(mockApiClient.requestAvatarGeneration).toHaveBeenCalledWith(input);
expect(mockPresenter.presentRequestGeneration).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
});
it('should handle generation failure', async () => {
// Arrange
const input: RequestAvatarGenerationInputDto = {
driverId: 'driver-123',
};
const mockDto: RequestAvatarGenerationOutputDto = {
success: false,
error: 'Generation failed',
};
const mockViewModel: RequestAvatarGenerationViewModel = {
success: false,
error: 'Generation failed',
};
vi.mocked(mockApiClient.requestAvatarGeneration).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentRequestGeneration).mockReturnValue(mockViewModel);
// Act
const result = await service.requestAvatarGeneration(input);
// Assert
expect(mockApiClient.requestAvatarGeneration).toHaveBeenCalledWith(input);
expect(mockPresenter.presentRequestGeneration).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
expect(result.success).toBe(false);
});
it('should propagate errors from API client', async () => {
// Arrange
const input: RequestAvatarGenerationInputDto = {
driverId: 'driver-123',
};
const error = new Error('Network error');
vi.mocked(mockApiClient.requestAvatarGeneration).mockRejectedValue(error);
// Act & Assert
await expect(service.requestAvatarGeneration(input)).rejects.toThrow('Network error');
expect(mockApiClient.requestAvatarGeneration).toHaveBeenCalledWith(input);
expect(mockPresenter.presentRequestGeneration).not.toHaveBeenCalled();
});
});
describe('getAvatar', () => {
it('should fetch avatar and transform via presenter', async () => {
// Arrange
const driverId = 'driver-123';
const mockDto: GetAvatarOutputDto = {
driverId: 'driver-123',
avatarUrl: 'https://example.com/avatar.jpg',
hasAvatar: true,
};
const mockViewModel: AvatarViewModel = {
driverId: 'driver-123',
avatarUrl: 'https://example.com/avatar.jpg',
hasAvatar: true,
};
vi.mocked(mockApiClient.getAvatar).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentAvatar).mockReturnValue(mockViewModel);
// Act
const result = await service.getAvatar(driverId);
// Assert
expect(mockApiClient.getAvatar).toHaveBeenCalledWith(driverId);
expect(mockPresenter.presentAvatar).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
});
it('should handle driver without avatar', async () => {
// Arrange
const driverId = 'driver-123';
const mockDto: GetAvatarOutputDto = {
driverId: 'driver-123',
hasAvatar: false,
};
const mockViewModel: AvatarViewModel = {
driverId: 'driver-123',
hasAvatar: false,
};
vi.mocked(mockApiClient.getAvatar).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentAvatar).mockReturnValue(mockViewModel);
// Act
const result = await service.getAvatar(driverId);
// Assert
expect(mockApiClient.getAvatar).toHaveBeenCalledWith(driverId);
expect(mockPresenter.presentAvatar).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
expect(result.hasAvatar).toBe(false);
});
it('should propagate errors from API client', async () => {
// Arrange
const driverId = 'driver-123';
const error = new Error('Avatar not found');
vi.mocked(mockApiClient.getAvatar).mockRejectedValue(error);
// Act & Assert
await expect(service.getAvatar(driverId)).rejects.toThrow('Avatar not found');
expect(mockApiClient.getAvatar).toHaveBeenCalledWith(driverId);
expect(mockPresenter.presentAvatar).not.toHaveBeenCalled();
});
});
describe('updateAvatar', () => {
it('should update avatar and transform via presenter', async () => {
// Arrange
const input: UpdateAvatarInputDto = {
driverId: 'driver-123',
avatarUrl: 'https://example.com/new-avatar.jpg',
};
const mockDto: UpdateAvatarOutputDto = {
success: true,
};
const mockViewModel: UpdateAvatarViewModel = {
success: true,
};
vi.mocked(mockApiClient.updateAvatar).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentUpdate).mockReturnValue(mockViewModel);
// Act
const result = await service.updateAvatar(input);
// Assert
expect(mockApiClient.updateAvatar).toHaveBeenCalledWith(input);
expect(mockPresenter.presentUpdate).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
});
it('should handle update failure', async () => {
// Arrange
const input: UpdateAvatarInputDto = {
driverId: 'driver-123',
avatarUrl: 'https://example.com/new-avatar.jpg',
};
const mockDto: UpdateAvatarOutputDto = {
success: false,
error: 'Update failed',
};
const mockViewModel: UpdateAvatarViewModel = {
success: false,
error: 'Update failed',
};
vi.mocked(mockApiClient.updateAvatar).mockResolvedValue(mockDto);
vi.mocked(mockPresenter.presentUpdate).mockReturnValue(mockViewModel);
// Act
const result = await service.updateAvatar(input);
// Assert
expect(mockApiClient.updateAvatar).toHaveBeenCalledWith(input);
expect(mockPresenter.presentUpdate).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
expect(result.success).toBe(false);
});
it('should propagate errors from API client', async () => {
// Arrange
const input: UpdateAvatarInputDto = {
driverId: 'driver-123',
avatarUrl: 'https://example.com/new-avatar.jpg',
};
const error = new Error('Update failed');
vi.mocked(mockApiClient.updateAvatar).mockRejectedValue(error);
// Act & Assert
await expect(service.updateAvatar(input)).rejects.toThrow('Update failed');
expect(mockApiClient.updateAvatar).toHaveBeenCalledWith(input);
expect(mockPresenter.presentUpdate).not.toHaveBeenCalled();
});
});
});