/** * Tests for GetUserRatingsSummaryQuery */ import { GetUserRatingsSummaryQuery, GetUserRatingsSummaryQueryHandler } from './GetUserRatingsSummaryQuery'; import { UserRating } from '../../domain/value-objects/UserRating'; import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile'; import { GameKey } from '../../domain/value-objects/GameKey'; import { ExternalRating } from '../../domain/value-objects/ExternalRating'; import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance'; import { RatingEvent } from '../../domain/entities/RatingEvent'; import { RatingEventId } from '../../domain/value-objects/RatingEventId'; import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey'; import { RatingDelta } from '../../domain/value-objects/RatingDelta'; describe('GetUserRatingsSummaryQuery', () => { let mockUserRatingRepo: any; let mockExternalRatingRepo: any; let mockRatingEventRepo: any; let handler: GetUserRatingsSummaryQueryHandler; beforeEach(() => { mockUserRatingRepo = { findByUserId: jest.fn(), }; mockExternalRatingRepo = { findByUserId: jest.fn(), }; mockRatingEventRepo = { getAllByUserId: jest.fn(), }; handler = new GetUserRatingsSummaryQueryHandler( mockUserRatingRepo, mockExternalRatingRepo, mockRatingEventRepo ); }); describe('execute', () => { it('should return summary with platform and external ratings', async () => { const userId = 'user-123'; // Mock user rating const userRating = UserRating.create(userId); mockUserRatingRepo.findByUserId.mockResolvedValue(userRating); // Mock external ratings const gameKey = GameKey.create('iracing'); const profile = ExternalGameRatingProfile.create({ userId: { toString: () => userId } as any, gameKey, ratings: new Map([ ['iRating', ExternalRating.create(gameKey, 'iRating', 2200)], ['safetyRating', ExternalRating.create(gameKey, 'safetyRating', 4.5)], ]), provenance: ExternalRatingProvenance.create('iRacing API', new Date()), }); mockExternalRatingRepo.findByUserId.mockResolvedValue([profile]); // Mock rating events const event = RatingEvent.create({ id: RatingEventId.create(), userId, dimension: RatingDimensionKey.create('driver'), delta: RatingDelta.create(5), occurredAt: new Date('2024-01-01'), createdAt: new Date('2024-01-01'), source: { type: 'race', id: 'race-123' }, reason: { code: 'RACE_FINISH', summary: 'Good race', details: {} }, visibility: { public: true, redactedFields: [] }, version: 1, }); mockRatingEventRepo.getAllByUserId.mockResolvedValue([event]); const query: GetUserRatingsSummaryQuery = { userId }; const result = await handler.execute(query); expect(result.userId).toBe(userId); expect(result.platform.driving.value).toBe(50); // Default expect(result.platform.overallReputation).toBe(50); expect(result.external.iracing.iRating).toBe(2200); expect(result.external.iracing.safetyRating).toBe(4.5); expect(result.lastRatingEventAt).toBe('2024-01-01T00:00:00.000Z'); }); it('should handle missing user rating gracefully', async () => { const userId = 'user-123'; mockUserRatingRepo.findByUserId.mockResolvedValue(null); mockExternalRatingRepo.findByUserId.mockResolvedValue([]); mockRatingEventRepo.getAllByUserId.mockResolvedValue([]); const query: GetUserRatingsSummaryQuery = { userId }; const result = await handler.execute(query); expect(result.userId).toBe(userId); expect(result.platform.driving.value).toBe(0); expect(result.platform.overallReputation).toBe(0); expect(result.external).toEqual({}); expect(result.lastRatingEventAt).toBeUndefined(); }); it('should handle multiple external game profiles', async () => { const userId = 'user-123'; mockUserRatingRepo.findByUserId.mockResolvedValue(UserRating.create(userId)); // Multiple game profiles const iracingProfile = ExternalGameRatingProfile.create({ userId: { toString: () => userId } as any, gameKey: GameKey.create('iracing'), ratings: new Map([ ['iRating', ExternalRating.create(GameKey.create('iracing'), 'iRating', 2200)], ]), provenance: ExternalRatingProvenance.create('iRacing API', new Date()), }); const assettoProfile = ExternalGameRatingProfile.create({ userId: { toString: () => userId } as any, gameKey: GameKey.create('assetto'), ratings: new Map([ ['rating', ExternalRating.create(GameKey.create('assetto'), 'rating', 85)], ]), provenance: ExternalRatingProvenance.create('Assetto API', new Date()), }); mockExternalRatingRepo.findByUserId.mockResolvedValue([iracingProfile, assettoProfile]); mockRatingEventRepo.getAllByUserId.mockResolvedValue([]); const query: GetUserRatingsSummaryQuery = { userId }; const result = await handler.execute(query); expect(result.external.iracing.iRating).toBe(2200); expect(result.external.assetto.rating).toBe(85); }); it('should handle empty external ratings', async () => { const userId = 'user-123'; mockUserRatingRepo.findByUserId.mockResolvedValue(UserRating.create(userId)); mockExternalRatingRepo.findByUserId.mockResolvedValue([]); mockRatingEventRepo.getAllByUserId.mockResolvedValue([]); const query: GetUserRatingsSummaryQuery = { userId }; const result = await handler.execute(query); expect(result.external).toEqual({}); }); it('should use current date for timestamps when no user rating exists', async () => { const userId = 'user-123'; mockUserRatingRepo.findByUserId.mockResolvedValue(null); mockExternalRatingRepo.findByUserId.mockResolvedValue([]); mockRatingEventRepo.getAllByUserId.mockResolvedValue([]); const beforeQuery = new Date(); const query: GetUserRatingsSummaryQuery = { userId }; const result = await handler.execute(query); const afterQuery = new Date(); // Should have valid ISO date strings expect(new Date(result.createdAt).getTime()).toBeGreaterThanOrEqual(beforeQuery.getTime()); expect(new Date(result.createdAt).getTime()).toBeLessThanOrEqual(afterQuery.getTime()); expect(new Date(result.updatedAt).getTime()).toBeGreaterThanOrEqual(beforeQuery.getTime()); expect(new Date(result.updatedAt).getTime()).toBeLessThanOrEqual(afterQuery.getTime()); }); }); });