diff --git a/core/identity/application/queries/GetUserRatingLedgerQuery.test.ts b/core/identity/application/queries/GetUserRatingLedgerQuery.test.ts index d757f6c1f..df18f3457 100644 --- a/core/identity/application/queries/GetUserRatingLedgerQuery.test.ts +++ b/core/identity/application/queries/GetUserRatingLedgerQuery.test.ts @@ -72,7 +72,7 @@ describe('GetUserRatingLedgerQueryHandler', () => { hasMore: false, }); - const filter: any = { + const filter: unknown = { dimensions: ['trust'], sourceTypes: ['vote'], from: '2026-01-01T00:00:00Z', diff --git a/core/identity/application/use-cases/CloseAdminVoteSessionUseCase.test.ts b/core/identity/application/use-cases/CloseAdminVoteSessionUseCase.test.ts index a83e9e428..7b5509831 100644 --- a/core/identity/application/use-cases/CloseAdminVoteSessionUseCase.test.ts +++ b/core/identity/application/use-cases/CloseAdminVoteSessionUseCase.test.ts @@ -1,13 +1,17 @@ /** * Application Use Case Tests: CloseAdminVoteSessionUseCase - * + * * Tests for closing admin vote sessions and generating rating events */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { CloseAdminVoteSessionUseCase } from './CloseAdminVoteSessionUseCase'; import { RatingEventFactory } from '../../domain/services/RatingEventFactory'; import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator'; +import { AdminVoteSessionRepository } from '../../domain/repositories/AdminVoteSessionRepository'; +import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository'; +import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository'; +import { AdminVoteSession, AdminVoteOutcome } from '../../domain/entities/AdminVoteSession'; // Mock repositories const createMockRepositories = () => ({ @@ -51,14 +55,14 @@ describe('CloseAdminVoteSessionUseCase', () => { beforeEach(() => { mockRepositories = createMockRepositories(); useCase = new CloseAdminVoteSessionUseCase( - mockRepositories.adminVoteSessionRepository, - mockRepositories.ratingEventRepository, - mockRepositories.userRatingRepository + mockRepositories.adminVoteSessionRepository as unknown as AdminVoteSessionRepository, + mockRepositories.ratingEventRepository as unknown as RatingEventRepository, + mockRepositories.userRatingRepository as unknown as UserRatingRepository ); vi.clearAllMocks(); // Default mock for RatingEventFactory.createFromVote to return an empty array // to avoid "events is not iterable" error in tests that don't explicitly mock it - (RatingEventFactory.createFromVote as any).mockReturnValue([]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([]); }); describe('Input validation', () => { @@ -84,7 +88,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should accept valid input', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -144,7 +157,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should find session by ID when provided', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -186,7 +208,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Admin ownership validation', () => { it('should reject when admin does not own the session', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'different-admin', startDate: new Date('2026-01-01'), @@ -227,7 +258,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should accept when admin owns the session', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -269,7 +309,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Session closure validation', () => { it('should reject when session is already closed', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -310,7 +359,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should accept when session is not closed', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -352,7 +410,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Voting window validation', () => { it('should reject when trying to close outside voting window', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -388,7 +455,7 @@ describe('CloseAdminVoteSessionUseCase', () => { constructor() { super('2026-02-02'); } - } as any; + } as unknown as typeof Date; const result = await useCase.execute({ voteSessionId: 'session-123', @@ -404,7 +471,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should accept when trying to close within voting window', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -440,7 +516,7 @@ describe('CloseAdminVoteSessionUseCase', () => { constructor() { super('2026-01-15T12:00:00'); } - } as any; + } as unknown as typeof Date; const result = await useCase.execute({ voteSessionId: 'session-123', @@ -457,7 +533,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Session closure', () => { it('should call close method on session', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -497,7 +582,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should save closed session', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -537,7 +631,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should return outcome in success response', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -585,7 +688,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Rating event creation', () => { it('should create rating events when outcome is positive', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -616,7 +728,7 @@ describe('CloseAdminVoteSessionUseCase', () => { mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession); const mockEvent = { id: 'event-123' }; - (RatingEventFactory.createFromVote as any).mockReturnValue([mockEvent]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]); await useCase.execute({ voteSessionId: 'session-123', @@ -635,7 +747,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should create rating events when outcome is negative', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -666,7 +787,7 @@ describe('CloseAdminVoteSessionUseCase', () => { mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession); const mockEvent = { id: 'event-123' }; - (RatingEventFactory.createFromVote as any).mockReturnValue([mockEvent]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]); await useCase.execute({ voteSessionId: 'session-123', @@ -685,7 +806,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should not create rating events when outcome is tie', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -726,7 +856,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should save created rating events', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -758,7 +897,7 @@ describe('CloseAdminVoteSessionUseCase', () => { const mockEvent1 = { id: 'event-123' }; const mockEvent2 = { id: 'event-124' }; - (RatingEventFactory.createFromVote as any).mockReturnValue([mockEvent1, mockEvent2]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent1, mockEvent2]); await useCase.execute({ voteSessionId: 'session-123', @@ -772,7 +911,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should return eventsCreated count', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -804,7 +952,7 @@ describe('CloseAdminVoteSessionUseCase', () => { const mockEvent1 = { id: 'event-123' }; const mockEvent2 = { id: 'event-124' }; - (RatingEventFactory.createFromVote as any).mockReturnValue([mockEvent1, mockEvent2]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent1, mockEvent2]); const result = await useCase.execute({ voteSessionId: 'session-123', @@ -818,7 +966,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Snapshot recalculation', () => { it('should recalculate snapshot when events are created', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -849,13 +1006,13 @@ describe('CloseAdminVoteSessionUseCase', () => { mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession); const mockEvent = { id: 'event-123' }; - (RatingEventFactory.createFromVote as any).mockReturnValue([mockEvent]); + (RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]); const mockAllEvents = [{ id: 'event-1' }, { id: 'event-2' }]; mockRepositories.ratingEventRepository.getAllByUserId.mockResolvedValue(mockAllEvents); const mockSnapshot = { userId: 'admin-123', overallReputation: 75 }; - (RatingSnapshotCalculator.calculate as any).mockReturnValue(mockSnapshot); + (RatingSnapshotCalculator.calculate as unknown as Mock).mockReturnValue(mockSnapshot); await useCase.execute({ voteSessionId: 'session-123', @@ -869,7 +1026,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should not recalculate snapshot when no events are created (tie)', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -937,7 +1103,16 @@ describe('CloseAdminVoteSessionUseCase', () => { it('should handle save errors gracefully', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), @@ -981,7 +1156,16 @@ describe('CloseAdminVoteSessionUseCase', () => { describe('Return values', () => { it('should return voteSessionId in success response', async () => { const futureDate = new Date('2026-02-01'); - const mockSession: any = { + const mockSession: { + id: string; + adminId: string; + startDate: Date; + endDate: Date; + _closed: boolean; + _outcome?: AdminVoteOutcome; + close: Mock; + closed: boolean; + } = { id: 'session-123', adminId: 'admin-123', startDate: new Date('2026-01-01'), diff --git a/core/identity/application/use-cases/OpenAdminVoteSessionUseCase.test.ts b/core/identity/application/use-cases/OpenAdminVoteSessionUseCase.test.ts index 5104a40f6..b5b519086 100644 --- a/core/identity/application/use-cases/OpenAdminVoteSessionUseCase.test.ts +++ b/core/identity/application/use-cases/OpenAdminVoteSessionUseCase.test.ts @@ -171,7 +171,7 @@ describe('OpenAdminVoteSessionUseCase', () => { describe('Business rules', () => { it('should reject when session ID already exists', async () => { - mockRepository.findById.mockResolvedValue({ id: 'session-1' } as any); + mockRepository.findById.mockResolvedValue({ id: 'session-1' } as unknown as AdminVoteSession); const result = await useCase.execute({ voteSessionId: 'session-1', @@ -193,7 +193,7 @@ describe('OpenAdminVoteSessionUseCase', () => { startDate: new Date('2026-01-05'), endDate: new Date('2026-01-10'), } - ] as any); + ] as unknown as AdminVoteSession[]); const result = await useCase.execute({ voteSessionId: 'session-1', diff --git a/core/identity/domain/entities/Company.test.ts b/core/identity/domain/entities/Company.test.ts index a95e0ddb7..ab0b13881 100644 --- a/core/identity/domain/entities/Company.test.ts +++ b/core/identity/domain/entities/Company.test.ts @@ -216,7 +216,7 @@ describe('Company', () => { id: 'comp-123', name: 'Acme Racing Team', ownerUserId: 'user-123', - contactEmail: null as any, + contactEmail: null, createdAt, }); diff --git a/core/identity/domain/services/PasswordHashingService.test.ts b/core/identity/domain/services/PasswordHashingService.test.ts index 403829a53..c95a83c5b 100644 --- a/core/identity/domain/services/PasswordHashingService.test.ts +++ b/core/identity/domain/services/PasswordHashingService.test.ts @@ -133,7 +133,7 @@ describe('PasswordHashingService', () => { it('should reject verification with null hash', async () => { // bcrypt throws an error when hash is null, which is expected behavior - await expect(service.verify('password', null as any)).rejects.toThrow(); + await expect(service.verify('password', null as unknown as string)).rejects.toThrow(); }); it('should reject verification with empty hash', async () => { diff --git a/core/identity/domain/types/EmailAddress.test.ts b/core/identity/domain/types/EmailAddress.test.ts index 910f3d047..7e5a016e8 100644 --- a/core/identity/domain/types/EmailAddress.test.ts +++ b/core/identity/domain/types/EmailAddress.test.ts @@ -216,17 +216,17 @@ describe('EmailAddress', () => { describe('Edge cases', () => { it('should handle null input gracefully', () => { - const result = validateEmail(null as any); + const result = validateEmail(null as unknown as string); expect(result.success).toBe(false); }); it('should handle undefined input gracefully', () => { - const result = validateEmail(undefined as any); + const result = validateEmail(undefined as unknown as string); expect(result.success).toBe(false); }); it('should handle non-string input gracefully', () => { - const result = validateEmail(123 as any); + const result = validateEmail(123 as unknown as string); expect(result.success).toBe(false); }); }); @@ -305,13 +305,13 @@ describe('EmailAddress', () => { it('should handle null input', () => { // The current implementation throws an error when given null // This is expected behavior - the function expects a string - expect(() => isDisposableEmail(null as any)).toThrow(); + expect(() => isDisposableEmail(null as unknown as string)).toThrow(); }); it('should handle undefined input', () => { // The current implementation throws an error when given undefined // This is expected behavior - the function expects a string - expect(() => isDisposableEmail(undefined as any)).toThrow(); + expect(() => isDisposableEmail(undefined as unknown as string)).toThrow(); }); }); }); diff --git a/core/leaderboards/application/use-cases/GetDriverRankingsUseCase.test.ts b/core/leaderboards/application/use-cases/GetDriverRankingsUseCase.test.ts index f10067d62..c07f06f0b 100644 --- a/core/leaderboards/application/use-cases/GetDriverRankingsUseCase.test.ts +++ b/core/leaderboards/application/use-cases/GetDriverRankingsUseCase.test.ts @@ -1,14 +1,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { GetDriverRankingsUseCase, GetDriverRankingsUseCasePorts } from './GetDriverRankingsUseCase'; import { ValidationError } from '../../../shared/errors/ValidationError'; +import { LeaderboardsRepository, LeaderboardDriverData } from '../ports/LeaderboardsRepository'; +import { LeaderboardsEventPublisher } from '../ports/LeaderboardsEventPublisher'; describe('GetDriverRankingsUseCase', () => { - let mockLeaderboardsRepository: any; - let mockEventPublisher: any; + let mockLeaderboardsRepository: LeaderboardsRepository; + let mockEventPublisher: LeaderboardsEventPublisher; let ports: GetDriverRankingsUseCasePorts; let useCase: GetDriverRankingsUseCase; - const mockDrivers = [ + const mockDrivers: LeaderboardDriverData[] = [ { id: '1', name: 'Alice', rating: 2000, raceCount: 10, teamId: 't1', teamName: 'Team A' }, { id: '2', name: 'Bob', rating: 1500, raceCount: 5, teamId: 't2', teamName: 'Team B' }, { id: '3', name: 'Charlie', rating: 1800, raceCount: 8 }, @@ -17,10 +19,14 @@ describe('GetDriverRankingsUseCase', () => { beforeEach(() => { mockLeaderboardsRepository = { findAllDrivers: vi.fn().mockResolvedValue([...mockDrivers]), + findAllTeams: vi.fn(), + findDriversByTeamId: vi.fn(), }; mockEventPublisher = { publishDriverRankingsAccessed: vi.fn().mockResolvedValue(undefined), publishLeaderboardsError: vi.fn().mockResolvedValue(undefined), + publishGlobalLeaderboardsAccessed: vi.fn(), + publishTeamRankingsAccessed: vi.fn(), }; ports = { leaderboardsRepository: mockLeaderboardsRepository, @@ -92,6 +98,6 @@ describe('GetDriverRankingsUseCase', () => { }); it('should throw ValidationError for invalid sortBy', async () => { - await expect(useCase.execute({ sortBy: 'invalid' as any })).rejects.toThrow(ValidationError); + await expect(useCase.execute({ sortBy: 'invalid' as unknown as 'name' })).rejects.toThrow(ValidationError); }); }); diff --git a/core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase.test.ts b/core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase.test.ts index 54e9eb45c..cdcc7ec03 100644 --- a/core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase.test.ts +++ b/core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase.test.ts @@ -1,30 +1,35 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { GetGlobalLeaderboardsUseCase, GetGlobalLeaderboardsUseCasePorts } from './GetGlobalLeaderboardsUseCase'; +import { LeaderboardsRepository, LeaderboardDriverData, LeaderboardTeamData } from '../ports/LeaderboardsRepository'; +import { LeaderboardsEventPublisher } from '../ports/LeaderboardsEventPublisher'; describe('GetGlobalLeaderboardsUseCase', () => { - let mockLeaderboardsRepository: any; - let mockEventPublisher: any; + let mockLeaderboardsRepository: LeaderboardsRepository; + let mockEventPublisher: LeaderboardsEventPublisher; let ports: GetGlobalLeaderboardsUseCasePorts; let useCase: GetGlobalLeaderboardsUseCase; - const mockDrivers = [ + const mockDrivers: LeaderboardDriverData[] = [ { id: 'd1', name: 'Alice', rating: 2000, raceCount: 10 }, { id: 'd2', name: 'Bob', rating: 1500, raceCount: 5 }, ]; - const mockTeams = [ + const mockTeams: LeaderboardTeamData[] = [ { id: 't1', name: 'Team A', rating: 2500, memberCount: 5, raceCount: 20 }, { id: 't2', name: 'Team B', rating: 2200, memberCount: 3, raceCount: 15 }, ]; beforeEach(() => { mockLeaderboardsRepository = { - findAllDrivers: vi.fn().mockResolvedValue([...mockDrivers]), - findAllTeams: vi.fn().mockResolvedValue([...mockTeams]), + findAllDrivers: vi.fn().mockResolvedValue([...mockDrivers]) as unknown as Mock, + findAllTeams: vi.fn().mockResolvedValue([...mockTeams]) as unknown as Mock, + findDriversByTeamId: vi.fn() as unknown as Mock, }; mockEventPublisher = { - publishGlobalLeaderboardsAccessed: vi.fn().mockResolvedValue(undefined), - publishLeaderboardsError: vi.fn().mockResolvedValue(undefined), + publishGlobalLeaderboardsAccessed: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + publishLeaderboardsError: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + publishDriverRankingsAccessed: vi.fn() as unknown as Mock, + publishTeamRankingsAccessed: vi.fn() as unknown as Mock, }; ports = { leaderboardsRepository: mockLeaderboardsRepository, @@ -57,7 +62,7 @@ describe('GetGlobalLeaderboardsUseCase', () => { }); it('should handle errors and publish error event', async () => { - mockLeaderboardsRepository.findAllDrivers.mockRejectedValue(new Error('Repo error')); + (mockLeaderboardsRepository.findAllDrivers as Mock).mockRejectedValue(new Error('Repo error')); await expect(useCase.execute()).rejects.toThrow('Repo error'); expect(mockEventPublisher.publishLeaderboardsError).toHaveBeenCalled(); diff --git a/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.test.ts b/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.test.ts index e72fa9b12..eec66888a 100644 --- a/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.test.ts +++ b/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.test.ts @@ -1,19 +1,21 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { GetTeamRankingsUseCase, GetTeamRankingsUseCasePorts } from './GetTeamRankingsUseCase'; import { ValidationError } from '../../../shared/errors/ValidationError'; +import { LeaderboardsRepository, LeaderboardTeamData, LeaderboardDriverData } from '../ports/LeaderboardsRepository'; +import { LeaderboardsEventPublisher } from '../ports/LeaderboardsEventPublisher'; describe('GetTeamRankingsUseCase', () => { - let mockLeaderboardsRepository: any; - let mockEventPublisher: any; + let mockLeaderboardsRepository: LeaderboardsRepository; + let mockEventPublisher: LeaderboardsEventPublisher; let ports: GetTeamRankingsUseCasePorts; let useCase: GetTeamRankingsUseCase; - const mockTeams = [ + const mockTeams: LeaderboardTeamData[] = [ { id: 't1', name: 'Team A', rating: 2500, memberCount: 0, raceCount: 20 }, { id: 't2', name: 'Team B', rating: 2200, memberCount: 0, raceCount: 15 }, ]; - const mockDrivers = [ + const mockDrivers: LeaderboardDriverData[] = [ { id: 'd1', name: 'Alice', rating: 2000, raceCount: 10, teamId: 't1', teamName: 'Team A' }, { id: 'd2', name: 'Bob', rating: 1500, raceCount: 5, teamId: 't1', teamName: 'Team A' }, { id: 'd3', name: 'Charlie', rating: 1800, raceCount: 8, teamId: 't2', teamName: 'Team B' }, @@ -22,12 +24,15 @@ describe('GetTeamRankingsUseCase', () => { beforeEach(() => { mockLeaderboardsRepository = { - findAllTeams: vi.fn().mockResolvedValue([...mockTeams]), - findAllDrivers: vi.fn().mockResolvedValue([...mockDrivers]), + findAllTeams: vi.fn().mockResolvedValue([...mockTeams]) as unknown as Mock, + findAllDrivers: vi.fn().mockResolvedValue([...mockDrivers]) as unknown as Mock, + findDriversByTeamId: vi.fn() as unknown as Mock, }; mockEventPublisher = { - publishTeamRankingsAccessed: vi.fn().mockResolvedValue(undefined), - publishLeaderboardsError: vi.fn().mockResolvedValue(undefined), + publishTeamRankingsAccessed: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + publishLeaderboardsError: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + publishGlobalLeaderboardsAccessed: vi.fn() as unknown as Mock, + publishDriverRankingsAccessed: vi.fn() as unknown as Mock, }; ports = { leaderboardsRepository: mockLeaderboardsRepository, diff --git a/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.ts b/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.ts index 2dc621ec7..84dc4268b 100644 --- a/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.ts +++ b/core/leaderboards/application/use-cases/GetTeamRankingsUseCase.ts @@ -19,6 +19,14 @@ export interface GetTeamRankingsUseCasePorts { eventPublisher: LeaderboardsEventPublisher; } +interface DiscoveredTeam { + id: string; + name: string; + rating: number; + memberCount: number; + raceCount: number; +} + export class GetTeamRankingsUseCase { constructor(private readonly ports: GetTeamRankingsUseCasePorts) {} @@ -56,7 +64,7 @@ export class GetTeamRankingsUseCase { }); // Discover teams that only exist in the drivers repository - const discoveredTeams: any[] = []; + const discoveredTeams: DiscoveredTeam[] = []; driverCounts.forEach((count, teamId) => { if (!allTeams.some(t => t.id === teamId)) { const driverWithTeam = allDrivers.find(d => d.teamId === teamId); diff --git a/core/leagues/application/ports/LeagueCreateCommand.ts b/core/leagues/application/ports/LeagueCreateCommand.ts index 9fb111aee..8b669a0c2 100644 --- a/core/leagues/application/ports/LeagueCreateCommand.ts +++ b/core/leagues/application/ports/LeagueCreateCommand.ts @@ -1,3 +1,18 @@ +export interface ScoringSystem { + // Define scoring system properties based on your domain + // This is a placeholder - adjust based on actual scoring system structure + pointsPerPosition?: Record; + bonusPoints?: { + polePosition?: number; + fastestLap?: number; + cleanRace?: number; + }; + penalties?: { + timePenalty?: number; + pointsDeduction?: number; + }; +} + export interface LeagueCreateCommand { name: string; description?: string; @@ -16,7 +31,7 @@ export interface LeagueCreateCommand { tracks?: string[]; // Scoring - scoringSystem?: any; + scoringSystem?: ScoringSystem; bonusPointsEnabled: boolean; penaltiesEnabled: boolean; diff --git a/core/leagues/application/ports/LeagueEventPublisher.ts b/core/leagues/application/ports/LeagueEventPublisher.ts index c8ed25dc3..4eec9d703 100644 --- a/core/leagues/application/ports/LeagueEventPublisher.ts +++ b/core/leagues/application/ports/LeagueEventPublisher.ts @@ -1,3 +1,5 @@ +import { ScoringSystem } from './LeagueCreateCommand'; + export interface LeagueCreatedEvent { type: 'LeagueCreatedEvent'; leagueId: string; @@ -5,10 +7,33 @@ export interface LeagueCreatedEvent { timestamp: Date; } +export interface LeagueUpdates { + name?: string; + description?: string; + visibility?: 'public' | 'private'; + maxDrivers?: number; + approvalRequired?: boolean; + lateJoinAllowed?: boolean; + raceFrequency?: string; + raceDay?: string; + raceTime?: string; + tracks?: string[]; + scoringSystem?: ScoringSystem; + bonusPointsEnabled?: boolean; + penaltiesEnabled?: boolean; + protestsEnabled?: boolean; + appealsEnabled?: boolean; + stewardTeam?: string[]; + gameType?: string; + skillLevel?: string; + category?: string; + tags?: string[]; +} + export interface LeagueUpdatedEvent { type: 'LeagueUpdatedEvent'; leagueId: string; - updates: Partial; + updates: Partial; timestamp: Date; } diff --git a/core/leagues/application/ports/LeagueRepository.ts b/core/leagues/application/ports/LeagueRepository.ts index 9efd78827..39e5aee92 100644 --- a/core/leagues/application/ports/LeagueRepository.ts +++ b/core/leagues/application/ports/LeagueRepository.ts @@ -1,3 +1,5 @@ +import { ScoringSystem } from './LeagueCreateCommand'; + export interface LeagueData { id: string; name: string; @@ -20,7 +22,7 @@ export interface LeagueData { tracks: string[] | null; // Scoring - scoringSystem: any | null; + scoringSystem: ScoringSystem | null; bonusPointsEnabled: boolean; penaltiesEnabled: boolean; diff --git a/core/leagues/application/use-cases/CreateLeagueUseCase.test.ts b/core/leagues/application/use-cases/CreateLeagueUseCase.test.ts index 0c72ba39b..08d991553 100644 --- a/core/leagues/application/use-cases/CreateLeagueUseCase.test.ts +++ b/core/leagues/application/use-cases/CreateLeagueUseCase.test.ts @@ -1,28 +1,63 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { CreateLeagueUseCase } from './CreateLeagueUseCase'; import { LeagueCreateCommand } from '../ports/LeagueCreateCommand'; +import { LeagueRepository } from '../ports/LeagueRepository'; +import { LeagueEventPublisher } from '../ports/LeagueEventPublisher'; describe('CreateLeagueUseCase', () => { - let mockLeagueRepository: any; - let mockEventPublisher: any; + let mockLeagueRepository: LeagueRepository; + let mockEventPublisher: LeagueEventPublisher; let useCase: CreateLeagueUseCase; beforeEach(() => { mockLeagueRepository = { - create: vi.fn().mockImplementation((data) => Promise.resolve(data)), - updateStats: vi.fn().mockResolvedValue(undefined), - updateFinancials: vi.fn().mockResolvedValue(undefined), - updateStewardingMetrics: vi.fn().mockResolvedValue(undefined), - updatePerformanceMetrics: vi.fn().mockResolvedValue(undefined), - updateRatingMetrics: vi.fn().mockResolvedValue(undefined), - updateTrendMetrics: vi.fn().mockResolvedValue(undefined), - updateSuccessRateMetrics: vi.fn().mockResolvedValue(undefined), - updateResolutionTimeMetrics: vi.fn().mockResolvedValue(undefined), - updateComplexSuccessRateMetrics: vi.fn().mockResolvedValue(undefined), - updateComplexResolutionTimeMetrics: vi.fn().mockResolvedValue(undefined), + create: vi.fn().mockImplementation((data) => Promise.resolve(data)) as unknown as Mock, + findById: vi.fn() as unknown as Mock, + findByName: vi.fn() as unknown as Mock, + findByOwner: vi.fn() as unknown as Mock, + search: vi.fn() as unknown as Mock, + update: vi.fn() as unknown as Mock, + delete: vi.fn() as unknown as Mock, + getStats: vi.fn() as unknown as Mock, + updateStats: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getFinancials: vi.fn() as unknown as Mock, + updateFinancials: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getStewardingMetrics: vi.fn() as unknown as Mock, + updateStewardingMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getPerformanceMetrics: vi.fn() as unknown as Mock, + updatePerformanceMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getRatingMetrics: vi.fn() as unknown as Mock, + updateRatingMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getTrendMetrics: vi.fn() as unknown as Mock, + updateTrendMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getSuccessRateMetrics: vi.fn() as unknown as Mock, + updateSuccessRateMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateResolutionTimeMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getComplexSuccessRateMetrics: vi.fn() as unknown as Mock, + updateComplexSuccessRateMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getComplexResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateComplexResolutionTimeMetrics: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + getLeagueMembers: vi.fn() as unknown as Mock, + getPendingRequests: vi.fn() as unknown as Mock, + addLeagueMembers: vi.fn() as unknown as Mock, + updateLeagueMember: vi.fn() as unknown as Mock, + removeLeagueMember: vi.fn() as unknown as Mock, + addPendingRequests: vi.fn() as unknown as Mock, + removePendingRequest: vi.fn() as unknown as Mock, }; mockEventPublisher = { - emitLeagueCreated: vi.fn().mockResolvedValue(undefined), + emitLeagueCreated: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + emitLeagueUpdated: vi.fn() as unknown as Mock, + emitLeagueDeleted: vi.fn() as unknown as Mock, + emitLeagueAccessed: vi.fn() as unknown as Mock, + emitLeagueRosterAccessed: vi.fn() as unknown as Mock, + getLeagueCreatedEventCount: vi.fn().mockReturnValue(0) as unknown as Mock, + getLeagueUpdatedEventCount: vi.fn().mockReturnValue(0) as unknown as Mock, + getLeagueDeletedEventCount: vi.fn().mockReturnValue(0) as unknown as Mock, + getLeagueAccessedEventCount: vi.fn().mockReturnValue(0) as unknown as Mock, + getLeagueRosterAccessedEventCount: vi.fn().mockReturnValue(0) as unknown as Mock, + clear: vi.fn() as unknown as Mock, }; useCase = new CreateLeagueUseCase(mockLeagueRepository, mockEventPublisher); }); @@ -51,12 +86,12 @@ describe('CreateLeagueUseCase', () => { }); it('should throw error if name is missing', async () => { - const command: any = { ownerId: 'owner-1' }; + const command = { ownerId: 'owner-1' } as unknown as LeagueCreateCommand; await expect(useCase.execute(command)).rejects.toThrow('League name is required'); }); it('should throw error if ownerId is missing', async () => { - const command: any = { name: 'League' }; + const command = { name: 'League' } as unknown as LeagueCreateCommand; await expect(useCase.execute(command)).rejects.toThrow('Owner ID is required'); }); }); diff --git a/core/leagues/application/use-cases/DemoteAdminUseCase.test.ts b/core/leagues/application/use-cases/DemoteAdminUseCase.test.ts index b642cf082..18d4a0bd3 100644 --- a/core/leagues/application/use-cases/DemoteAdminUseCase.test.ts +++ b/core/leagues/application/use-cases/DemoteAdminUseCase.test.ts @@ -1,19 +1,94 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; import { DemoteAdminUseCase } from './DemoteAdminUseCase'; +import { LeagueRepository } from '../ports/LeagueRepository'; +import { DriverRepository } from '../../racing/domain/repositories/DriverRepository'; +import { LeagueEventPublisher } from '../ports/LeagueEventPublisher'; describe('DemoteAdminUseCase', () => { - let mockLeagueRepository: any; - let mockDriverRepository: any; - let mockEventPublisher: any; + let mockLeagueRepository: LeagueRepository; + let mockDriverRepository: DriverRepository; + let mockEventPublisher: LeagueEventPublisher; let useCase: DemoteAdminUseCase; beforeEach(() => { mockLeagueRepository = { - updateLeagueMember: vi.fn().mockResolvedValue(undefined), + updateLeagueMember: vi.fn().mockResolvedValue(undefined) as unknown as Mock, + create: vi.fn() as unknown as Mock, + findById: vi.fn() as unknown as Mock, + findByName: vi.fn() as unknown as Mock, + findByOwner: vi.fn() as unknown as Mock, + search: vi.fn() as unknown as Mock, + update: vi.fn() as unknown as Mock, + delete: vi.fn() as unknown as Mock, + getStats: vi.fn() as unknown as Mock, + updateStats: vi.fn() as unknown as Mock, + getFinancials: vi.fn() as unknown as Mock, + updateFinancials: vi.fn() as unknown as Mock, + getStewardingMetrics: vi.fn() as unknown as Mock, + updateStewardingMetrics: vi.fn() as unknown as Mock, + getPerformanceMetrics: vi.fn() as unknown as Mock, + updatePerformanceMetrics: vi.fn() as unknown as Mock, + getRatingMetrics: vi.fn() as unknown as Mock, + updateRatingMetrics: vi.fn() as unknown as Mock, + getTrendMetrics: vi.fn() as unknown as Mock, + updateTrendMetrics: vi.fn() as unknown as Mock, + getSuccessRateMetrics: vi.fn() as unknown as Mock, + updateSuccessRateMetrics: vi.fn() as unknown as Mock, + getResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateResolutionTimeMetrics: vi.fn() as unknown as Mock, + getComplexSuccessRateMetrics: vi.fn() as unknown as Mock, + updateComplexSuccessRateMetrics: vi.fn() as unknown as Mock, + getComplexResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateComplexResolutionTimeMetrics: vi.fn() as unknown as Mock, + getLeagueMembers: vi.fn() as unknown as Mock, + getPendingRequests: vi.fn() as unknown as Mock, + addLeagueMembers: vi.fn() as unknown as Mock, + removeLeagueMember: vi.fn() as unknown as Mock, + addPendingRequests: vi.fn() as unknown as Mock, + removePendingRequest: vi.fn() as unknown as Mock, }; - mockDriverRepository = {}; - mockEventPublisher = {}; - useCase = new DemoteAdminUseCase(mockLeagueRepository, mockDriverRepository, mockEventPublisher as any); + mockDriverRepository = { + findById: vi.fn() as unknown as Mock, + findByName: vi.fn() as unknown as Mock, + findByEmail: vi.fn() as unknown as Mock, + search: vi.fn() as unknown as Mock, + update: vi.fn() as unknown as Mock, + delete: vi.fn() as unknown as Mock, + getStats: vi.fn() as unknown as Mock, + updateStats: vi.fn() as unknown as Mock, + getPerformanceMetrics: vi.fn() as unknown as Mock, + updatePerformanceMetrics: vi.fn() as unknown as Mock, + getRatingMetrics: vi.fn() as unknown as Mock, + updateRatingMetrics: vi.fn() as unknown as Mock, + getTrendMetrics: vi.fn() as unknown as Mock, + updateTrendMetrics: vi.fn() as unknown as Mock, + getSuccessRateMetrics: vi.fn() as unknown as Mock, + updateSuccessRateMetrics: vi.fn() as unknown as Mock, + getResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateResolutionTimeMetrics: vi.fn() as unknown as Mock, + getComplexSuccessRateMetrics: vi.fn() as unknown as Mock, + updateComplexSuccessRateMetrics: vi.fn() as unknown as Mock, + getComplexResolutionTimeMetrics: vi.fn() as unknown as Mock, + updateComplexResolutionTimeMetrics: vi.fn() as unknown as Mock, + getDriverMemberships: vi.fn() as unknown as Mock, + addDriverMembership: vi.fn() as unknown as Mock, + updateDriverMembership: vi.fn() as unknown as Mock, + removeDriverMembership: vi.fn() as unknown as Mock, + }; + mockEventPublisher = { + emitLeagueCreated: vi.fn() as unknown as Mock, + emitLeagueUpdated: vi.fn() as unknown as Mock, + emitLeagueDeleted: vi.fn() as unknown as Mock, + emitLeagueAccessed: vi.fn() as unknown as Mock, + emitLeagueRosterAccessed: vi.fn() as unknown as Mock, + getLeagueCreatedEventCount: vi.fn() as unknown as Mock, + getLeagueUpdatedEventCount: vi.fn() as unknown as Mock, + getLeagueDeletedEventCount: vi.fn() as unknown as Mock, + getLeagueAccessedEventCount: vi.fn() as unknown as Mock, + getLeagueRosterAccessedEventCount: vi.fn() as unknown as Mock, + clear: vi.fn() as unknown as Mock, + }; + useCase = new DemoteAdminUseCase(mockLeagueRepository, mockDriverRepository, mockEventPublisher); }); it('should update member role to member', async () => { diff --git a/core/rating/application/use-cases/CalculateRatingUseCase.test.ts b/core/rating/application/use-cases/CalculateRatingUseCase.test.ts index d576cd270..413f21a73 100644 --- a/core/rating/application/use-cases/CalculateRatingUseCase.test.ts +++ b/core/rating/application/use-cases/CalculateRatingUseCase.test.ts @@ -1,6 +1,6 @@ /** * Unit tests for CalculateRatingUseCase - * + * * Tests business logic and orchestration using mocked ports. */ @@ -9,6 +9,11 @@ import { CalculateRatingUseCase } from './CalculateRatingUseCase'; import { Driver } from '../../../racing/domain/entities/Driver'; import { Race } from '../../../racing/domain/entities/Race'; import { Result } from '../../../racing/domain/entities/result/Result'; +import { DriverRepository } from '../../../racing/domain/repositories/DriverRepository'; +import { RaceRepository } from '../../../racing/domain/repositories/RaceRepository'; +import { ResultRepository } from '../../../racing/domain/repositories/ResultRepository'; +import { RatingRepository } from '../../ports/RatingRepository'; +import { EventPublisher } from '../../../shared/ports/EventPublisher'; // Mock repositories and publisher const mockDriverRepository = { @@ -37,11 +42,11 @@ describe('CalculateRatingUseCase', () => { beforeEach(() => { vi.clearAllMocks(); useCase = new CalculateRatingUseCase({ - driverRepository: mockDriverRepository as any, - raceRepository: mockRaceRepository as any, - resultRepository: mockResultRepository as any, - ratingRepository: mockRatingRepository as any, - eventPublisher: mockEventPublisher as any, + driverRepository: mockDriverRepository as unknown as DriverRepository, + raceRepository: mockRaceRepository as unknown as RaceRepository, + resultRepository: mockResultRepository as unknown as ResultRepository, + ratingRepository: mockRatingRepository as unknown as RatingRepository, + eventPublisher: mockEventPublisher as unknown as EventPublisher, }); }); diff --git a/core/rating/application/use-cases/CalculateRatingUseCase.ts b/core/rating/application/use-cases/CalculateRatingUseCase.ts index 2473c9513..2236cacfc 100644 --- a/core/rating/application/use-cases/CalculateRatingUseCase.ts +++ b/core/rating/application/use-cases/CalculateRatingUseCase.ts @@ -14,6 +14,7 @@ import { RatingComponents } from '../../domain/RatingComponents'; import { RatingCalculatedEvent } from '../../domain/events/RatingCalculatedEvent'; import { DriverId } from '../../../racing/domain/entities/DriverId'; import { RaceId } from '../../../racing/domain/entities/RaceId'; +import { Result as RaceResult } from '../../../racing/domain/entities/result/Result'; export interface CalculateRatingUseCasePorts { driverRepository: DriverRepository; @@ -84,12 +85,12 @@ export class CalculateRatingUseCase { } } - private calculateComponents(driverResult: any, allResults: any[]): RatingComponents { - const position = typeof driverResult.position === 'object' ? (typeof driverResult.position.toNumber === 'function' ? driverResult.position.toNumber() : driverResult.position.value) : driverResult.position; + private calculateComponents(driverResult: RaceResult, allResults: RaceResult[]): RatingComponents { + const position = driverResult.position.toNumber(); const totalDrivers = allResults.length; - const incidents = typeof driverResult.incidents === 'object' ? (typeof driverResult.incidents.toNumber === 'function' ? driverResult.incidents.toNumber() : driverResult.incidents.value) : driverResult.incidents; - const lapsCompleted = typeof driverResult.lapsCompleted === 'object' ? (typeof driverResult.lapsCompleted.toNumber === 'function' ? driverResult.lapsCompleted.toNumber() : driverResult.lapsCompleted.value) : (driverResult.lapsCompleted !== undefined ? driverResult.lapsCompleted : (driverResult.totalTime === 0 && (typeof position === 'object' ? position.value : position) > 0 ? 5 : (driverResult.points === 0 && (typeof position === 'object' ? position.value : position) > 0 ? 5 : 20))); - const startPosition = typeof driverResult.startPosition === 'object' ? driverResult.startPosition.toNumber() : driverResult.startPosition; + const incidents = driverResult.incidents.toNumber(); + const startPosition = driverResult.startPosition.toNumber(); + const points = driverResult.points; // Results Strength: Based on position relative to field size const resultsStrength = this.calculateResultsStrength(position, totalDrivers); @@ -104,10 +105,12 @@ export class CalculateRatingUseCase { const racecraft = this.calculateRacecraft(position, startPosition); // Reliability: Based on laps completed and DNF/DNS - const reliability = this.calculateReliability(lapsCompleted, position, driverResult.points); + // For the Result entity, we need to determine reliability based on position and points + // If position is 0 (DNS) or points are 0 (DNF), reliability is low + const reliability = this.calculateReliabilityFromResult(position, points); // Team Contribution: Based on points scored - const teamContribution = this.calculateTeamContribution(driverResult.points); + const teamContribution = this.calculateTeamContribution(points); return { resultsStrength, @@ -119,6 +122,21 @@ export class CalculateRatingUseCase { }; } + private calculateReliabilityFromResult(position: number, points: number): number { + // DNS (Did Not Start) - position 0 + if (position === 0) { + return 1; + } + + // DNF (Did Not Finish) - no points but finished (position > 0) + if (points === 0 && position > 0) { + return 20; + } + + // Finished with points - high reliability + return 100; + } + private calculateResultsStrength(position: number, totalDrivers: number): number { if (position <= 0) return 1; // DNF/DNS (ensure > 0) const drivers = totalDrivers || 1; diff --git a/core/rating/application/use-cases/CalculateTeamContributionUseCase.test.ts b/core/rating/application/use-cases/CalculateTeamContributionUseCase.test.ts index fd1c51af5..c725cf43b 100644 --- a/core/rating/application/use-cases/CalculateTeamContributionUseCase.test.ts +++ b/core/rating/application/use-cases/CalculateTeamContributionUseCase.test.ts @@ -8,6 +8,8 @@ import { Driver } from '../../../racing/domain/entities/Driver'; import { Race } from '../../../racing/domain/entities/Race'; import { Result } from '../../../racing/domain/entities/result/Result'; import { Rating } from '../../domain/Rating'; +import { DriverId } from '../../../racing/domain/entities/DriverId'; +import { RaceId } from '../../../racing/domain/entities/RaceId'; const mockRatingRepository = { findByDriverAndRace: vi.fn(), @@ -92,8 +94,8 @@ describe('CalculateTeamContributionUseCase', () => { const points = 12.5; // 50% contribution const existingRating = Rating.create({ - driverId: 'driver-1' as any, // Simplified for test - raceId: 'race-1' as any, + driverId: DriverId.create('driver-1'), + raceId: RaceId.create('race-1'), rating: 1500, components: { resultsStrength: 80, @@ -106,10 +108,10 @@ describe('CalculateTeamContributionUseCase', () => { timestamp: new Date('2023-01-01') }); - mockDriverRepository.findById.mockResolvedValue({ id: driverId } as any); - mockRaceRepository.findById.mockResolvedValue({ id: raceId } as any); + mockDriverRepository.findById.mockResolvedValue({ id: driverId }); + mockRaceRepository.findById.mockResolvedValue({ id: raceId }); mockResultRepository.findByRaceId.mockResolvedValue([ - { driverId: { toString: () => driverId }, points } as any + { driverId: { toString: () => driverId }, points } ]); mockRatingRepository.findByDriverAndRace.mockResolvedValue(existingRating); diff --git a/core/rating/application/use-cases/GetRatingLeaderboardUseCase.test.ts b/core/rating/application/use-cases/GetRatingLeaderboardUseCase.test.ts index d81a5c930..cbd857b0d 100644 --- a/core/rating/application/use-cases/GetRatingLeaderboardUseCase.test.ts +++ b/core/rating/application/use-cases/GetRatingLeaderboardUseCase.test.ts @@ -5,6 +5,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { GetRatingLeaderboardUseCase } from './GetRatingLeaderboardUseCase'; import { Rating } from '../../domain/Rating'; +import { DriverId } from '../../../racing/domain/entities/DriverId'; +import { RaceId } from '../../../racing/domain/entities/RaceId'; const mockRatingRepository = { findByDriver: vi.fn(), @@ -37,37 +39,65 @@ describe('GetRatingLeaderboardUseCase', () => { const ratingsD1 = [ Rating.create({ - driverId: 'd1' as any, - raceId: 'r1' as any, + driverId: DriverId.create('d1'), + raceId: RaceId.create('r1'), rating: 1000, - components: {} as any, + components: { + resultsStrength: 0, + consistency: 0, + cleanDriving: 0, + racecraft: 0, + reliability: 0, + teamContribution: 0, + }, timestamp: new Date('2023-01-01') }), Rating.create({ - driverId: 'd1' as any, - raceId: 'r2' as any, + driverId: DriverId.create('d1'), + raceId: RaceId.create('r2'), rating: 1200, // Latest for D1 - components: {} as any, + components: { + resultsStrength: 0, + consistency: 0, + cleanDriving: 0, + racecraft: 0, + reliability: 0, + teamContribution: 0, + }, timestamp: new Date('2023-01-02') }) ]; const ratingsD2 = [ Rating.create({ - driverId: 'd2' as any, - raceId: 'r1' as any, + driverId: DriverId.create('d2'), + raceId: RaceId.create('r1'), rating: 1500, // Latest for D2 - components: {} as any, + components: { + resultsStrength: 0, + consistency: 0, + cleanDriving: 0, + racecraft: 0, + reliability: 0, + teamContribution: 0, + }, timestamp: new Date('2023-01-01') }) ]; const ratingsD3 = [ Rating.create({ - driverId: 'd3' as any, - raceId: 'r1' as any, + driverId: DriverId.create('d3'), + raceId: RaceId.create('r1'), rating: 800, // Latest for D3 - components: {} as any, + components: { + resultsStrength: 0, + consistency: 0, + cleanDriving: 0, + racecraft: 0, + reliability: 0, + teamContribution: 0, + }, timestamp: new Date('2023-01-01') }) ]; diff --git a/core/rating/application/use-cases/SaveRatingUseCase.test.ts b/core/rating/application/use-cases/SaveRatingUseCase.test.ts index 8628d3ee1..e04fd185a 100644 --- a/core/rating/application/use-cases/SaveRatingUseCase.test.ts +++ b/core/rating/application/use-cases/SaveRatingUseCase.test.ts @@ -4,6 +4,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SaveRatingUseCase } from './SaveRatingUseCase'; +import { RatingRepository } from '../../ports/RatingRepository'; const mockRatingRepository = { save: vi.fn(), @@ -15,7 +16,7 @@ describe('SaveRatingUseCase', () => { beforeEach(() => { vi.clearAllMocks(); useCase = new SaveRatingUseCase({ - ratingRepository: mockRatingRepository as any, + ratingRepository: mockRatingRepository as unknown as RatingRepository, }); }); diff --git a/core/rating/domain/Rating.ts b/core/rating/domain/Rating.ts index 084a96af4..aaa070777 100644 --- a/core/rating/domain/Rating.ts +++ b/core/rating/domain/Rating.ts @@ -1,6 +1,6 @@ /** * Rating Entity - * + * * Represents a driver's rating calculated after a race. */ @@ -16,6 +16,14 @@ export interface RatingProps { timestamp: Date; } +export interface RatingJSON { + driverId: string; + raceId: string; + rating: number; + components: RatingComponents; + timestamp: string; +} + export class Rating { private constructor(private readonly props: RatingProps) {} @@ -43,7 +51,7 @@ export class Rating { return this.props.timestamp; } - toJSON(): Record { + toJSON(): RatingJSON { return { driverId: this.driverId.toString(), raceId: this.raceId.toString(), diff --git a/core/rating/domain/events/RatingCalculatedEvent.ts b/core/rating/domain/events/RatingCalculatedEvent.ts index 667ce264d..045bf333e 100644 --- a/core/rating/domain/events/RatingCalculatedEvent.ts +++ b/core/rating/domain/events/RatingCalculatedEvent.ts @@ -1,15 +1,22 @@ /** * RatingCalculatedEvent - * + * * Event published when a driver's rating is calculated. */ import { DomainEvent } from '../../../shared/ports/EventPublisher'; -import { Rating } from '../Rating'; +import { Rating, RatingJSON } from '../Rating'; + +export interface RatingCalculatedEventJSON { + type: string; + timestamp: string; + rating: RatingJSON; +} export class RatingCalculatedEvent implements DomainEvent { readonly type = 'RatingCalculatedEvent'; readonly timestamp: Date; + [key: string]: unknown; constructor(private readonly rating: Rating) { this.timestamp = new Date(); @@ -19,7 +26,7 @@ export class RatingCalculatedEvent implements DomainEvent { return this.rating; } - toJSON(): Record { + toJSON(): RatingCalculatedEventJSON { return { type: this.type, timestamp: this.timestamp.toISOString(), diff --git a/core/shared/ports/EventPublisher.ts b/core/shared/ports/EventPublisher.ts index c63218409..c5fda37c4 100644 --- a/core/shared/ports/EventPublisher.ts +++ b/core/shared/ports/EventPublisher.ts @@ -1,6 +1,6 @@ /** * EventPublisher Port - * + * * Defines the interface for publishing domain events. * This port is implemented by adapters that can publish events. */ @@ -15,5 +15,5 @@ export interface EventPublisher { export interface DomainEvent { type: string; timestamp: Date; - [key: string]: any; + [key: string]: unknown; }