website refactor

This commit is contained in:
2026-01-16 18:21:06 +01:00
parent 2f53727702
commit 095885544b
146 changed files with 970 additions and 524 deletions

View File

@@ -1,5 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { ListUsersUseCase, ListUsersResult } from './ListUsersUseCase';
import { ListUsersUseCase } from './ListUsersUseCase';
import { AdminUserRepository } from '../ports/AdminUserRepository';
import { AdminUser } from '../../domain/entities/AdminUser';
import { AuthorizationService } from '../../domain/services/AuthorizationService';

View File

@@ -1,6 +1,6 @@
import type { DomainErrorProps, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
import type { DomainError, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
export abstract class AdminDomainError extends Error implements DomainErrorProps<CommonDomainErrorKind> {
export abstract class AdminDomainError extends Error implements DomainError<CommonDomainErrorKind> {
readonly type = 'domain' as const;
readonly context = 'admin-domain';
abstract readonly kind: CommonDomainErrorKind;

View File

@@ -5,8 +5,7 @@
* Uses EligibilityEvaluator to provide explainable results.
*/
import { EvaluationResultDto } from '../dtos/EvaluationResultDto';
import { EligibilityFilterDto } from '../dtos/EligibilityFilterDto';
import { EvaluationResultDto, EligibilityFilterDto } from '../../domain/types/Eligibility';
import { EligibilityEvaluator, RatingData } from '../../domain/services/EligibilityEvaluator';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';

View File

@@ -2,7 +2,7 @@
* Tests for GetUserRatingsSummaryQuery
*/
import { describe, expect, it, beforeEach, vi } from 'vitest';
import { describe, expect, it, beforeEach, vi, type Mock } from 'vitest';
import { GetUserRatingsSummaryQuery, GetUserRatingsSummaryQueryHandler } from './GetUserRatingsSummaryQuery';
import { UserRating } from '../../domain/value-objects/UserRating';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
@@ -13,11 +13,16 @@ 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';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserId } from '../../domain/value-objects/UserId';
describe('GetUserRatingsSummaryQuery', () => {
let mockUserRatingRepo: any;
let mockExternalRatingRepo: any;
let mockRatingEventRepo: any;
let mockUserRatingRepo: { findByUserId: Mock };
let mockExternalRatingRepo: { findByUserId: Mock };
let mockRatingEventRepo: { getAllByUserId: Mock };
let handler: GetUserRatingsSummaryQueryHandler;
beforeEach(() => {
@@ -32,9 +37,9 @@ describe('GetUserRatingsSummaryQuery', () => {
};
handler = new GetUserRatingsSummaryQueryHandler(
mockUserRatingRepo,
mockExternalRatingRepo,
mockRatingEventRepo
mockUserRatingRepo as unknown as UserRatingRepository,
mockExternalRatingRepo as unknown as ExternalGameRatingRepository,
mockRatingEventRepo as unknown as RatingEventRepository
);
});
@@ -49,7 +54,7 @@ describe('GetUserRatingsSummaryQuery', () => {
// Mock external ratings
const gameKey = GameKey.create('iracing');
const profile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey,
ratings: new Map([
['iRating', ExternalRating.create(gameKey, 'iRating', 2200)],
@@ -109,7 +114,7 @@ describe('GetUserRatingsSummaryQuery', () => {
// Multiple game profiles
const iracingProfile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey: GameKey.create('iracing'),
ratings: new Map([
['iRating', ExternalRating.create(GameKey.create('iracing'), 'iRating', 2200)],
@@ -118,7 +123,7 @@ describe('GetUserRatingsSummaryQuery', () => {
});
const assettoProfile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey: GameKey.create('assetto'),
ratings: new Map([
['rating', ExternalRating.create(GameKey.create('assetto'), 'rating', 85)],

View File

@@ -134,14 +134,16 @@ class MockUserRatingRepository {
}
}
import { CreateRatingEventDto } from '../dtos/CreateRatingEventDto';
// Mock AppendRatingEventsUseCase
class MockAppendRatingEventsUseCase {
constructor(
private ratingEventRepository: any,
private userRatingRepository: any
private ratingEventRepository: MockRatingEventRepository,
private userRatingRepository: MockUserRatingRepository
) {}
async execute(input: any): Promise<any> {
async execute(input: { userId: string; events: CreateRatingEventDto[] }): Promise<{ events: string[]; snapshotUpdated: boolean }> {
const events: RatingEvent[] = [];
// Create events from input
@@ -195,7 +197,6 @@ describe('Admin Vote Session Use Cases', () => {
// Use dates relative to current time so close() works
const now = new Date(Date.now() - 86400000); // Yesterday
const tomorrow = new Date(Date.now() + 86400000); // Tomorrow
const dayAfter = new Date(Date.now() + 86400000 * 2); // Day after tomorrow
beforeEach(() => {
mockSessionRepo = new MockAdminVoteSessionRepository();

View File

@@ -3,9 +3,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest';
import { AppendRatingEventsUseCase, AppendRatingEventsInput } from './AppendRatingEventsUseCase';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
describe('AppendRatingEventsUseCase', () => {
let mockEventRepo: Partial<RatingEventRepository>;

View File

@@ -2,7 +2,7 @@ import { RatingEventRepository } from '../../domain/repositories/RatingEventRepo
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEvent, type RatingEventProps } 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';
@@ -97,7 +97,7 @@ export class AppendRatingEventsUseCase {
}
private createEventFromDto(dto: CreateRatingEventDto) {
const props: any = {
const props: RatingEventProps = {
id: RatingEventId.generate(),
userId: dto.userId,
dimension: RatingDimensionKey.create(dto.dimension),

View File

@@ -1,10 +1,10 @@
import { AdminVoteSessionRepository } from '../../domain/repositories/AdminVoteSessionRepository';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { AdminTrustRatingCalculator } from '../../domain/services/AdminTrustRatingCalculator';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { CloseAdminVoteSessionInput, CloseAdminVoteSessionOutput } from '../dtos/AdminVoteSessionDto';
import { AdminVoteSession, AdminVoteOutcome } from '../../domain/entities/AdminVoteSession';
/**
* Use Case: CloseAdminVoteSessionUseCase
@@ -26,7 +26,6 @@ export class CloseAdminVoteSessionUseCase {
private readonly adminVoteSessionRepository: AdminVoteSessionRepository,
private readonly ratingEventRepository: RatingEventRepository,
private readonly userRatingRepository: UserRatingRepository,
private readonly appendRatingEventsUseCase: any, // Will be typed properly in integration
) {}
async execute(input: CloseAdminVoteSessionInput): Promise<CloseAdminVoteSessionOutput> {
@@ -117,7 +116,7 @@ export class CloseAdminVoteSessionUseCase {
* Events are created for the admin being voted on
* Per plans: no events are created for tie outcomes
*/
private async createRatingEvents(session: any, outcome: any): Promise<number> {
private async createRatingEvents(session: AdminVoteSession, outcome: AdminVoteOutcome): Promise<number> {
let eventsCreated = 0;
// Don't create events for tie outcomes

View File

@@ -1,7 +1,8 @@
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { User } from '../../domain/entities/User';
import { UserRepository } from '../../domain/repositories/UserRepository';
export type GetCurrentSessionInput = {
userId: string;

View File

@@ -4,11 +4,12 @@
* Authenticates a user with email and password.
*/
import { PasswordHash } from '@/identity/domain/value-objects/PasswordHash';
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { UserRepository } from '../../domain/repositories/UserRepository';
export type LoginWithEmailInput = {
email: string;

View File

@@ -6,8 +6,6 @@ import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { UserRating } from '../../domain/value-objects/UserRating';
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';
// In-memory implementations for integration testing
class InMemoryRaceResultsProvider implements RaceResultsProvider {

View File

@@ -6,8 +6,7 @@ import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { UserRating } from '../../domain/value-objects/UserRating';
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';
import { describe, it, expect, beforeEach } from 'vitest';
// Mock implementations
class MockRaceResultsProvider implements RaceResultsProvider {
@@ -17,11 +16,11 @@ class MockRaceResultsProvider implements RaceResultsProvider {
this.results = results;
}
async getRaceResults(raceId: string): Promise<RaceResultsData | null> {
async getRaceResults(_raceId: string): Promise<RaceResultsData | null> {
return this.results;
}
async hasRaceResults(raceId: string): Promise<boolean> {
async hasRaceResults(_raceId: string): Promise<boolean> {
return this.results !== null;
}
}

View File

@@ -2,8 +2,6 @@ import { RaceResultsProvider } from '../ports/RaceResultsProvider';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { DrivingRatingCalculator } from '../../domain/services/DrivingRatingCalculator';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { RecordRaceRatingEventsInput, RecordRaceRatingEventsOutput } from '../dtos/RecordRaceRatingEventsDto';

View File

@@ -1,6 +1,7 @@
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { StoredUser } from '../../domain/repositories/UserRepository';
import type { StoredUser, UserRepository } from '../../domain/repositories/UserRepository';
import type { AuthenticatedUser } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';

View File

@@ -1,27 +1,30 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
// Mock repository for integration test
class MockExternalGameRatingRepository {
private profiles = new Map<string, any>();
private profiles = new Map<string, ExternalGameRatingProfile>();
private getKey(userId: string, gameKey: string): string {
return `${userId}|${gameKey}`;
}
async findByUserIdAndGameKey(userId: string, gameKey: string): Promise<any | null> {
async findByUserIdAndGameKey(userId: string, gameKey: string): Promise<ExternalGameRatingProfile | null> {
return this.profiles.get(this.getKey(userId, gameKey)) || null;
}
async findByUserId(userId: string): Promise<any[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.userId.toString() === userId);
async findByUserId(userId: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.userId.toString() === userId);
}
async findByGameKey(gameKey: string): Promise<any[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.gameKey.toString() === gameKey);
async findByGameKey(gameKey: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.gameKey.toString() === gameKey);
}
async save(profile: any): Promise<any> {
async save(profile: ExternalGameRatingProfile): Promise<ExternalGameRatingProfile> {
const key = this.getKey(profile.userId.toString(), profile.gameKey.toString());
this.profiles.set(key, profile);
return profile;
@@ -50,7 +53,7 @@ describe('UpsertExternalGameRatingUseCase - Integration', () => {
beforeEach(() => {
repository = new MockExternalGameRatingRepository();
useCase = new UpsertExternalGameRatingUseCase(repository as any);
useCase = new UpsertExternalGameRatingUseCase(repository as unknown as ExternalGameRatingRepository);
});
describe('Full upsert flow', () => {

View File

@@ -6,11 +6,19 @@ 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';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
describe('UpsertExternalGameRatingUseCase', () => {
let useCase: UpsertExternalGameRatingUseCase;
let mockRepository: ExternalGameRatingRepository;
let mockRepository: {
findByUserIdAndGameKey: Mock;
findByUserId: Mock;
findByGameKey: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
exists: Mock;
};
beforeEach(() => {
mockRepository = {
@@ -21,9 +29,17 @@ describe('UpsertExternalGameRatingUseCase', () => {
saveMany: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
} as any;
} as unknown as {
findByUserIdAndGameKey: Mock;
findByUserId: Mock;
findByGameKey: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
exists: Mock;
};
useCase = new UpsertExternalGameRatingUseCase(mockRepository);
useCase = new UpsertExternalGameRatingUseCase(mockRepository as unknown as ExternalGameRatingRepository);
});
describe('execute', () => {
@@ -42,8 +58,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);
@@ -61,8 +77,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
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);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(existingProfile);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
@@ -211,7 +227,7 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockRejectedValue(new Error('Database connection failed'));
mockRepository.findByUserIdAndGameKey.mockRejectedValue(new Error('Database connection failed'));
const result = await useCase.execute(input);
@@ -232,8 +248,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);
@@ -254,8 +270,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);

View File

@@ -1,5 +1,4 @@
import type { DomainError } from '@core/shared/errors/DomainError';
import type { CommonDomainErrorKind } from '@core/shared/errors/DomainError';
import type { DomainError, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
export abstract class IdentityDomainError extends Error implements DomainError<CommonDomainErrorKind> {
readonly type = 'domain' as const;

View File

@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { ExternalGameRatingRepository } from './ExternalGameRatingRepository';
import { ExternalGameRatingProfile } from '../entities/ExternalGameRatingProfile';
import { UserId } from '../value-objects/UserId';
@@ -182,11 +183,6 @@ describe('ExternalGameRatingRepository', () => {
await repository.save(profile);
// Update the profile
const updatedProvenance = ExternalRatingProvenance.create({
source: 'iracing',
lastSyncedAt: new Date('2024-01-02'),
verified: true,
});
profile.updateLastSyncedAt(new Date('2024-01-02'));
profile.markVerified();
@@ -290,7 +286,7 @@ describe('ExternalGameRatingRepository', () => {
lastSyncedAt: new Date('2024-01-01'),
verified: false,
});
profile2.updateRatings(profile2.ratings, profile2Provenance);
profile2.updateRatings(new Map(profile2.ratings), profile2Provenance);
await repository.saveMany([profile1, profile2]);

View File

@@ -3,7 +3,6 @@ import { RatingEvent } from '../entities/RatingEvent';
import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingDelta } from '../value-objects/RatingDelta';
import { AdminVoteOutcome } from '../entities/AdminVoteSession';
describe('AdminTrustRatingCalculator', () => {
describe('calculate', () => {
@@ -316,13 +315,12 @@ describe('AdminTrustRatingCalculator', () => {
it('should default to zero for unknown action type', () => {
const input: SystemSignalInput = {
actionType: 'sla_response' as any,
actionType: 'unknown_type' as unknown as SystemSignalInput['actionType'],
details: {},
};
// Override for test
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
expect(delta.value).toBe(5); // Known type
expect(delta.value).toBe(0);
});
});

View File

@@ -1,6 +1,4 @@
import { RatingEvent } from '../entities/RatingEvent';
import { DrivingReasonCode } from '../value-objects/DrivingReasonCode';
import { RatingDelta } from '../value-objects/RatingDelta';
/**
* Input DTO for driving rating calculation from race facts
@@ -71,7 +69,7 @@ export class DrivingRatingCalculator {
const fieldStrength = facts.results.length > 0
? (facts.results
.filter(r => r.status === 'finished')
.reduce((sum, r) => sum + (r.sof || this.estimateDriverRating(r.userId)), 0) /
.reduce((sum, r) => sum + (r.sof || this.estimateDriverRating()), 0) /
Math.max(1, facts.results.filter(r => r.status === 'finished').length))
: 0;
@@ -297,7 +295,7 @@ export class DrivingRatingCalculator {
* Estimate driver rating for SoF calculation
* This is a placeholder - in real implementation, would query user rating snapshot
*/
private static estimateDriverRating(userId: string): number {
private static estimateDriverRating(): number {
// Default rating for new drivers
return 50;
}

View File

@@ -3,7 +3,7 @@
*/
import { EligibilityEvaluator, RatingData } from './EligibilityEvaluator';
import { EligibilityFilterDto } from '../../application/dtos/EligibilityFilterDto';
import { EligibilityFilterDto } from '../types/Eligibility';
describe('EligibilityEvaluator', () => {
let evaluator: EligibilityEvaluator;

View File

@@ -6,8 +6,13 @@
* Provides explainable results with detailed reasons.
*/
import { EvaluationResultDto, EvaluationReason } from '../../application/dtos/EvaluationResultDto';
import { EligibilityFilterDto, ParsedEligibilityFilter, EligibilityCondition } from '../../application/dtos/EligibilityFilterDto';
import {
EvaluationResultDto,
EvaluationReason,
EligibilityFilterDto,
ParsedEligibilityFilter,
EligibilityCondition
} from '../types/Eligibility';
export interface RatingData {
platform: {

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { RatingUpdateService } from './RatingUpdateService';
import type { UserRatingRepository } from '../repositories/UserRatingRepository';
import type { RatingEventRepository } from '../repositories/RatingEventRepository';
@@ -10,8 +10,8 @@ import { RatingDelta } from '../value-objects/RatingDelta';
describe('RatingUpdateService - Slice 7 Evolution', () => {
let service: RatingUpdateService;
let userRatingRepository: any;
let ratingEventRepository: any;
let userRatingRepository: { findByUserId: Mock; save: Mock };
let ratingEventRepository: { save: Mock; getAllByUserId: Mock };
beforeEach(() => {
userRatingRepository = {
@@ -24,7 +24,10 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
getAllByUserId: vi.fn(),
};
service = new RatingUpdateService(userRatingRepository, ratingEventRepository);
service = new RatingUpdateService(
userRatingRepository as unknown as UserRatingRepository,
ratingEventRepository as unknown as RatingEventRepository
);
});
describe('recordRaceRatingEvents - Ledger-based approach', () => {
@@ -150,7 +153,7 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
// Verify DNF penalty event was created
const savedEvents = ratingEventRepository.save.mock.calls.map(call => call[0]);
const hasDnfPenalty = savedEvents.some((event: any) =>
const hasDnfPenalty = savedEvents.some((event: RatingEvent) =>
event.reason.code === 'DRIVING_DNF_PENALTY'
);
expect(hasDnfPenalty).toBe(true);

View File

@@ -0,0 +1,55 @@
/**
* Domain Types: Eligibility
*
* DSL-based eligibility filter and evaluation results.
*/
export interface EligibilityFilterDto {
/**
* DSL expression for eligibility rules
*/
dsl: string;
/**
* Optional context for evaluation
*/
context?: {
userId?: string;
leagueId?: string;
[key: string]: unknown;
};
}
export interface EligibilityCondition {
target: 'platform' | 'external';
dimension?: string;
game?: string;
operator: string;
expected: number | [number, number];
}
export interface ParsedEligibilityFilter {
conditions: EligibilityCondition[];
logicalOperator: 'AND' | 'OR';
}
export interface EvaluationReason {
target: string;
operator: string;
expected: number | [number, number];
actual: number;
failed: boolean;
message?: string;
}
export interface EvaluationResultDto {
eligible: boolean;
reasons: EvaluationReason[];
summary: string;
evaluatedAt: string;
metadata?: {
userId?: string;
filter?: string;
[key: string]: unknown;
};
}

View File

@@ -1,5 +1,6 @@
import { AdminTrustReasonCode } from './AdminTrustReasonCode';
import { AdminTrustReasonCode, type AdminTrustReasonCodeValue } from './AdminTrustReasonCode';
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('AdminTrustReasonCode', () => {
describe('create', () => {
@@ -71,7 +72,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE',
];
voteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(true);
});
});
@@ -82,7 +83,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_REVERSAL_PENALTY',
];
nonVoteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(false);
});
});
@@ -97,7 +98,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
systemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(true);
});
});
@@ -108,7 +109,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE',
];
nonSystemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(false);
});
});
@@ -122,7 +123,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS',
];
positiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(true);
});
});
@@ -134,7 +135,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
nonPositiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(false);
});
});
@@ -148,7 +149,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
negativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(true);
});
});
@@ -160,7 +161,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS',
];
nonNegativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(false);
});
});

View File

@@ -1,5 +1,6 @@
import { DrivingReasonCode } from './DrivingReasonCode';
import { DrivingReasonCode, type DrivingReasonCodeValue } from './DrivingReasonCode';
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('DrivingReasonCode', () => {
describe('create', () => {
@@ -77,7 +78,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN',
];
performanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(true);
});
});
@@ -89,7 +90,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonPerformanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(false);
});
});
@@ -103,7 +104,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PENALTY_INVOLVEMENT_PENALTY',
];
cleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(true);
});
});
@@ -115,7 +116,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonCleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(false);
});
});
@@ -131,7 +132,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
reliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(true);
});
});
@@ -142,7 +143,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_INCIDENTS_PENALTY',
];
nonReliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(false);
});
});
@@ -160,7 +161,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_AFK_PENALTY',
];
penaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(true);
});
});
@@ -172,7 +173,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonPenaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(false);
});
});
@@ -187,7 +188,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN',
];
bonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(true);
});
});
@@ -198,7 +199,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_DNS_PENALTY',
];
nonBonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(false);
});
});

View File

@@ -5,8 +5,10 @@
*/
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { MediaStoragePort } from '../ports/MediaStoragePort';
import { MediaRepository } from '../../domain/repositories/MediaRepository';
export interface DeleteMediaInput {
mediaId: string;

View File

@@ -5,7 +5,9 @@
*/
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { AvatarRepository } from '../../domain/repositories/AvatarRepository';
export interface GetAvatarInput {
driverId: string;

View File

@@ -5,7 +5,9 @@
*/
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { MediaRepository } from '../../domain/repositories/MediaRepository';
export interface GetMediaInput {
mediaId: string;

View File

@@ -4,7 +4,7 @@
* Handles the business logic for requesting avatar generation from a face photo.
*/
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuidv4 } from 'uuid';
@@ -12,6 +12,7 @@ import { AvatarGenerationRequest } from '../../domain/entities/AvatarGenerationR
import type { RacingSuitColor } from '../../domain/types/AvatarGenerationRequest';
import type { AvatarGenerationPort } from '../ports/AvatarGenerationPort';
import type { FaceValidationPort } from '../ports/FaceValidationPort';
import { AvatarGenerationRepository } from '../../domain/repositories/AvatarGenerationRepository';
export interface RequestAvatarGenerationInput {
userId: string;

View File

@@ -5,7 +5,9 @@
*/
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { AvatarGenerationRepository } from '../../domain/repositories/AvatarGenerationRepository';
export interface SelectAvatarInput {
requestId: string;

View File

@@ -4,12 +4,13 @@
* Handles the business logic for updating a driver's avatar.
*/
import { AvatarId } from '@/media/domain/value-objects/AvatarId';
import { AvatarId } from '../../domain/value-objects/AvatarId';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuidv4 } from 'uuid';
import { Avatar } from '../../domain/entities/Avatar';
import { AvatarRepository } from '../../domain/repositories/AvatarRepository';
export interface UpdateAvatarInput {
driverId: string;

View File

@@ -5,10 +5,12 @@
*/
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuidv4 } from 'uuid';
import { Media } from '../../domain/entities/Media';
import type { MediaStoragePort } from '../ports/MediaStoragePort';
import { MediaRepository } from '../../domain/repositories/MediaRepository';
// Define Multer file type locally since @types/multer is not available
export interface MulterFile {

View File

@@ -4,12 +4,15 @@
* Retrieves unread notifications for a recipient.
*/
import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository';
import { NotificationRepository } from '../../domain/repositories/NotificationRepository';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Notification } from '../../domain/entities/Notification';
export interface GetUnreadNotificationsInput {
recipientId: string;
}
export interface GetUnreadNotificationsResult {
notifications: Notification[];

View File

@@ -4,11 +4,15 @@
* Marks a notification as read.
*/
import { NotificationRepository } from '@/notifications/domain/repositories/NotificationRepository';
import { NotificationRepository } from '../../domain/repositories/NotificationRepository';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
export interface MarkNotificationReadCommand {
notificationId: string;
recipientId: string;
}
export interface MarkNotificationReadResult {
notificationId: string;

View File

@@ -4,8 +4,8 @@
* Manages user notification preferences.
*/
import { NotificationPreferenceRepository } from '@/notifications/domain/repositories/NotificationPreferenceRepository';
import { NotificationChannel, NotificationType } from '@/notifications/domain/types/NotificationTypes';
import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository';
import { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

View File

@@ -5,7 +5,7 @@
* based on their preferences.
*/
import { NotificationChannel, NotificationType } from '@/notifications/domain/types/NotificationTypes';
import { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -13,6 +13,8 @@ import { v4 as uuid } from 'uuid';
import type { NotificationData } from '../../domain/entities/Notification';
import { Notification } from '../../domain/entities/Notification';
import type { NotificationDeliveryResult, NotificationGatewayRegistry } from '../ports/NotificationGateway';
import { NotificationRepository } from '../../domain/repositories/NotificationRepository';
import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository';
export interface SendNotificationCommand {
recipientId: string;
@@ -93,7 +95,7 @@ export class SendNotificationUseCase {
// Check quiet hours (skip external channels during quiet hours)
const effectiveChannels = preferences.isInQuietHours()
? channels.filter(ch => ch === 'in_app')
? channels.filter((ch: NotificationChannel) => ch === 'in_app')
: channels;
// Ensure at least in_app is used

View File

@@ -1,5 +1,4 @@
import type { DomainError } from '@core/shared/errors/DomainError';
import type { CommonDomainErrorKind } from '@core/shared/errors/DomainError';
import type { DomainError, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
/**
* Domain Error: NotificationDomainError

View File

@@ -1,28 +0,0 @@
import { describe, expect, it } from 'vitest';
import * as useCases from './index';
describe('payments use-cases barrel exports', () => {
it('re-exports all expected use cases', () => {
const exported = useCases as unknown as Record<string, unknown>;
const expectedExports = [
'AwardPrizeUseCase',
'CreatePaymentUseCase',
'CreatePrizeUseCase',
'DeletePrizeUseCase',
'GetMembershipFeesUseCase',
'GetPaymentsUseCase',
'GetPrizesUseCase',
'GetSponsorBillingUseCase',
'GetWalletUseCase',
'ProcessWalletTransactionUseCase',
'UpdateMemberPaymentUseCase',
'UpdatePaymentStatusUseCase',
'UpsertMembershipFeeUseCase',
];
for (const name of expectedExports) {
expect(exported[name], `missing export: ${name}`).toBeDefined();
}
});
});

View File

@@ -1,7 +0,0 @@
import * as mod from '@core/payments/domain/entities/index';
describe('payments/domain/entities/index.ts', () => {
it('imports', () => {
expect(mod).toBeTruthy();
});
});

View File

@@ -1,5 +1,4 @@
import type { ApplicationError } from '@core/shared/errors/ApplicationError';
import type { CommonApplicationErrorKind } from '@core/shared/errors/ApplicationError';
import type { ApplicationError, CommonApplicationErrorKind } from '@core/shared/errors/ApplicationError';
export abstract class RacingApplicationError
extends Error

View File

@@ -2,15 +2,16 @@
* Tests for GetTeamRatingLedgerQuery
*/
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { GetTeamRatingLedgerQuery, GetTeamRatingLedgerQueryHandler } from './GetTeamRatingLedgerQuery';
import { TeamRatingEvent } from '../../domain/entities/TeamRatingEvent';
import { TeamRatingEventId } from '../../domain/value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '../../domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '../../domain/value-objects/TeamRatingDelta';
import type { TeamRatingEventRepository } from '../../domain/repositories/TeamRatingEventRepository';
describe('GetTeamRatingLedgerQuery', () => {
let mockRatingEventRepo: any;
let mockRatingEventRepo: { findEventsPaginated: Mock };
let handler: GetTeamRatingLedgerQueryHandler;
beforeEach(() => {
@@ -18,7 +19,7 @@ describe('GetTeamRatingLedgerQuery', () => {
findEventsPaginated: vi.fn(),
};
handler = new GetTeamRatingLedgerQueryHandler(mockRatingEventRepo);
handler = new GetTeamRatingLedgerQueryHandler(mockRatingEventRepo as unknown as TeamRatingEventRepository);
});
describe('execute', () => {

View File

@@ -2,7 +2,7 @@
* Tests for GetTeamRatingsSummaryQuery
*/
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { GetTeamRatingsSummaryQuery, GetTeamRatingsSummaryQueryHandler } from './GetTeamRatingsSummaryQuery';
import { TeamRatingSnapshot } from '../../domain/services/TeamRatingSnapshotCalculator';
import { TeamRatingValue } from '../../domain/value-objects/TeamRatingValue';
@@ -10,10 +10,12 @@ import { TeamRatingEvent } from '../../domain/entities/TeamRatingEvent';
import { TeamRatingEventId } from '../../domain/value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '../../domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '../../domain/value-objects/TeamRatingDelta';
import type { TeamRatingRepository } from '../../domain/repositories/TeamRatingRepository';
import type { TeamRatingEventRepository } from '../../domain/repositories/TeamRatingEventRepository';
describe('GetTeamRatingsSummaryQuery', () => {
let mockTeamRatingRepo: any;
let mockRatingEventRepo: any;
let mockTeamRatingRepo: { findByTeamId: Mock };
let mockRatingEventRepo: { getAllByTeamId: Mock };
let handler: GetTeamRatingsSummaryQueryHandler;
beforeEach(() => {
@@ -25,8 +27,8 @@ describe('GetTeamRatingsSummaryQuery', () => {
};
handler = new GetTeamRatingsSummaryQueryHandler(
mockTeamRatingRepo,
mockRatingEventRepo
mockTeamRatingRepo as unknown as TeamRatingRepository,
mockRatingEventRepo as unknown as TeamRatingEventRepository
);
});

View File

@@ -6,9 +6,15 @@
*/
import type { NotificationService } from '@core/notifications/application/ports/NotificationService';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship';
import { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import { SeasonSponsorshipRepository } from '../../domain/repositories/SeasonSponsorshipRepository';
import { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { WalletRepository } from '@core/payments/domain/repositories/WalletRepository';
import { LeagueWalletRepository } from '../../domain/repositories/LeagueWalletRepository';
export interface AcceptSponsorshipRequestInput {
requestId: string;

View File

@@ -5,6 +5,8 @@ import { TeamRatingEvent } from '@core/racing/domain/entities/TeamRatingEvent';
import { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '@core/racing/domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '@core/racing/domain/value-objects/TeamRatingDelta';
import { TeamRating } from '../../domain/entities/TeamRating';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
// Mock repositories
class MockTeamRatingEventRepository implements TeamRatingEventRepository {
@@ -27,7 +29,7 @@ class MockTeamRatingEventRepository implements TeamRatingEventRepository {
return this.events.filter(e => e.teamId === teamId);
}
async findEventsPaginated(teamId: string): Promise<any> {
async findEventsPaginated(teamId: string): Promise<PaginatedResult<TeamRatingEvent>> {
const events = await this.getAllByTeamId(teamId);
return {
items: events,
@@ -44,13 +46,13 @@ class MockTeamRatingEventRepository implements TeamRatingEventRepository {
}
class MockTeamRatingRepository implements TeamRatingRepository {
private snapshots: Map<string, any> = new Map();
private snapshots: Map<string, TeamRating> = new Map();
async findByTeamId(teamId: string): Promise<any | null> {
async findByTeamId(teamId: string): Promise<TeamRating | null> {
return this.snapshots.get(teamId) || null;
}
async save(snapshot: any): Promise<any> {
async save(snapshot: TeamRating): Promise<TeamRating> {
this.snapshots.set(snapshot.teamId, snapshot);
return snapshot;
}

View File

@@ -5,11 +5,14 @@
* (driver, team, race, or season/league).
*/
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
import { Money, isCurrency } from '../../domain/value-objects/Money';
import { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import { SponsorRepository } from '../../domain/repositories/SponsorRepository';
export interface ApplyForSponsorshipInput {
sponsorId: string;

View File

@@ -1,5 +1,10 @@
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { ApplyPenaltyUseCase } from './ApplyPenaltyUseCase';
import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('ApplyPenaltyUseCase', () => {
let mockPenaltyRepo: {
@@ -43,11 +48,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when race does not exist', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue(null);
@@ -65,11 +72,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when steward does not have authority', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -95,11 +104,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when protest does not exist', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -127,11 +138,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when protest is not upheld', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -159,11 +172,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when protest is not for this race', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -191,11 +206,13 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should create penalty and return result on success', async () => {
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
const useCase = new ApplyPenaltyUseCase(
mockPenaltyRepo as unknown as PenaltyRepository,
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository,
mockLogger as unknown as Logger
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -223,7 +240,7 @@ describe('ApplyPenaltyUseCase', () => {
expect(presented.penaltyId).toBeDefined();
expect(mockPenaltyRepo.create).toHaveBeenCalledTimes(1);
const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as any;
const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as Penalty;
type ToStringable = { toString(): string };
const asString = (value: unknown): string => {

View File

@@ -5,11 +5,15 @@
* The penalty can be standalone or linked to an upheld protest.
*/
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto';
import { Penalty } from '../../domain/entities/penalty/Penalty';
import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
export interface ApplyPenaltyInput {
raceId: string;
@@ -62,7 +66,7 @@ export class ApplyPenaltyUseCase {
// Validate steward has authority (owner or admin of the league)
const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId);
const stewardMembership = memberships.find(
m => m.driverId.toString() === command.stewardId && m.status.toString() === 'active'
(m) => m.driverId.toString() === command.stewardId && m.status.toString() === 'active'
);
if (!stewardMembership || (stewardMembership.role.toString() !== 'owner' && stewardMembership.role.toString() !== 'admin')) {
@@ -110,7 +114,7 @@ export class ApplyPenaltyUseCase {
`ApplyPenaltyUseCase: Successfully applied penalty ${penalty.id} for driver ${command.driverId} in race ${command.raceId}.`,
);
const result: ApplyPenaltyResult = { penaltyId: penalty.id };
const result: ApplyPenaltyResult = { penaltyId: penalty.id.toString() };
return Result.ok(result);
}

View File

@@ -3,11 +3,7 @@ import type { LeagueRepository } from '../../domain/repositories/LeagueRepositor
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto';
import { JoinedAt } from '../../domain/value-objects/JoinedAt';
import { LeagueId } from '../../domain/entities/LeagueId';
import { DriverId } from '../../domain/entities/DriverId';
import { MembershipRole } from '../../domain/entities/MembershipRole';
import { MembershipStatus } from '../../domain/entities/MembershipStatus';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
export interface ApproveLeagueJoinRequestInput {
leagueId: string;
@@ -55,14 +51,16 @@ export class ApproveLeagueJoinRequestUseCase {
}
await this.leagueMembershipRepository.removeJoinRequest(input.joinRequestId);
await this.leagueMembershipRepository.saveMembership({
id: randomUUID(),
leagueId: LeagueId.create(input.leagueId),
driverId: DriverId.create(request.driverId.toString()),
role: MembershipRole.create('member'),
status: MembershipStatus.create('active'),
joinedAt: JoinedAt.create(new Date()),
});
await this.leagueMembershipRepository.saveMembership(
LeagueMembership.create({
id: randomUUID(),
leagueId: input.leagueId,
driverId: request.driverId.toString(),
role: 'member',
status: 'active',
joinedAt: new Date(),
})
);
const result: ApproveLeagueJoinRequestResult = { success: true, message: 'Join request approved.' };

View File

@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType';
import { CancelRaceUseCase } from './CancelRaceUseCase';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('CancelRaceUseCase', () => {
let useCase: CancelRaceUseCase;
@@ -27,8 +29,8 @@ describe('CancelRaceUseCase', () => {
info: vi.fn(),
error: vi.fn(),
};
useCase = new CancelRaceUseCase(raceRepository as any,
logger as any);
useCase = new CancelRaceUseCase(raceRepository as unknown as RaceRepository,
logger as unknown as Logger);
});
it('should cancel race successfully', async () => {

View File

@@ -1,6 +1,8 @@
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
export type CancelRaceInput = {
raceId: string;

View File

@@ -3,6 +3,11 @@ import { RaceEvent } from '../../domain/entities/RaceEvent';
import { Session } from '../../domain/entities/Session';
import { SessionType } from '../../domain/value-objects/SessionType';
import { CloseRaceEventStewardingUseCase } from './CloseRaceEventStewardingUseCase';
import { RaceEventRepository } from '../../domain/repositories/RaceEventRepository';
import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import { DomainEventPublisher } from '@core/shared/domain/DomainEvent';
import type { Logger } from '@core/shared/domain/Logger';
describe('CloseRaceEventStewardingUseCase', () => {
let useCase: CloseRaceEventStewardingUseCase;
@@ -42,11 +47,11 @@ describe('CloseRaceEventStewardingUseCase', () => {
logger = {
error: vi.fn(),
};
useCase = new CloseRaceEventStewardingUseCase(logger as any,
raceEventRepository as any,
raceRegistrationRepository as any,
penaltyRepository as any,
domainEventPublisher as any);
useCase = new CloseRaceEventStewardingUseCase(logger as unknown as Logger,
raceEventRepository as unknown as RaceEventRepository,
raceRegistrationRepository as unknown as RaceRegistrationRepository,
penaltyRepository as unknown as PenaltyRepository,
domainEventPublisher as unknown as DomainEventPublisher);
});
it('should close stewarding for expired events successfully', async () => {

View File

@@ -1,9 +1,12 @@
import { DomainEventPublisher } from '@/shared/domain/DomainEvent';
import { DomainEventPublisher } from '@core/shared/domain/DomainEvent';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceEvent } from '../../domain/entities/RaceEvent';
import { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed';
import { RaceEventRepository } from '../../domain/repositories/RaceEventRepository';
import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
export type CloseRaceEventStewardingInput = {
raceId: string;

View File

@@ -2,7 +2,7 @@ import type { DriverRepository } from '../../domain/repositories/DriverRepositor
import { Driver } from '../../domain/entities/Driver';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application/Logger';
import type { Logger } from '@core/shared/domain/Logger';
export interface CompleteDriverOnboardingInput {
userId: string;

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CompleteRaceUseCase, type CompleteRaceInput, type CompleteRaceResult } from './CompleteRaceUseCase';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { CompleteRaceUseCase, type CompleteRaceInput } from './CompleteRaceUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
@@ -39,10 +39,10 @@ describe('CompleteRaceUseCase', () => {
save: vi.fn(),
};
getDriverRating = vi.fn();
useCase = new CompleteRaceUseCase(raceRepository as any,
raceRegistrationRepository as any,
resultRepository as any,
standingRepository as any,
useCase = new CompleteRaceUseCase(raceRepository as unknown as RaceRepository,
raceRegistrationRepository as unknown as RaceRegistrationRepository,
resultRepository as unknown as ResultRepository,
standingRepository as unknown as StandingRepository,
getDriverRating);
});

View File

@@ -4,7 +4,16 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
import { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season';
import { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
export interface CreateLeagueSeasonScheduleRaceInput {
leagueId: string;
seasonId: string;
track: string;
car: string;
scheduledAt: Date;
}
export type CreateLeagueSeasonScheduleRaceResult = {
raceId: string;
@@ -88,6 +97,9 @@ export class CreateLeagueSeasonScheduleRaceUseCase {
}
private isWithinSeasonWindow(season: Season, scheduledAt: Date): boolean {
if (!season.startDate || !season.endDate) {
return true;
}
return scheduledAt >= season.startDate && scheduledAt <= season.endDate;
}
}

View File

@@ -1,4 +1,4 @@
import { League } from '@/racing/domain/entities/League';
import { League } from '../../domain/entities/League';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -13,6 +13,9 @@ import {
MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility';
import { PointsTable } from '../../domain/value-objects/PointsTable';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository';
export type CreateLeagueWithSeasonAndScoringCommand = {
name: string;

View File

@@ -1,15 +1,12 @@
import { describe, it, expect, vi, Mock, beforeEach } from 'vitest';
import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest';
import { Season } from '@core/racing/domain/entities/season/Season';
import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
import {
CreateSeasonForLeagueUseCase,
type CreateSeasonForLeagueInput,
type CreateSeasonForLeagueResult,
type LeagueConfigFormModel,
} from '@core/racing/application/use-cases/CreateSeasonForLeagueUseCase';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result } from '@core/shared/domain/Result';
function createLeagueConfigFormModel(overrides?: Partial<LeagueConfigFormModel>): LeagueConfigFormModel {
return {
@@ -68,13 +65,18 @@ function createLeagueConfigFormModel(overrides?: Partial<LeagueConfigFormModel>)
};
}
type CreateSeasonErrorCode = ApplicationErrorCode<'LEAGUE_NOT_FOUND' | 'VALIDATION_ERROR' | 'REPOSITORY_ERROR'> & {
details?: { message: string };
};
describe('CreateSeasonForLeagueUseCase', () => {
const mockLeagueFindById = vi.fn();
const mockLeagueRepo: any = {
const mockLeagueRepo: {
findById: Mock;
findAll: Mock;
findByOwnerId: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
searchByName: Mock;
} = {
findById: mockLeagueFindById,
findAll: vi.fn(),
findByOwnerId: vi.fn(),
@@ -87,7 +89,15 @@ describe('CreateSeasonForLeagueUseCase', () => {
const mockSeasonFindById = vi.fn();
const mockSeasonAdd = vi.fn();
const mockSeasonRepo: any = {
const mockSeasonRepo: {
findById: Mock;
findByLeagueId: Mock;
create: Mock;
add: Mock;
update: Mock;
listByLeague: Mock;
listActiveByLeague: Mock;
} = {
findById: mockSeasonFindById,
findByLeagueId: vi.fn(),
create: vi.fn(),
@@ -105,7 +115,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockLeagueFindById.mockResolvedValue({ id: 'league-1' });
mockSeasonAdd.mockResolvedValue(undefined);
const useCase = new CreateSeasonForLeagueUseCase(mockLeagueRepo, mockSeasonRepo);
const useCase = new CreateSeasonForLeagueUseCase(
mockLeagueRepo as unknown as LeagueRepository,
mockSeasonRepo as unknown as SeasonRepository
);
const config = createLeagueConfigFormModel({
basics: {
@@ -157,7 +170,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockSeasonFindById.mockResolvedValue(sourceSeason);
mockSeasonAdd.mockResolvedValue(undefined);
const useCase = new CreateSeasonForLeagueUseCase(mockLeagueRepo, mockSeasonRepo);
const useCase = new CreateSeasonForLeagueUseCase(
mockLeagueRepo as unknown as LeagueRepository,
mockSeasonRepo as unknown as SeasonRepository
);
const command: CreateSeasonForLeagueInput = {
leagueId: 'league-1',
@@ -177,7 +193,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
it('returns error when league not found', async () => {
mockLeagueFindById.mockResolvedValue(null);
const useCase = new CreateSeasonForLeagueUseCase(mockLeagueRepo, mockSeasonRepo);
const useCase = new CreateSeasonForLeagueUseCase(
mockLeagueRepo as unknown as LeagueRepository,
mockSeasonRepo as unknown as SeasonRepository
);
const command: CreateSeasonForLeagueInput = {
leagueId: 'missing-league',
@@ -197,7 +216,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockLeagueFindById.mockResolvedValue({ id: 'league-1' });
mockSeasonFindById.mockResolvedValue(undefined);
const useCase = new CreateSeasonForLeagueUseCase(mockLeagueRepo, mockSeasonRepo);
const useCase = new CreateSeasonForLeagueUseCase(
mockLeagueRepo as unknown as LeagueRepository,
mockSeasonRepo as unknown as SeasonRepository
);
const command: CreateSeasonForLeagueInput = {
leagueId: 'league-1',

View File

@@ -3,11 +3,12 @@
*
* Creates a new sponsor.
*/
import { ApplicationErrorCode } from '@/shared/errors/ApplicationErrorCode';
import { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import { v4 as uuidv4 } from 'uuid';
import { Sponsor } from '../../domain/entities/sponsor/Sponsor';
import { SponsorRepository } from '../../domain/repositories/SponsorRepository';
export interface CreateSponsorInput {
name: string;

View File

@@ -3,6 +3,9 @@ import {
CreateTeamUseCase,
type CreateTeamInput
} from './CreateTeamUseCase';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('CreateTeamUseCase', () => {
let useCase: CreateTeamUseCase;
@@ -34,9 +37,9 @@ describe('CreateTeamUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
useCase = new CreateTeamUseCase(teamRepository as any,
membershipRepository as any,
logger as any);
useCase = new CreateTeamUseCase(teamRepository as unknown as TeamRepository,
membershipRepository as unknown as TeamMembershipRepository,
logger as unknown as Logger);
});
it('should create team successfully', async () => {

View File

@@ -3,7 +3,7 @@
*
* Creates a new team.
*/
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuidv4 } from 'uuid';
@@ -13,6 +13,9 @@ import type {
TeamMembershipStatus,
TeamRole,
} from '../../domain/types/TeamMembership';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
export interface CreateTeamInput {
name: string;
tag: string;

View File

@@ -1,8 +1,14 @@
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
export interface DeleteLeagueSeasonScheduleRaceInput {
leagueId: string;
seasonId: string;
raceId: string;
}
export type DeleteLeagueSeasonScheduleRaceResult = {
success: true;

View File

@@ -3,7 +3,8 @@ import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import { FileProtestUseCase, type FileProtestErrorCode, type FileProtestInput, type FileProtestResult } from './FileProtestUseCase';
import { FileProtestUseCase, type FileProtestErrorCode, type FileProtestInput } from './FileProtestUseCase';
import { Protest } from '../../domain/entities/Protest';
describe('FileProtestUseCase', () => {
let mockProtestRepo: {
@@ -29,9 +30,11 @@ describe('FileProtestUseCase', () => {
});
it('should return error when race does not exist', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
const useCase = new FileProtestUseCase(
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue(null);
@@ -49,9 +52,11 @@ describe('FileProtestUseCase', () => {
});
it('should return error when protesting against self', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
const useCase = new FileProtestUseCase(
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -69,9 +74,11 @@ describe('FileProtestUseCase', () => {
});
it('should return error when protesting driver is not an active member', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
const useCase = new FileProtestUseCase(
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -92,9 +99,11 @@ describe('FileProtestUseCase', () => {
});
it('should create protest and return protestId on success', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
const useCase = new FileProtestUseCase(
mockProtestRepo as unknown as ProtestRepository,
mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -114,7 +123,7 @@ describe('FileProtestUseCase', () => {
expect(result.isOk()).toBe(true);
const presented = result.unwrap();
expect(mockProtestRepo.create).toHaveBeenCalledTimes(1);
const created = (mockProtestRepo.create as unknown as Mock).mock.calls[0]?.[0] as any;
const created = (mockProtestRepo.create as unknown as Mock).mock.calls[0]?.[0] as Protest;
expect(created.raceId.toString()).toBe('race1');
expect(created.protestingDriverId.toString()).toBe('driver1');

View File

@@ -27,11 +27,11 @@ describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => {
it('should return enriched leagues with capacity and scoring', async () => {
const useCase = new GetAllLeaguesWithCapacityAndScoringUseCase(
mockLeagueRepo as any,
mockMembershipRepo as any,
mockSeasonRepo as any,
mockScoringConfigRepo as any,
mockGameRepo as any,
mockLeagueRepo as unknown as LeagueRepository,
mockMembershipRepo as unknown as LeagueMembershipRepository,
mockSeasonRepo as unknown as SeasonRepository,
mockScoringConfigRepo as unknown as LeagueScoringConfigRepository,
mockGameRepo as unknown as GameRepository,
{ getPresetById: vi.fn().mockReturnValue({ id: 'preset1', name: 'Default' }) }
);

View File

@@ -1,5 +1,5 @@
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { League } from '../../domain/entities/League';
import { Race } from '../../domain/entities/Race';
import {
@@ -7,9 +7,24 @@ import {
type GetAllRacesPageDataInput
} from './GetAllRacesPageDataUseCase';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
describe('GetAllRacesPageDataUseCase', () => {
const mockRaceFindAll = vi.fn();
const mockRaceRepo: any = {
const mockRaceRepo: {
findById: Mock;
findAll: Mock;
findByLeagueId: Mock;
findUpcomingByLeagueId: Mock;
findCompletedByLeagueId: Mock;
findByStatus: Mock;
findByDateRange: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
} = {
findById: vi.fn(),
findAll: mockRaceFindAll,
findByLeagueId: vi.fn(),
@@ -24,7 +39,16 @@ describe('GetAllRacesPageDataUseCase', () => {
};
const mockLeagueFindAll = vi.fn();
const mockLeagueRepo: any = {
const mockLeagueRepo: {
findById: Mock;
findAll: Mock;
findByOwnerId: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
searchByName: Mock;
} = {
findById: vi.fn(),
findAll: mockLeagueFindAll,
findByOwnerId: vi.fn(),
@@ -47,9 +71,11 @@ describe('GetAllRacesPageDataUseCase', () => {
});
it('should return races and filters data', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
const useCase = new GetAllRacesPageDataUseCase(
mockRaceRepo as unknown as RaceRepository,
mockLeagueRepo as unknown as LeagueRepository,
mockLogger
);
const race1 = Race.create({
id: 'race1',
@@ -132,9 +158,11 @@ describe('GetAllRacesPageDataUseCase', () => {
});
it('should return empty result when no races or leagues', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
const useCase = new GetAllRacesPageDataUseCase(
mockRaceRepo as unknown as RaceRepository,
mockLeagueRepo as unknown as LeagueRepository,
mockLogger
);
mockRaceFindAll.mockResolvedValue([]);
mockLeagueFindAll.mockResolvedValue([]);
@@ -157,9 +185,11 @@ describe('GetAllRacesPageDataUseCase', () => {
});
it('should return error when repository throws', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
const useCase = new GetAllRacesPageDataUseCase(
mockRaceRepo as unknown as RaceRepository,
mockLeagueRepo as unknown as LeagueRepository,
mockLogger
);
const error = new Error('Repository error');
mockRaceFindAll.mockRejectedValue(error);

View File

@@ -1,6 +1,10 @@
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceStatusValue } from '../../domain/entities/Race';
import type { Race, RaceStatusValue } from '../../domain/entities/Race';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { League } from '../../domain/entities/League';
export type GetAllRacesPageDataInput = {};
@@ -46,12 +50,12 @@ export class GetAllRacesPageDataUseCase {
]);
this.logger.info(`Found ${allRaces.length} races and ${allLeagues.length} leagues.`);
const leagueMap = new Map(allLeagues.map(league => [league.id.toString(), league.name.toString()]));
const leagueMap = new Map(allLeagues.map((league: League) => [league.id.toString(), league.name.toString()]));
const races: GetAllRacesPageRaceItem[] = allRaces
.slice()
.sort((a, b) => b.scheduledAt.getTime() - a.scheduledAt.getTime())
.map(race => ({
.sort((a: Race, b: Race) => b.scheduledAt.getTime() - a.scheduledAt.getTime())
.map((race: Race) => ({
id: race.id,
track: race.track,
car: race.car,

View File

@@ -2,6 +2,9 @@ import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/domain/Logger';
export type GetAllRacesInput = {};

View File

@@ -1,7 +1,10 @@
import { Team } from '@/racing/domain/entities/Team';
import { Team } from '../../domain/entities/Team';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { TeamStatsRepository } from '../../domain/repositories/TeamStatsRepository';
export interface GetAllTeamsInput {}
@@ -39,6 +42,7 @@ export class GetAllTeamsUseCase {
async execute(
_input: GetAllTeamsInput,
): Promise<Result<GetAllTeamsResult, ApplicationErrorCode<GetAllTeamsErrorCode, { message: string }>>> {
void _input;
this.logger.debug('GetAllTeamsUseCase: Fetching all teams');
try {
@@ -71,7 +75,7 @@ export class GetAllTeamsUseCase {
rating: stats?.rating ?? 0,
logoUrl: logoUrl ?? null,
description: team.description.toString(),
leagues: team.leagues.map(l => l.toString()),
leagues: team.leagues.map((l) => l.toString()),
isRecruiting: team.isRecruiting,
});
}

View File

@@ -4,11 +4,12 @@
* Retrieves all liveries for a specific driver.
*/
import { DriverLivery } from '@/racing/domain/entities/DriverLivery';
import { DriverLivery } from '../../domain/entities/DriverLivery';
import { UseCase } from '@core/shared/application/UseCase';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { LiveryRepository } from '../../domain/repositories/LiveryRepository';
export interface GetDriverLiveriesInput {
driverId: string;

View File

@@ -1,8 +1,10 @@
import { TeamMembership } from '@/racing/domain/types/TeamMembership';
import { TeamMembership } from '../../domain/types/TeamMembership';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Team } from '../../domain/entities/Team';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
export interface GetDriverTeamInput {
driverId: string;

View File

@@ -5,7 +5,7 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
import type { Driver } from '../../domain/entities/Driver';
import type { Team } from '../../domain/entities/Team';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { DriverStatsUseCase } from './DriverStatsUseCase';
import type { DriverStats, DriverStatsUseCase } from './DriverStatsUseCase';
import type { RankingUseCase } from './RankingUseCase';
import { SkillLevelService, type SkillLevel } from '../../domain/services/SkillLevelService';
import { MediaReference } from '@core/domain/media/MediaReference';
@@ -71,10 +71,11 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
this.driverStatsUseCase.getDriverStats(driver.id)
);
const statsResults = await Promise.all(statsPromises);
const statsMap = new Map<string, any>();
const statsMap = new Map<string, DriverStats>();
drivers.forEach((driver, idx) => {
if (statsResults[idx]) {
statsMap.set(driver.id, statsResults[idx]);
const stats = statsResults[idx];
if (stats) {
statsMap.set(driver.id, stats);
}
});

View File

@@ -8,6 +8,8 @@
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest';
import { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import type { Logger } from '@core/shared/domain/Logger';
export type SponsorshipEntityType = SponsorableEntityType;

View File

@@ -2,7 +2,13 @@ import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
export interface GetLeagueAdminPermissionsInput {
leagueId: string;
performerDriverId: string;
}
export type LeagueAdminPermissions = {
canManageSchedule: boolean;

View File

@@ -1,15 +1,23 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueAdminUseCase,
type GetLeagueAdminInput,
type GetLeagueAdminResult,
type GetLeagueAdminErrorCode,
} from './GetLeagueAdminUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueAdminUseCase', () => {
let mockLeagueRepo: any;
let mockLeagueRepo: {
findById: Mock;
findAll: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
findByOwnerId: Mock;
searchByName: Mock;
};
let mockFindById: Mock;
beforeEach(() => {
@@ -26,7 +34,7 @@ describe('GetLeagueAdminUseCase', () => {
};
});
const createUseCase = () => new GetLeagueAdminUseCase(mockLeagueRepo);
const createUseCase = () => new GetLeagueAdminUseCase(mockLeagueRepo as unknown as LeagueRepository);
const params: GetLeagueAdminInput = {
leagueId: 'league1',

View File

@@ -1,9 +1,8 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueDriverSeasonStatsUseCase,
type GetLeagueDriverSeasonStatsErrorCode,
type GetLeagueDriverSeasonStatsInput,
type GetLeagueDriverSeasonStatsResult,
} from './GetLeagueDriverSeasonStatsUseCase';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
@@ -22,12 +21,70 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const mockDriverFindById = vi.fn();
let useCase: GetLeagueDriverSeasonStatsUseCase;
let standingRepository: any;
let resultRepository: any;
let penaltyRepository: any;
let raceRepository: any;
let driverRepository: any;
let driverRatingPort: any;
let standingRepository: {
findByLeagueId: Mock;
findByDriverIdAndLeagueId: Mock;
findAll: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
deleteByLeagueId: Mock;
exists: Mock;
recalculate: Mock;
};
let resultRepository: {
findById: Mock;
findAll: Mock;
findByRaceId: Mock;
findByDriverId: Mock;
findByDriverIdAndLeagueId: Mock;
create: Mock;
createMany: Mock;
update: Mock;
delete: Mock;
deleteByRaceId: Mock;
exists: Mock;
existsByRaceId: Mock;
};
let penaltyRepository: {
findById: Mock;
findByDriverId: Mock;
findByProtestId: Mock;
findPending: Mock;
findByRaceId: Mock;
findIssuedBy: Mock;
create: Mock;
update: Mock;
exists: Mock;
};
let raceRepository: {
findById: Mock;
findAll: Mock;
findByLeagueId: Mock;
findUpcomingByLeagueId: Mock;
findCompletedByLeagueId: Mock;
findByStatus: Mock;
findByDateRange: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
};
let driverRepository: {
findById: Mock;
findByIRacingId: Mock;
findAll: Mock;
create: Mock;
update: Mock;
delete: Mock;
exists: Mock;
existsByIRacingId: Mock;
};
let driverRatingPort: {
getDriverRating: Mock;
calculateRatingChange: Mock;
updateDriverRating: Mock;
};
beforeEach(() => {
mockStandingFindByLeagueId.mockReset();
@@ -102,12 +159,14 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
updateDriverRating: vi.fn(),
};
useCase = new GetLeagueDriverSeasonStatsUseCase(standingRepository,
resultRepository,
penaltyRepository,
raceRepository,
driverRepository,
driverRatingPort);
useCase = new GetLeagueDriverSeasonStatsUseCase(
standingRepository as unknown as StandingRepository,
resultRepository as unknown as ResultRepository,
penaltyRepository as unknown as PenaltyRepository,
raceRepository as unknown as RaceRepository,
driverRepository as unknown as DriverRepository,
driverRatingPort as unknown as DriverRatingPort
);
});
it('should return league driver season stats for given league id', async () => {

View File

@@ -1,8 +1,7 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import {
GetLeagueFullConfigUseCase,
type GetLeagueFullConfigInput,
type GetLeagueFullConfigResult,
type GetLeagueFullConfigErrorCode,
} from './GetLeagueFullConfigUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
@@ -13,10 +12,10 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
describe('GetLeagueFullConfigUseCase', () => {
let useCase: GetLeagueFullConfigUseCase;
let leagueRepository: any;
let seasonRepository: any;
let leagueScoringConfigRepository: any;
let gameRepository: any;
let leagueRepository: { findById: Mock };
let seasonRepository: { findByLeagueId: Mock };
let leagueScoringConfigRepository: { findBySeasonId: Mock };
let gameRepository: { findById: Mock };
beforeEach(() => {
leagueRepository = {
@@ -32,10 +31,12 @@ describe('GetLeagueFullConfigUseCase', () => {
findById: vi.fn(),
};
useCase = new GetLeagueFullConfigUseCase(leagueRepository,
seasonRepository,
leagueScoringConfigRepository,
gameRepository);
useCase = new GetLeagueFullConfigUseCase(
leagueRepository as unknown as LeagueRepository,
seasonRepository as unknown as SeasonRepository,
leagueScoringConfigRepository as unknown as LeagueScoringConfigRepository,
gameRepository as unknown as GameRepository
);
});
it('should return league config when league exists', async () => {

View File

@@ -1,14 +1,16 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueMembershipsUseCase,
type GetLeagueMembershipsInput,
type GetLeagueMembershipsResult,
type GetLeagueMembershipsErrorCode,
} from './GetLeagueMembershipsUseCase';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
describe('GetLeagueMembershipsUseCase', () => {
let useCase: GetLeagueMembershipsUseCase;
@@ -32,9 +34,11 @@ describe('GetLeagueMembershipsUseCase', () => {
leagueRepository = {
findById: vi.fn(),
};
useCase = new GetLeagueMembershipsUseCase(leagueMembershipRepository as any,
driverRepository as any,
leagueRepository as any);
useCase = new GetLeagueMembershipsUseCase(
leagueMembershipRepository as unknown as LeagueMembershipRepository,
driverRepository as unknown as DriverRepository,
leagueRepository as unknown as LeagueRepository
);
});
it('should return league memberships with drivers', async () => {

View File

@@ -1,13 +1,16 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueOwnerSummaryUseCase,
type GetLeagueOwnerSummaryInput,
type GetLeagueOwnerSummaryResult,
type GetLeagueOwnerSummaryErrorCode,
} from './GetLeagueOwnerSummaryUseCase';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
describe('GetLeagueOwnerSummaryUseCase', () => {
let useCase: GetLeagueOwnerSummaryUseCase;
@@ -40,10 +43,10 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
findByLeagueId: vi.fn(),
};
useCase = new GetLeagueOwnerSummaryUseCase(
leagueRepository as any,
driverRepository as any,
leagueMembershipRepository as any,
standingRepository as any
leagueRepository as unknown as LeagueRepository,
driverRepository as unknown as DriverRepository,
leagueMembershipRepository as unknown as LeagueMembershipRepository,
standingRepository as unknown as StandingRepository
);
});

View File

@@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueScheduleUseCase,
type GetLeagueScheduleInput,
type GetLeagueScheduleResult,
type GetLeagueScheduleErrorCode,
} from './GetLeagueScheduleUseCase';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -10,6 +9,8 @@ import type { League } from '../../domain/entities/League';
import { Race } from '../../domain/entities/Race';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
describe('GetLeagueScheduleUseCase', () => {
let useCase: GetLeagueScheduleUseCase;
@@ -42,7 +43,7 @@ describe('GetLeagueScheduleUseCase', () => {
error: vi.fn(),
} as unknown as Logger;
useCase = new GetLeagueScheduleUseCase(leagueRepository as unknown as LeagueRepository,
seasonRepository as any,
seasonRepository as unknown as SeasonRepository,
raceRepository as unknown as RaceRepository,
logger);
});
@@ -109,7 +110,7 @@ describe('GetLeagueScheduleUseCase', () => {
raceRepository.findByLeagueId.mockResolvedValue([janRace, febRace]);
// Season 1 covers January
const resultSeason1 = await useCase.execute({ leagueId, seasonId: 'season-jan' } as any);
const resultSeason1 = await useCase.execute({ leagueId, seasonId: 'season-jan' });
expect(resultSeason1.isOk()).toBe(true);
const resultValue1 = resultSeason1.unwrap();
expect(resultValue1.seasonId).toBe('season-jan');
@@ -117,7 +118,7 @@ describe('GetLeagueScheduleUseCase', () => {
expect(resultValue1.races.map(r => r.race.id)).toEqual(['race-jan']);
// Season 2 covers February
const resultSeason2 = await useCase.execute({ leagueId, seasonId: 'season-feb' } as any);
const resultSeason2 = await useCase.execute({ leagueId, seasonId: 'season-feb' });
expect(resultSeason2.isOk()).toBe(true);
const resultValue2 = resultSeason2.unwrap();
expect(resultValue2.seasonId).toBe('season-feb');

View File

@@ -1,10 +1,13 @@
import { SeasonScheduleGenerator } from '@/racing/domain/services/SeasonScheduleGenerator';
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
export type GetLeagueScheduleErrorCode =
| 'LEAGUE_NOT_FOUND'
@@ -51,7 +54,7 @@ export class GetLeagueScheduleUseCase {
}
const seasons = await this.seasonRepository.findByLeagueId(params.leagueId);
const activeSeason = seasons.find(s => s.status.isActive()) ?? seasons[0];
const activeSeason = seasons.find((s: Season) => s.status.isActive()) ?? seasons[0];
if (!activeSeason) {
return Result.err({
code: 'SEASON_NOT_FOUND',

View File

@@ -1,5 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Result } from '@core/shared/domain/Result';
import { GetLeagueScoringConfigUseCase } from './GetLeagueScoringConfigUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
@@ -13,10 +12,26 @@ import type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset
describe('GetLeagueScoringConfigUseCase', () => {
let useCase: GetLeagueScoringConfigUseCase;
let mockLeagueRepository: any;
let mockSeasonRepository: any;
let mockLeagueScoringConfigRepository: any;
let mockGameRepository: any;
let mockLeagueRepository: {
findById: any;
exists: any;
save: any;
findAll: any;
};
let mockSeasonRepository: {
findByLeagueId: any;
save: any;
findById: any;
};
let mockLeagueScoringConfigRepository: {
findBySeasonId: any;
save: any;
};
let mockGameRepository: {
findById: any;
save: any;
findAll: any;
};
let mockPresetProvider: { getPresetById: any };
beforeEach(() => {
@@ -49,11 +64,11 @@ describe('GetLeagueScoringConfigUseCase', () => {
};
useCase = new GetLeagueScoringConfigUseCase(
mockLeagueRepository,
mockSeasonRepository,
mockLeagueScoringConfigRepository,
mockGameRepository,
mockPresetProvider,
mockLeagueRepository as unknown as LeagueRepository,
mockSeasonRepository as unknown as SeasonRepository,
mockLeagueScoringConfigRepository as unknown as LeagueScoringConfigRepository,
mockGameRepository as unknown as GameRepository,
mockPresetProvider as any,
);
});

View File

@@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetRaceDetailUseCase,
type GetRaceDetailInput,
type GetRaceDetailResult,
type GetRaceDetailErrorCode,
} from './GetRaceDetailUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';

View File

@@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
GetRacePenaltiesUseCase,
type GetRacePenaltiesInput,
type GetRacePenaltiesResult,
type GetRacePenaltiesErrorCode,
} from './GetRacePenaltiesUseCase';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';

View File

@@ -9,6 +9,9 @@ import { Protest } from '../../domain/entities/Protest';
import { Driver } from '../../domain/entities/Driver';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
describe('GetRaceProtestsUseCase', () => {
let useCase: GetRaceProtestsUseCase;
let protestRepository: { findByRaceId: Mock };
@@ -17,8 +20,10 @@ describe('GetRaceProtestsUseCase', () => {
beforeEach(() => {
protestRepository = { findByRaceId: vi.fn() };
driverRepository = { findById: vi.fn() };
useCase = new GetRaceProtestsUseCase(protestRepository as any,
driverRepository as any);
useCase = new GetRaceProtestsUseCase(
protestRepository as unknown as ProtestRepository,
driverRepository as unknown as DriverRepository
);
});
it('should return protests with drivers', async () => {

View File

@@ -2,12 +2,13 @@ import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetRaceRegistrationsUseCase,
type GetRaceRegistrationsInput,
type GetRaceRegistrationsResult,
type GetRaceRegistrationsErrorCode,
} from './GetRaceRegistrationsUseCase';
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
import { Race } from '@core/racing/domain/entities/Race';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
describe('GetRaceRegistrationsUseCase', () => {
let useCase: GetRaceRegistrationsUseCase;
@@ -17,8 +18,10 @@ describe('GetRaceRegistrationsUseCase', () => {
beforeEach(() => {
raceRepository = { findById: vi.fn() };
registrationRepository = { findByRaceId: vi.fn() };
useCase = new GetRaceRegistrationsUseCase(raceRepository as any,
registrationRepository as any);
useCase = new GetRaceRegistrationsUseCase(
raceRepository as unknown as RaceRepository,
registrationRepository as unknown as RaceRegistrationRepository
);
});
it('should return race and registrations on success', async () => {

View File

@@ -27,11 +27,13 @@ describe('GetRaceResultsDetailUseCase', () => {
driverRepository = { findAll: vi.fn() };
penaltyRepository = { findByRaceId: vi.fn() };
useCase = new GetRaceResultsDetailUseCase(raceRepository as any,
leagueRepository as any,
resultRepository as any,
driverRepository as any,
penaltyRepository as any);
useCase = new GetRaceResultsDetailUseCase(
raceRepository as unknown as RaceRepository,
leagueRepository as unknown as LeagueRepository,
resultRepository as unknown as ResultRepository,
driverRepository as unknown as DriverRepository,
penaltyRepository as unknown as PenaltyRepository
);
});
it('presents race results detail when race exists', async () => {

View File

@@ -1,13 +1,15 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetRaceWithSOFUseCase,
type GetRaceWithSOFInput,
type GetRaceWithSOFResult,
type GetRaceWithSOFErrorCode,
} from './GetRaceWithSOFUseCase';
import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
describe('GetRaceWithSOFUseCase', () => {
let useCase: GetRaceWithSOFUseCase;
@@ -34,10 +36,10 @@ describe('GetRaceWithSOFUseCase', () => {
};
getDriverRating = vi.fn();
useCase = new GetRaceWithSOFUseCase(
raceRepository as any,
registrationRepository as any,
resultRepository as any,
getDriverRating
raceRepository as unknown as RaceRepository,
registrationRepository as unknown as RaceRegistrationRepository,
resultRepository as unknown as ResultRepository,
getDriverRating as any
);
});

View File

@@ -2,7 +2,13 @@ import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { League } from '../../domain/entities/League';
export interface GetRacesPageDataInput {
leagueId: string;
}
export type GetRacesPageRaceItem = {
race: Race;
@@ -35,16 +41,16 @@ export class GetRacesPageDataUseCase {
]);
const leagueMap = new Map(
allLeagues.map(league => [league.id.toString(), league.name.toString()]),
allLeagues.map((league: League) => [league.id.toString(), league.name.toString()]),
);
const filteredRaces = input.leagueId
? allRaces.filter(race => race.leagueId === input.leagueId)
? allRaces.filter((race: Race) => race.leagueId === input.leagueId)
: allRaces;
filteredRaces.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
filteredRaces.sort((a: Race, b: Race) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
const races: GetRacesPageRaceItem[] = filteredRaces.map(race => ({
const races: GetRacesPageRaceItem[] = filteredRaces.map((race: Race) => ({
race,
leagueName: leagueMap.get(race.leagueId) ?? 'Unknown League',
}));

View File

@@ -1,12 +1,12 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetSeasonDetailsUseCase,
type GetSeasonDetailsInput,
type GetSeasonDetailsResult,
type GetSeasonDetailsErrorCode,
} from './GetSeasonDetailsUseCase';
import { Season } from '../../domain/entities/season/Season';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
describe('GetSeasonDetailsUseCase', () => {
let useCase: GetSeasonDetailsUseCase;
@@ -19,7 +19,7 @@ describe('GetSeasonDetailsUseCase', () => {
findById: vi.fn(),
};
useCase = new GetSeasonDetailsUseCase(seasonRepository as any);
useCase = new GetSeasonDetailsUseCase(seasonRepository as unknown as SeasonRepository);
});
it('returns full details for a season', async () => {

View File

@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
GetSeasonSponsorshipsUseCase,
type GetSeasonSponsorshipsInput,
type GetSeasonSponsorshipsResult,
type GetSeasonSponsorshipsErrorCode,
} from './GetSeasonSponsorshipsUseCase';
import type { SeasonSponsorshipRepository } from '../../domain/repositories/SeasonSponsorshipRepository';
@@ -52,11 +51,13 @@ describe('GetSeasonSponsorshipsUseCase', () => {
findByLeagueId: vi.fn(),
};
useCase = new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepository as any,
seasonRepository as any,
leagueRepository as any,
leagueMembershipRepository as any,
raceRepository as any);
useCase = new GetSeasonSponsorshipsUseCase(
seasonSponsorshipRepository as unknown as SeasonSponsorshipRepository,
seasonRepository as unknown as SeasonRepository,
leagueRepository as unknown as LeagueRepository,
leagueMembershipRepository as unknown as LeagueMembershipRepository,
raceRepository as unknown as RaceRepository
);
});
it('returns SEASON_NOT_FOUND when season does not exist', async () => {

View File

@@ -15,6 +15,7 @@ export class GetSponsorsUseCase {
constructor(private readonly sponsorRepository: SponsorRepository) {}
async execute(_input: GetSponsorsInput): Promise<Result<GetSponsorsResult, ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>>> {
void _input;
try {
const sponsors = await this.sponsorRepository.findAll();
return Result.ok({ sponsors });

View File

@@ -5,9 +5,9 @@ import {
type GetTeamJoinRequestsResult,
type GetTeamJoinRequestsErrorCode,
} from './GetTeamJoinRequestsUseCase';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import { Driver } from '../../domain/entities/Driver';
import { Team } from '../../domain/entities/Team';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -34,9 +34,11 @@ describe('GetTeamJoinRequestsUseCase', () => {
findById: vi.fn(),
};
useCase = new GetTeamJoinRequestsUseCase(membershipRepository as any,
driverRepository as any,
teamRepository as any);
useCase = new GetTeamJoinRequestsUseCase(
membershipRepository as unknown as TeamMembershipRepository,
driverRepository as unknown as DriverRepository,
teamRepository as unknown as TeamRepository
);
});
it('should return join requests with drivers when team exists', async () => {

View File

@@ -7,6 +7,10 @@ import {
type GetTeamMembersErrorCode,
type GetTeamMembersInput
} from './GetTeamMembersUseCase';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('GetTeamMembersUseCase', () => {
let useCase: GetTeamMembersUseCase;
@@ -41,10 +45,12 @@ describe('GetTeamMembersUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
useCase = new GetTeamMembersUseCase(membershipRepository as any,
driverRepository as any,
teamRepository as any,
logger as any);
useCase = new GetTeamMembersUseCase(
membershipRepository as unknown as TeamMembershipRepository,
driverRepository as unknown as DriverRepository,
teamRepository as unknown as TeamRepository,
logger as unknown as Logger
);
});
it('should return team members with driver entities', async () => {

View File

@@ -3,6 +3,10 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
import type { Driver } from '../../domain/entities/Driver';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { Logger } from '@core/shared/domain/Logger';
export type GetTeamMembersInput = {
teamId: string;

View File

@@ -1,15 +1,21 @@
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
GetTeamMembershipUseCase,
type GetTeamMembershipErrorCode,
type GetTeamMembershipInput
} from './GetTeamMembershipUseCase';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
describe('GetTeamMembershipUseCase', () => {
const mockGetMembership = vi.fn();
const mockMembershipRepo: any = {
const mockMembershipRepo: {
getMembership: Mock;
getActiveMembershipForDriver: Mock;
getTeamMembers: Mock;
saveMembership: Mock;
removeMembership: Mock;
getJoinRequests: Mock;
countByTeamId: Mock;
saveJoinRequest: Mock;
removeJoinRequest: Mock;
} = {
getMembership: mockGetMembership,
getActiveMembershipForDriver: vi.fn(),
getTeamMembers: vi.fn(),
@@ -32,7 +38,7 @@ describe('GetTeamMembershipUseCase', () => {
beforeEach(() => {
vi.clearAllMocks();
useCase = new GetTeamMembershipUseCase(mockMembershipRepo, mockLogger);
useCase = new GetTeamMembershipUseCase(mockMembershipRepo as unknown as TeamMembershipRepository, mockLogger);
});
it('should return membership data when membership exists', async () => {

View File

@@ -1,5 +1,8 @@
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
export type GetTeamMembershipInput = {
teamId: string;
driverId: string;

View File

@@ -7,10 +7,15 @@ import {
type GetTeamsLeaderboardInput
} from './GetTeamsLeaderboardUseCase';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('GetTeamsLeaderboardUseCase', () => {
let useCase: GetTeamsLeaderboardUseCase;
let teamRepository: {
findAll: Mock;
findById: Mock;
};
let teamMembershipRepository: {
getTeamMembers: Mock;
@@ -26,6 +31,7 @@ describe('GetTeamsLeaderboardUseCase', () => {
beforeEach(() => {
teamRepository = {
findAll: vi.fn(),
findById: vi.fn(),
};
teamMembershipRepository = {
getTeamMembers: vi.fn(),
@@ -37,10 +43,11 @@ describe('GetTeamsLeaderboardUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
useCase = new GetTeamsLeaderboardUseCase(teamRepository as any,
teamMembershipRepository as any,
useCase = new GetTeamsLeaderboardUseCase(
teamRepository as unknown as TeamRepository,
teamMembershipRepository as unknown as TeamMembershipRepository,
getDriverStats as any,
logger as any
logger as unknown as Logger
);
});

View File

@@ -1,8 +1,10 @@
import { Team } from '@/racing/domain/entities/Team';
import { Team } from '../../domain/entities/Team';
import { SkillLevelService, type SkillLevel } from '@core/racing/domain/services/SkillLevelService';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
interface DriverStatsAdapter {
rating: number | null;
@@ -55,7 +57,7 @@ export class GetTeamsLeaderboardUseCase {
const items: TeamLeaderboardItem[] = [];
await Promise.all(
allTeams.map(async (team) => {
allTeams.map(async (team: Team) => {
const memberships = await this.teamMembershipRepository.getTeamMembers(team.id);
const memberCount = memberships.length;

View File

@@ -16,6 +16,7 @@ export class GetTotalDriversUseCase {
async execute(
_input: GetTotalDriversInput,
): Promise<Result<GetTotalDriversResult, ApplicationErrorCode<GetTotalDriversErrorCode, { message: string }>>> {
void _input;
try {
const drivers = await this.driverRepository.findAll();
const totalDrivers = drivers.length;

View File

@@ -16,6 +16,7 @@ export class GetTotalLeaguesUseCase {
async execute(
_input: GetTotalLeaguesInput,
): Promise<Result<GetTotalLeaguesResult, ApplicationErrorCode<GetTotalLeaguesErrorCode, { message: string }>>> {
void _input;
try {
const leagues = await this.leagueRepository.findAll();
const totalLeagues = leagues.length;

Some files were not shown because too many files have changed in this diff Show More