rating
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user