import { IdentityDomainValidationError } from '../errors/IdentityDomainError'; import { ExternalRating } from '../value-objects/ExternalRating'; import { ExternalRatingProvenance } from '../value-objects/ExternalRatingProvenance'; import { GameKey } from '../value-objects/GameKey'; import { UserId } from '../value-objects/UserId'; import { ExternalGameRatingProfile } from './ExternalGameRatingProfile'; describe('ExternalGameRatingProfile', () => { let userId: UserId; let gameKey: GameKey; let ratings: Map; let provenance: ExternalRatingProvenance; beforeEach(() => { userId = UserId.fromString('user-123'); gameKey = GameKey.create('iracing'); ratings = new Map([ ['safety', ExternalRating.create(gameKey, 'safety', 85.5)], ['skill', ExternalRating.create(gameKey, 'skill', 92.0)], ]); provenance = ExternalRatingProvenance.create({ source: 'iracing', lastSyncedAt: new Date('2024-01-01'), verified: true, }); }); describe('create', () => { it('should create a valid profile', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); expect(profile.userId).toBe(userId); expect(profile.gameKey).toBe(gameKey); expect(profile.ratings.size).toBe(2); expect(profile.provenance).toBe(provenance); }); it('should allow empty ratings map', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings: new Map(), provenance, }); expect(profile.hasRatings()).toBe(false); expect(profile.ratings.size).toBe(0); }); it('should throw error if rating gameKey does not match profile gameKey', () => { const wrongGameKey = GameKey.create('assetto'); const wrongRatings = new Map([ ['safety', ExternalRating.create(wrongGameKey, 'safety', 85.5)], ]); expect(() => ExternalGameRatingProfile.create({ userId, gameKey, ratings: wrongRatings, provenance, }) ).toThrow(IdentityDomainValidationError); }); }); describe('restore', () => { it('should restore profile from stored data', () => { const profile = ExternalGameRatingProfile.restore({ userId: 'user-123', gameKey: 'iracing', ratings: [ { type: 'safety', gameKey: 'iracing', value: 85.5 }, { type: 'skill', gameKey: 'iracing', value: 92.0 }, ], provenance: { source: 'iracing', lastSyncedAt: new Date('2024-01-01'), verified: true, }, }); expect(profile.userId.toString()).toBe('user-123'); expect(profile.gameKey.toString()).toBe('iracing'); expect(profile.ratings.size).toBe(2); expect(profile.provenance.source).toBe('iracing'); expect(profile.provenance.verified).toBe(true); }); it('should handle missing verified flag in provenance', () => { const profile = ExternalGameRatingProfile.restore({ userId: 'user-123', gameKey: 'iracing', ratings: [ { type: 'safety', gameKey: 'iracing', value: 85.5 }, ], provenance: { source: 'iracing', lastSyncedAt: new Date('2024-01-01'), }, }); expect(profile.provenance.verified).toBe(false); }); }); describe('getRatingByType', () => { it('should return rating for existing type', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const rating = profile.getRatingByType('safety'); expect(rating).toBeDefined(); expect(rating?.type).toBe('safety'); expect(rating?.value).toBe(85.5); }); it('should return undefined for non-existing type', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const rating = profile.getRatingByType('nonexistent'); expect(rating).toBeUndefined(); }); }); describe('getRatingTypes', () => { it('should return all rating types', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const types = profile.getRatingTypes(); expect(types).toHaveLength(2); expect(types).toContain('safety'); expect(types).toContain('skill'); }); }); describe('hasRatings', () => { it('should return true when has ratings', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); expect(profile.hasRatings()).toBe(true); }); it('should return false when no ratings', () => { const emptyRatings = new Map(); const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings: emptyRatings, provenance, }); expect(profile.hasRatings()).toBe(false); }); }); describe('updateRatings', () => { it('should update ratings and provenance', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const newRatings = new Map([ ['safety', ExternalRating.create(gameKey, 'safety', 90.0)], ['newType', ExternalRating.create(gameKey, 'newType', 88.0)], ]); const newProvenance = ExternalRatingProvenance.create({ source: 'iracing', lastSyncedAt: new Date('2024-01-02'), verified: false, }); profile.updateRatings(newRatings, newProvenance); expect(profile.ratings.size).toBe(2); expect(profile.getRatingByType('safety')?.value).toBe(90.0); expect(profile.getRatingByType('newType')?.value).toBe(88.0); expect(profile.provenance.lastSyncedAt).toEqual(new Date('2024-01-02')); }); it('should throw error when updating with mismatched gameKey', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const wrongGameKey = GameKey.create('assetto'); const wrongRatings = new Map([ ['safety', ExternalRating.create(wrongGameKey, 'safety', 90.0)], ]); expect(() => profile.updateRatings( wrongRatings, ExternalRatingProvenance.create({ source: 'iracing', lastSyncedAt: new Date(), }) ) ).toThrow(IdentityDomainValidationError); }); }); describe('markVerified', () => { it('should mark provenance as verified', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance: ExternalRatingProvenance.create({ source: 'iracing', lastSyncedAt: new Date(), verified: false, }), }); profile.markVerified(); expect(profile.provenance.verified).toBe(true); }); }); describe('updateLastSyncedAt', () => { it('should update last synced timestamp', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const newDate = new Date('2024-01-03'); profile.updateLastSyncedAt(newDate); expect(profile.provenance.lastSyncedAt).toEqual(newDate); }); }); describe('toSummary', () => { it('should return summary object', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const summary = profile.toSummary(); expect(summary.userId).toBe('user-123'); expect(summary.gameKey).toBe('iracing'); expect(summary.ratingCount).toBe(2); expect(summary.ratingTypes).toEqual(['safety', 'skill']); expect(summary.source).toBe('iracing'); expect(summary.verified).toBe(true); }); }); describe('toJSON', () => { it('should serialize to JSON format', () => { const profile = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const json = profile.toJSON(); expect(json.userId).toBe('user-123'); expect(json.gameKey).toBe('iracing'); expect(json.ratings).toHaveLength(2); expect(json.provenance.source).toBe('iracing'); expect(json.provenance.verified).toBe(true); }); }); describe('equals', () => { it('should return true for identical profiles', () => { const profile1 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const profile2 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); expect(profile1.equals(profile2)).toBe(true); }); it('should return false for different userId', () => { const profile1 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const profile2 = ExternalGameRatingProfile.create({ userId: UserId.fromString('user-456'), gameKey, ratings, provenance, }); expect(profile1.equals(profile2)).toBe(false); }); it('should return false for different gameKey', () => { const profile1 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const differentGameKey = GameKey.create('assetto'); const differentRatings = new Map([ ['safety', ExternalRating.create(differentGameKey, 'safety', 85.5)], ]); const profile2 = ExternalGameRatingProfile.create({ userId, gameKey: differentGameKey, ratings: differentRatings, provenance, }); expect(profile1.equals(profile2)).toBe(false); }); it('should return false for different ratings', () => { const profile1 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const differentRatings = new Map([ ['safety', ExternalRating.create(gameKey, 'safety', 99.0)], ]); const profile2 = ExternalGameRatingProfile.create({ userId, gameKey, ratings: differentRatings, provenance, }); expect(profile1.equals(profile2)).toBe(false); }); it('should return false for different provenance', () => { const profile1 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance, }); const differentProvenance = ExternalRatingProvenance.create({ source: 'different', lastSyncedAt: new Date(), }); const profile2 = ExternalGameRatingProfile.create({ userId, gameKey, ratings, provenance: differentProvenance, }); expect(profile1.equals(profile2)).toBe(false); }); }); });