Files
gridpilot.gg/core/identity/application/use-cases/UpsertExternalGameRatingUseCase.test.ts
2025-12-29 22:27:33 +01:00

285 lines
9.5 KiB
TypeScript

import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase';
import { IExternalGameRatingRepository } from '../../domain/repositories/IExternalGameRatingRepository';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
import { UserId } from '../../domain/value-objects/UserId';
import { GameKey } from '../../domain/value-objects/GameKey';
import { ExternalRating } from '../../domain/value-objects/ExternalRating';
import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
describe('UpsertExternalGameRatingUseCase', () => {
let useCase: UpsertExternalGameRatingUseCase;
let mockRepository: IExternalGameRatingRepository;
beforeEach(() => {
mockRepository = {
findByUserIdAndGameKey: jest.fn(),
findByUserId: jest.fn(),
findByGameKey: jest.fn(),
save: jest.fn(),
saveMany: jest.fn(),
delete: jest.fn(),
exists: jest.fn(),
} as any;
useCase = new UpsertExternalGameRatingUseCase(mockRepository);
});
describe('execute', () => {
it('should create a new profile when it does not exist', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [
{ type: 'safety', value: 85.5 },
{ type: 'skill', value: 92.0 },
],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
verified: true,
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
const result = await useCase.execute(input);
expect(result.success).toBe(true);
expect(result.action).toBe('created');
expect(result.profile.userId).toBe('user-123');
expect(result.profile.gameKey).toBe('iracing');
expect(result.profile.ratingCount).toBe(2);
expect(result.profile.ratingTypes).toEqual(['safety', 'skill']);
expect(result.profile.verified).toBe(true);
expect(mockRepository.findByUserIdAndGameKey).toHaveBeenCalledWith('user-123', 'iracing');
expect(mockRepository.save).toHaveBeenCalledTimes(1);
});
it('should update existing profile', async () => {
const existingProfile = createTestProfile('user-123', 'iracing');
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(existingProfile);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [
{ type: 'safety', value: 90.0 },
{ type: 'newType', value: 88.0 },
],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-02T00:00:00Z',
verified: false,
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(true);
expect(result.action).toBe('updated');
expect(result.profile.ratingCount).toBe(2);
expect(result.profile.ratingTypes).toEqual(['safety', 'newType']);
expect(result.profile.verified).toBe(false);
expect(mockRepository.findByUserIdAndGameKey).toHaveBeenCalledWith('user-123', 'iracing');
expect(mockRepository.save).toHaveBeenCalledTimes(1);
});
it('should handle validation errors - missing userId', async () => {
const input: UpsertExternalGameRatingInput = {
userId: '',
gameKey: 'iracing',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('User ID is required');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle validation errors - missing gameKey', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: '',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Game key is required');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle validation errors - empty ratings', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Ratings are required');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle validation errors - invalid rating value', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [
{ type: 'safety', value: NaN },
],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Rating value for type safety must be a valid number');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle validation errors - missing provenance source', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: '',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Provenance source is required');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle validation errors - invalid date', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: 'iracing',
lastSyncedAt: 'invalid-date',
},
};
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Provenance lastSyncedAt must be a valid date');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should handle repository errors gracefully', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
(mockRepository.findByUserIdAndGameKey as any).mockRejectedValue(new Error('Database connection failed'));
const result = await useCase.execute(input);
expect(result.success).toBe(false);
expect(result.errors).toContain('Database connection failed');
expect(mockRepository.save).not.toHaveBeenCalled();
});
it('should default verified to false when not provided', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [{ type: 'safety', value: 85.5 }],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
// verified not provided
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
const result = await useCase.execute(input);
expect(result.success).toBe(true);
expect(result.profile.verified).toBe(false);
});
it('should trim rating types', async () => {
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
gameKey: 'iracing',
ratings: [
{ type: ' safety ', value: 85.5 },
],
provenance: {
source: 'iracing',
lastSyncedAt: '2024-01-01T00:00:00Z',
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
const result = await useCase.execute(input);
expect(result.success).toBe(true);
expect(result.profile.ratingTypes).toEqual(['safety']);
});
});
function createTestProfile(userId: string, gameKey: string): ExternalGameRatingProfile {
const user = UserId.fromString(userId);
const game = GameKey.create(gameKey);
const ratings = new Map([
['safety', ExternalRating.create(game, 'safety', 85.5)],
]);
const provenance = ExternalRatingProvenance.create({
source: gameKey,
lastSyncedAt: new Date('2024-01-01'),
verified: false,
});
return ExternalGameRatingProfile.create({
userId: user,
gameKey: game,
ratings,
provenance,
});
}
});