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 { vi, describe, it, expect, beforeEach } from 'vitest';
import { ListUsersUseCase, ListUsersResult } from './ListUsersUseCase'; import { ListUsersUseCase } from './ListUsersUseCase';
import { AdminUserRepository } from '../ports/AdminUserRepository'; import { AdminUserRepository } from '../ports/AdminUserRepository';
import { AdminUser } from '../../domain/entities/AdminUser'; import { AdminUser } from '../../domain/entities/AdminUser';
import { AuthorizationService } from '../../domain/services/AuthorizationService'; 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 type = 'domain' as const;
readonly context = 'admin-domain'; readonly context = 'admin-domain';
abstract readonly kind: CommonDomainErrorKind; abstract readonly kind: CommonDomainErrorKind;

View File

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

View File

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

View File

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

View File

@@ -3,9 +3,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest';
import { AppendRatingEventsUseCase, AppendRatingEventsInput } from './AppendRatingEventsUseCase'; import { AppendRatingEventsUseCase, AppendRatingEventsInput } from './AppendRatingEventsUseCase';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository'; import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository'; 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', () => { describe('AppendRatingEventsUseCase', () => {
let mockEventRepo: Partial<RatingEventRepository>; let mockEventRepo: Partial<RatingEventRepository>;

View File

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

View File

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

View File

@@ -4,11 +4,12 @@
* Authenticates a user with email and password. * 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort'; import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { UserRepository } from '../../domain/repositories/UserRepository';
export type LoginWithEmailInput = { export type LoginWithEmailInput = {
email: string; email: string;

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { Result } from '@core/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 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 { AuthenticatedUser } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort'; import type { IdentitySessionPort } from '../ports/IdentitySessionPort';

View File

@@ -1,27 +1,30 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase'; import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto'; import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
// Mock repository for integration test // Mock repository for integration test
class MockExternalGameRatingRepository { class MockExternalGameRatingRepository {
private profiles = new Map<string, any>(); private profiles = new Map<string, ExternalGameRatingProfile>();
private getKey(userId: string, gameKey: string): string { private getKey(userId: string, gameKey: string): string {
return `${userId}|${gameKey}`; 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; return this.profiles.get(this.getKey(userId, gameKey)) || null;
} }
async findByUserId(userId: string): Promise<any[]> { async findByUserId(userId: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.userId.toString() === userId); return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.userId.toString() === userId);
} }
async findByGameKey(gameKey: string): Promise<any[]> { async findByGameKey(gameKey: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.gameKey.toString() === gameKey); 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()); const key = this.getKey(profile.userId.toString(), profile.gameKey.toString());
this.profiles.set(key, profile); this.profiles.set(key, profile);
return profile; return profile;
@@ -50,7 +53,7 @@ describe('UpsertExternalGameRatingUseCase - Integration', () => {
beforeEach(() => { beforeEach(() => {
repository = new MockExternalGameRatingRepository(); repository = new MockExternalGameRatingRepository();
useCase = new UpsertExternalGameRatingUseCase(repository as any); useCase = new UpsertExternalGameRatingUseCase(repository as unknown as ExternalGameRatingRepository);
}); });
describe('Full upsert flow', () => { 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 { ExternalRating } from '../../domain/value-objects/ExternalRating';
import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance'; import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto'; 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', () => { describe('UpsertExternalGameRatingUseCase', () => {
let useCase: UpsertExternalGameRatingUseCase; let useCase: UpsertExternalGameRatingUseCase;
let mockRepository: ExternalGameRatingRepository; let mockRepository: {
findByUserIdAndGameKey: Mock;
findByUserId: Mock;
findByGameKey: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
exists: Mock;
};
beforeEach(() => { beforeEach(() => {
mockRepository = { mockRepository = {
@@ -21,9 +29,17 @@ describe('UpsertExternalGameRatingUseCase', () => {
saveMany: vi.fn(), saveMany: vi.fn(),
delete: vi.fn(), delete: vi.fn(),
exists: 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', () => { describe('execute', () => {
@@ -42,8 +58,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
}, },
}; };
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null); mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile); mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input); const result = await useCase.execute(input);
@@ -61,8 +77,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
it('should update existing profile', async () => { it('should update existing profile', async () => {
const existingProfile = createTestProfile('user-123', 'iracing'); const existingProfile = createTestProfile('user-123', 'iracing');
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(existingProfile); mockRepository.findByUserIdAndGameKey.mockResolvedValue(existingProfile);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile); mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const input: UpsertExternalGameRatingInput = { const input: UpsertExternalGameRatingInput = {
userId: 'user-123', 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); const result = await useCase.execute(input);
@@ -232,8 +248,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
}, },
}; };
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null); mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile); mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input); const result = await useCase.execute(input);
@@ -254,8 +270,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
}, },
}; };
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null); mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile); mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input); const result = await useCase.execute(input);

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ import { RatingEvent } from '../entities/RatingEvent';
import { RatingEventId } from '../value-objects/RatingEventId'; import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey'; import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingDelta } from '../value-objects/RatingDelta'; import { RatingDelta } from '../value-objects/RatingDelta';
import { AdminVoteOutcome } from '../entities/AdminVoteSession';
describe('AdminTrustRatingCalculator', () => { describe('AdminTrustRatingCalculator', () => {
describe('calculate', () => { describe('calculate', () => {
@@ -316,13 +315,12 @@ describe('AdminTrustRatingCalculator', () => {
it('should default to zero for unknown action type', () => { it('should default to zero for unknown action type', () => {
const input: SystemSignalInput = { const input: SystemSignalInput = {
actionType: 'sla_response' as any, actionType: 'unknown_type' as unknown as SystemSignalInput['actionType'],
details: {}, details: {},
}; };
// Override for test
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input); 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 { 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 * Input DTO for driving rating calculation from race facts
@@ -71,7 +69,7 @@ export class DrivingRatingCalculator {
const fieldStrength = facts.results.length > 0 const fieldStrength = facts.results.length > 0
? (facts.results ? (facts.results
.filter(r => r.status === 'finished') .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)) Math.max(1, facts.results.filter(r => r.status === 'finished').length))
: 0; : 0;
@@ -297,7 +295,7 @@ export class DrivingRatingCalculator {
* Estimate driver rating for SoF calculation * Estimate driver rating for SoF calculation
* This is a placeholder - in real implementation, would query user rating snapshot * 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 // Default rating for new drivers
return 50; return 50;
} }

View File

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

View File

@@ -6,8 +6,13 @@
* Provides explainable results with detailed reasons. * Provides explainable results with detailed reasons.
*/ */
import { EvaluationResultDto, EvaluationReason } from '../../application/dtos/EvaluationResultDto'; import {
import { EligibilityFilterDto, ParsedEligibilityFilter, EligibilityCondition } from '../../application/dtos/EligibilityFilterDto'; EvaluationResultDto,
EvaluationReason,
EligibilityFilterDto,
ParsedEligibilityFilter,
EligibilityCondition
} from '../types/Eligibility';
export interface RatingData { export interface RatingData {
platform: { 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 { RatingUpdateService } from './RatingUpdateService';
import type { UserRatingRepository } from '../repositories/UserRatingRepository'; import type { UserRatingRepository } from '../repositories/UserRatingRepository';
import type { RatingEventRepository } from '../repositories/RatingEventRepository'; import type { RatingEventRepository } from '../repositories/RatingEventRepository';
@@ -10,8 +10,8 @@ import { RatingDelta } from '../value-objects/RatingDelta';
describe('RatingUpdateService - Slice 7 Evolution', () => { describe('RatingUpdateService - Slice 7 Evolution', () => {
let service: RatingUpdateService; let service: RatingUpdateService;
let userRatingRepository: any; let userRatingRepository: { findByUserId: Mock; save: Mock };
let ratingEventRepository: any; let ratingEventRepository: { save: Mock; getAllByUserId: Mock };
beforeEach(() => { beforeEach(() => {
userRatingRepository = { userRatingRepository = {
@@ -24,7 +24,10 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
getAllByUserId: vi.fn(), 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', () => { describe('recordRaceRatingEvents - Ledger-based approach', () => {
@@ -150,7 +153,7 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
// Verify DNF penalty event was created // Verify DNF penalty event was created
const savedEvents = ratingEventRepository.save.mock.calls.map(call => call[0]); 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' event.reason.code === 'DRIVING_DNF_PENALTY'
); );
expect(hasDnfPenalty).toBe(true); 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 { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('AdminTrustReasonCode', () => { describe('AdminTrustReasonCode', () => {
describe('create', () => { describe('create', () => {
@@ -71,7 +72,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE', 'ADMIN_VOTE_OUTCOME_NEGATIVE',
]; ];
voteCodes.forEach(codeStr => { voteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(true); expect(code.isVoteOutcome()).toBe(true);
}); });
}); });
@@ -82,7 +83,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_REVERSAL_PENALTY', 'ADMIN_ACTION_REVERSAL_PENALTY',
]; ];
nonVoteCodes.forEach(codeStr => { nonVoteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(false); expect(code.isVoteOutcome()).toBe(false);
}); });
}); });
@@ -97,7 +98,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY', 'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
]; ];
systemCodes.forEach(codeStr => { systemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(true); expect(code.isSystemSignal()).toBe(true);
}); });
}); });
@@ -108,7 +109,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE', 'ADMIN_VOTE_OUTCOME_NEGATIVE',
]; ];
nonSystemCodes.forEach(codeStr => { nonSystemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(false); expect(code.isSystemSignal()).toBe(false);
}); });
}); });
@@ -122,7 +123,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS', 'ADMIN_ACTION_RULE_CLARITY_BONUS',
]; ];
positiveCodes.forEach(codeStr => { positiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(true); expect(code.isPositive()).toBe(true);
}); });
}); });
@@ -134,7 +135,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY', 'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
]; ];
nonPositiveCodes.forEach(codeStr => { nonPositiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(false); expect(code.isPositive()).toBe(false);
}); });
}); });
@@ -148,7 +149,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY', 'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
]; ];
negativeCodes.forEach(codeStr => { negativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(true); expect(code.isNegative()).toBe(true);
}); });
}); });
@@ -160,7 +161,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS', 'ADMIN_ACTION_RULE_CLARITY_BONUS',
]; ];
nonNegativeCodes.forEach(codeStr => { nonNegativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any); const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(false); 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 { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('DrivingReasonCode', () => { describe('DrivingReasonCode', () => {
describe('create', () => { describe('create', () => {
@@ -77,7 +78,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN', 'DRIVING_PACE_RELATIVE_GAIN',
]; ];
performanceCodes.forEach(codeStr => { performanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(true); expect(code.isPerformance()).toBe(true);
}); });
}); });
@@ -89,7 +90,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS', 'DRIVING_SEASON_ATTENDANCE_BONUS',
]; ];
nonPerformanceCodes.forEach(codeStr => { nonPerformanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(false); expect(code.isPerformance()).toBe(false);
}); });
}); });
@@ -103,7 +104,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PENALTY_INVOLVEMENT_PENALTY', 'DRIVING_PENALTY_INVOLVEMENT_PENALTY',
]; ];
cleanDrivingCodes.forEach(codeStr => { cleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(true); expect(code.isCleanDriving()).toBe(true);
}); });
}); });
@@ -115,7 +116,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS', 'DRIVING_SEASON_ATTENDANCE_BONUS',
]; ];
nonCleanDrivingCodes.forEach(codeStr => { nonCleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(false); expect(code.isCleanDriving()).toBe(false);
}); });
}); });
@@ -131,7 +132,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS', 'DRIVING_SEASON_ATTENDANCE_BONUS',
]; ];
reliabilityCodes.forEach(codeStr => { reliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(true); expect(code.isReliability()).toBe(true);
}); });
}); });
@@ -142,7 +143,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_INCIDENTS_PENALTY', 'DRIVING_INCIDENTS_PENALTY',
]; ];
nonReliabilityCodes.forEach(codeStr => { nonReliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(false); expect(code.isReliability()).toBe(false);
}); });
}); });
@@ -160,7 +161,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_AFK_PENALTY', 'DRIVING_AFK_PENALTY',
]; ];
penaltyCodes.forEach(codeStr => { penaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(true); expect(code.isPenalty()).toBe(true);
}); });
}); });
@@ -172,7 +173,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS', 'DRIVING_SEASON_ATTENDANCE_BONUS',
]; ];
nonPenaltyCodes.forEach(codeStr => { nonPenaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(false); expect(code.isPenalty()).toBe(false);
}); });
}); });
@@ -187,7 +188,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN', 'DRIVING_PACE_RELATIVE_GAIN',
]; ];
bonusCodes.forEach(codeStr => { bonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(true); expect(code.isBonus()).toBe(true);
}); });
}); });
@@ -198,7 +199,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_DNS_PENALTY', 'DRIVING_DNS_PENALTY',
]; ];
nonBonusCodes.forEach(codeStr => { nonBonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any); const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(false); expect(code.isBonus()).toBe(false);
}); });
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,11 +4,15 @@
* Marks a notification as read. * 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
export interface MarkNotificationReadCommand {
notificationId: string;
recipientId: string;
}
export interface MarkNotificationReadResult { export interface MarkNotificationReadResult {
notificationId: string; notificationId: string;

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import type { DomainError } from '@core/shared/errors/DomainError'; import type { DomainError, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
import type { CommonDomainErrorKind } from '@core/shared/errors/DomainError';
/** /**
* Domain Error: NotificationDomainError * 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 { ApplicationError, CommonApplicationErrorKind } from '@core/shared/errors/ApplicationError';
import type { CommonApplicationErrorKind } from '@core/shared/errors/ApplicationError';
export abstract class RacingApplicationError export abstract class RacingApplicationError
extends Error extends Error

View File

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

View File

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

View File

@@ -6,9 +6,15 @@
*/ */
import type { NotificationService } from '@core/notifications/application/ports/NotificationService'; 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 { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship'; 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 { export interface AcceptSponsorshipRequestInput {
requestId: string; 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 { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '@core/racing/domain/value-objects/TeamRatingDimensionKey'; import { TeamRatingDimensionKey } from '@core/racing/domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '@core/racing/domain/value-objects/TeamRatingDelta'; 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 // Mock repositories
class MockTeamRatingEventRepository implements TeamRatingEventRepository { class MockTeamRatingEventRepository implements TeamRatingEventRepository {
@@ -27,7 +29,7 @@ class MockTeamRatingEventRepository implements TeamRatingEventRepository {
return this.events.filter(e => e.teamId === teamId); 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); const events = await this.getAllByTeamId(teamId);
return { return {
items: events, items: events,
@@ -44,13 +46,13 @@ class MockTeamRatingEventRepository implements TeamRatingEventRepository {
} }
class MockTeamRatingRepository implements TeamRatingRepository { 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; return this.snapshots.get(teamId) || null;
} }
async save(snapshot: any): Promise<any> { async save(snapshot: TeamRating): Promise<TeamRating> {
this.snapshots.set(snapshot.teamId, snapshot); this.snapshots.set(snapshot.teamId, snapshot);
return snapshot; return snapshot;
} }

View File

@@ -5,11 +5,14 @@
* (driver, team, race, or season/league). * (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 { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest'; import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
import { Money, isCurrency } from '../../domain/value-objects/Money'; 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 { export interface ApplyForSponsorshipInput {
sponsorId: string; sponsorId: string;

View File

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

View File

@@ -5,11 +5,15 @@
* The penalty can be standalone or linked to an upheld protest. * 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 { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { Penalty } from '../../domain/entities/penalty/Penalty'; 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 { export interface ApplyPenaltyInput {
raceId: string; raceId: string;
@@ -62,7 +66,7 @@ export class ApplyPenaltyUseCase {
// Validate steward has authority (owner or admin of the league) // Validate steward has authority (owner or admin of the league)
const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId); const memberships = await this.leagueMembershipRepository.getLeagueMembers(race.leagueId);
const stewardMembership = memberships.find( 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')) { 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}.`, `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); 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 { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { JoinedAt } from '../../domain/value-objects/JoinedAt'; import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import { LeagueId } from '../../domain/entities/LeagueId';
import { DriverId } from '../../domain/entities/DriverId';
import { MembershipRole } from '../../domain/entities/MembershipRole';
import { MembershipStatus } from '../../domain/entities/MembershipStatus';
export interface ApproveLeagueJoinRequestInput { export interface ApproveLeagueJoinRequestInput {
leagueId: string; leagueId: string;
@@ -55,14 +51,16 @@ export class ApproveLeagueJoinRequestUseCase {
} }
await this.leagueMembershipRepository.removeJoinRequest(input.joinRequestId); await this.leagueMembershipRepository.removeJoinRequest(input.joinRequestId);
await this.leagueMembershipRepository.saveMembership({ await this.leagueMembershipRepository.saveMembership(
id: randomUUID(), LeagueMembership.create({
leagueId: LeagueId.create(input.leagueId), id: randomUUID(),
driverId: DriverId.create(request.driverId.toString()), leagueId: input.leagueId,
role: MembershipRole.create('member'), driverId: request.driverId.toString(),
status: MembershipStatus.create('active'), role: 'member',
joinedAt: JoinedAt.create(new Date()), status: 'active',
}); joinedAt: new Date(),
})
);
const result: ApproveLeagueJoinRequestResult = { success: true, message: 'Join request approved.' }; 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 { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType'; import { SessionType } from '../../domain/value-objects/SessionType';
import { CancelRaceUseCase } from './CancelRaceUseCase'; import { CancelRaceUseCase } from './CancelRaceUseCase';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('CancelRaceUseCase', () => { describe('CancelRaceUseCase', () => {
let useCase: CancelRaceUseCase; let useCase: CancelRaceUseCase;
@@ -27,8 +29,8 @@ describe('CancelRaceUseCase', () => {
info: vi.fn(), info: vi.fn(),
error: vi.fn(), error: vi.fn(),
}; };
useCase = new CancelRaceUseCase(raceRepository as any, useCase = new CancelRaceUseCase(raceRepository as unknown as RaceRepository,
logger as any); logger as unknown as Logger);
}); });
it('should cancel race successfully', async () => { it('should cancel race successfully', async () => {

View File

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

View File

@@ -3,6 +3,11 @@ import { RaceEvent } from '../../domain/entities/RaceEvent';
import { Session } from '../../domain/entities/Session'; import { Session } from '../../domain/entities/Session';
import { SessionType } from '../../domain/value-objects/SessionType'; import { SessionType } from '../../domain/value-objects/SessionType';
import { CloseRaceEventStewardingUseCase } from './CloseRaceEventStewardingUseCase'; 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', () => { describe('CloseRaceEventStewardingUseCase', () => {
let useCase: CloseRaceEventStewardingUseCase; let useCase: CloseRaceEventStewardingUseCase;
@@ -42,11 +47,11 @@ describe('CloseRaceEventStewardingUseCase', () => {
logger = { logger = {
error: vi.fn(), error: vi.fn(),
}; };
useCase = new CloseRaceEventStewardingUseCase(logger as any, useCase = new CloseRaceEventStewardingUseCase(logger as unknown as Logger,
raceEventRepository as any, raceEventRepository as unknown as RaceEventRepository,
raceRegistrationRepository as any, raceRegistrationRepository as unknown as RaceRegistrationRepository,
penaltyRepository as any, penaltyRepository as unknown as PenaltyRepository,
domainEventPublisher as any); domainEventPublisher as unknown as DomainEventPublisher);
}); });
it('should close stewarding for expired events successfully', async () => { 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceEvent } from '../../domain/entities/RaceEvent'; import type { RaceEvent } from '../../domain/entities/RaceEvent';
import { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed'; 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 = { export type CloseRaceEventStewardingInput = {
raceId: string; raceId: string;

View File

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

View File

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

View File

@@ -4,7 +4,16 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
import { Race } from '../../domain/entities/Race'; import { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season'; 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 = { export type CreateLeagueSeasonScheduleRaceResult = {
raceId: string; raceId: string;
@@ -88,6 +97,9 @@ export class CreateLeagueSeasonScheduleRaceUseCase {
} }
private isWithinSeasonWindow(season: Season, scheduledAt: Date): boolean { private isWithinSeasonWindow(season: Season, scheduledAt: Date): boolean {
if (!season.startDate || !season.endDate) {
return true;
}
return scheduledAt >= season.startDate && scheduledAt <= season.endDate; 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -13,6 +13,9 @@ import {
MIN_RANKED_LEAGUE_DRIVERS, MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility'; } from '../../domain/value-objects/LeagueVisibility';
import { PointsTable } from '../../domain/value-objects/PointsTable'; 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 = { export type CreateLeagueWithSeasonAndScoringCommand = {
name: string; 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 { Season } from '@core/racing/domain/entities/season/Season';
import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository'; import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository'; import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
import { import {
CreateSeasonForLeagueUseCase, CreateSeasonForLeagueUseCase,
type CreateSeasonForLeagueInput, type CreateSeasonForLeagueInput,
type CreateSeasonForLeagueResult,
type LeagueConfigFormModel, type LeagueConfigFormModel,
} from '@core/racing/application/use-cases/CreateSeasonForLeagueUseCase'; } 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 { function createLeagueConfigFormModel(overrides?: Partial<LeagueConfigFormModel>): LeagueConfigFormModel {
return { 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', () => { describe('CreateSeasonForLeagueUseCase', () => {
const mockLeagueFindById = vi.fn(); 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, findById: mockLeagueFindById,
findAll: vi.fn(), findAll: vi.fn(),
findByOwnerId: vi.fn(), findByOwnerId: vi.fn(),
@@ -87,7 +89,15 @@ describe('CreateSeasonForLeagueUseCase', () => {
const mockSeasonFindById = vi.fn(); const mockSeasonFindById = vi.fn();
const mockSeasonAdd = 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, findById: mockSeasonFindById,
findByLeagueId: vi.fn(), findByLeagueId: vi.fn(),
create: vi.fn(), create: vi.fn(),
@@ -105,7 +115,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockLeagueFindById.mockResolvedValue({ id: 'league-1' }); mockLeagueFindById.mockResolvedValue({ id: 'league-1' });
mockSeasonAdd.mockResolvedValue(undefined); 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({ const config = createLeagueConfigFormModel({
basics: { basics: {
@@ -157,7 +170,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockSeasonFindById.mockResolvedValue(sourceSeason); mockSeasonFindById.mockResolvedValue(sourceSeason);
mockSeasonAdd.mockResolvedValue(undefined); 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 = { const command: CreateSeasonForLeagueInput = {
leagueId: 'league-1', leagueId: 'league-1',
@@ -177,7 +193,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
it('returns error when league not found', async () => { it('returns error when league not found', async () => {
mockLeagueFindById.mockResolvedValue(null); 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 = { const command: CreateSeasonForLeagueInput = {
leagueId: 'missing-league', leagueId: 'missing-league',
@@ -197,7 +216,10 @@ describe('CreateSeasonForLeagueUseCase', () => {
mockLeagueFindById.mockResolvedValue({ id: 'league-1' }); mockLeagueFindById.mockResolvedValue({ id: 'league-1' });
mockSeasonFindById.mockResolvedValue(undefined); 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 = { const command: CreateSeasonForLeagueInput = {
leagueId: 'league-1', leagueId: 'league-1',

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,14 @@
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 = { export type DeleteLeagueSeasonScheduleRaceResult = {
success: true; 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 { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository'; import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository'; 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', () => { describe('FileProtestUseCase', () => {
let mockProtestRepo: { let mockProtestRepo: {
@@ -29,9 +30,11 @@ describe('FileProtestUseCase', () => {
}); });
it('should return error when race does not exist', async () => { it('should return error when race does not exist', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any, const useCase = new FileProtestUseCase(
mockRaceRepo as any, mockProtestRepo as unknown as ProtestRepository,
mockLeagueMembershipRepo as any); mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue(null); mockRaceRepo.findById.mockResolvedValue(null);
@@ -49,9 +52,11 @@ describe('FileProtestUseCase', () => {
}); });
it('should return error when protesting against self', async () => { it('should return error when protesting against self', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any, const useCase = new FileProtestUseCase(
mockRaceRepo as any, mockProtestRepo as unknown as ProtestRepository,
mockLeagueMembershipRepo as any); mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); 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 () => { it('should return error when protesting driver is not an active member', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any, const useCase = new FileProtestUseCase(
mockRaceRepo as any, mockProtestRepo as unknown as ProtestRepository,
mockLeagueMembershipRepo as any); mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([ mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -92,9 +99,11 @@ describe('FileProtestUseCase', () => {
}); });
it('should create protest and return protestId on success', async () => { it('should create protest and return protestId on success', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as any, const useCase = new FileProtestUseCase(
mockRaceRepo as any, mockProtestRepo as unknown as ProtestRepository,
mockLeagueMembershipRepo as any); mockRaceRepo as unknown as RaceRepository,
mockLeagueMembershipRepo as unknown as LeagueMembershipRepository
);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' }); mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([ mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -114,7 +123,7 @@ describe('FileProtestUseCase', () => {
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
const presented = result.unwrap(); const presented = result.unwrap();
expect(mockProtestRepo.create).toHaveBeenCalledTimes(1); 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.raceId.toString()).toBe('race1');
expect(created.protestingDriverId.toString()).toBe('driver1'); expect(created.protestingDriverId.toString()).toBe('driver1');

View File

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

View File

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

View File

@@ -1,6 +1,10 @@
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 = {}; export type GetAllRacesPageDataInput = {};
@@ -46,12 +50,12 @@ export class GetAllRacesPageDataUseCase {
]); ]);
this.logger.info(`Found ${allRaces.length} races and ${allLeagues.length} leagues.`); 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 const races: GetAllRacesPageRaceItem[] = allRaces
.slice() .slice()
.sort((a, b) => b.scheduledAt.getTime() - a.scheduledAt.getTime()) .sort((a: Race, b: Race) => b.scheduledAt.getTime() - a.scheduledAt.getTime())
.map(race => ({ .map((race: Race) => ({
id: race.id, id: race.id,
track: race.track, track: race.track,
car: race.car, 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 { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League'; import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race'; 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 = {}; 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 {} export interface GetAllTeamsInput {}
@@ -39,6 +42,7 @@ export class GetAllTeamsUseCase {
async execute( async execute(
_input: GetAllTeamsInput, _input: GetAllTeamsInput,
): Promise<Result<GetAllTeamsResult, ApplicationErrorCode<GetAllTeamsErrorCode, { message: string }>>> { ): Promise<Result<GetAllTeamsResult, ApplicationErrorCode<GetAllTeamsErrorCode, { message: string }>>> {
void _input;
this.logger.debug('GetAllTeamsUseCase: Fetching all teams'); this.logger.debug('GetAllTeamsUseCase: Fetching all teams');
try { try {
@@ -71,7 +75,7 @@ export class GetAllTeamsUseCase {
rating: stats?.rating ?? 0, rating: stats?.rating ?? 0,
logoUrl: logoUrl ?? null, logoUrl: logoUrl ?? null,
description: team.description.toString(), description: team.description.toString(),
leagues: team.leagues.map(l => l.toString()), leagues: team.leagues.map((l) => l.toString()),
isRecruiting: team.isRecruiting, isRecruiting: team.isRecruiting,
}); });
} }

View File

@@ -4,11 +4,12 @@
* Retrieves all liveries for a specific driver. * 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 { UseCase } from '@core/shared/application/UseCase';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { LiveryRepository } from '../../domain/repositories/LiveryRepository';
export interface GetDriverLiveriesInput { export interface GetDriverLiveriesInput {
driverId: string; 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Team } from '../../domain/entities/Team'; import type { Team } from '../../domain/entities/Team';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
export interface GetDriverTeamInput { export interface GetDriverTeamInput {
driverId: string; 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 { Driver } from '../../domain/entities/Driver';
import type { Team } from '../../domain/entities/Team'; import type { Team } from '../../domain/entities/Team';
import type { DriverRepository } from '../../domain/repositories/DriverRepository'; import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { DriverStatsUseCase } from './DriverStatsUseCase'; import type { DriverStats, DriverStatsUseCase } from './DriverStatsUseCase';
import type { RankingUseCase } from './RankingUseCase'; import type { RankingUseCase } from './RankingUseCase';
import { SkillLevelService, type SkillLevel } from '../../domain/services/SkillLevelService'; import { SkillLevelService, type SkillLevel } from '../../domain/services/SkillLevelService';
import { MediaReference } from '@core/domain/media/MediaReference'; import { MediaReference } from '@core/domain/media/MediaReference';
@@ -71,10 +71,11 @@ export class GetDriversLeaderboardUseCase implements UseCase<GetDriversLeaderboa
this.driverStatsUseCase.getDriverStats(driver.id) this.driverStatsUseCase.getDriverStats(driver.id)
); );
const statsResults = await Promise.all(statsPromises); const statsResults = await Promise.all(statsPromises);
const statsMap = new Map<string, any>(); const statsMap = new Map<string, DriverStats>();
drivers.forEach((driver, idx) => { drivers.forEach((driver, idx) => {
if (statsResults[idx]) { const stats = statsResults[idx];
statsMap.set(driver.id, statsResults[idx]); if (stats) {
statsMap.set(driver.id, stats);
} }
}); });

View File

@@ -8,6 +8,8 @@
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest'; 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; 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 { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League'; 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 = { export type LeagueAdminPermissions = {
canManageSchedule: boolean; 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 { import {
GetLeagueAdminUseCase, GetLeagueAdminUseCase,
type GetLeagueAdminInput, type GetLeagueAdminInput,
type GetLeagueAdminResult,
type GetLeagueAdminErrorCode, type GetLeagueAdminErrorCode,
} from './GetLeagueAdminUseCase'; } from './GetLeagueAdminUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueAdminUseCase', () => { 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; let mockFindById: Mock;
beforeEach(() => { beforeEach(() => {
@@ -26,7 +34,7 @@ describe('GetLeagueAdminUseCase', () => {
}; };
}); });
const createUseCase = () => new GetLeagueAdminUseCase(mockLeagueRepo); const createUseCase = () => new GetLeagueAdminUseCase(mockLeagueRepo as unknown as LeagueRepository);
const params: GetLeagueAdminInput = { const params: GetLeagueAdminInput = {
leagueId: 'league1', 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 { import {
GetLeagueDriverSeasonStatsUseCase, GetLeagueDriverSeasonStatsUseCase,
type GetLeagueDriverSeasonStatsErrorCode, type GetLeagueDriverSeasonStatsErrorCode,
type GetLeagueDriverSeasonStatsInput, type GetLeagueDriverSeasonStatsInput,
type GetLeagueDriverSeasonStatsResult,
} from './GetLeagueDriverSeasonStatsUseCase'; } from './GetLeagueDriverSeasonStatsUseCase';
import type { StandingRepository } from '../../domain/repositories/StandingRepository'; import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository'; import type { ResultRepository } from '../../domain/repositories/ResultRepository';
@@ -22,12 +21,70 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const mockDriverFindById = vi.fn(); const mockDriverFindById = vi.fn();
let useCase: GetLeagueDriverSeasonStatsUseCase; let useCase: GetLeagueDriverSeasonStatsUseCase;
let standingRepository: any; let standingRepository: {
let resultRepository: any; findByLeagueId: Mock;
let penaltyRepository: any; findByDriverIdAndLeagueId: Mock;
let raceRepository: any; findAll: Mock;
let driverRepository: any; save: Mock;
let driverRatingPort: any; 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(() => { beforeEach(() => {
mockStandingFindByLeagueId.mockReset(); mockStandingFindByLeagueId.mockReset();
@@ -102,12 +159,14 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
updateDriverRating: vi.fn(), updateDriverRating: vi.fn(),
}; };
useCase = new GetLeagueDriverSeasonStatsUseCase(standingRepository, useCase = new GetLeagueDriverSeasonStatsUseCase(
resultRepository, standingRepository as unknown as StandingRepository,
penaltyRepository, resultRepository as unknown as ResultRepository,
raceRepository, penaltyRepository as unknown as PenaltyRepository,
driverRepository, raceRepository as unknown as RaceRepository,
driverRatingPort); driverRepository as unknown as DriverRepository,
driverRatingPort as unknown as DriverRatingPort
);
}); });
it('should return league driver season stats for given league id', async () => { 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 { import {
GetLeagueFullConfigUseCase, GetLeagueFullConfigUseCase,
type GetLeagueFullConfigInput, type GetLeagueFullConfigInput,
type GetLeagueFullConfigResult,
type GetLeagueFullConfigErrorCode, type GetLeagueFullConfigErrorCode,
} from './GetLeagueFullConfigUseCase'; } from './GetLeagueFullConfigUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
@@ -13,10 +12,10 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
describe('GetLeagueFullConfigUseCase', () => { describe('GetLeagueFullConfigUseCase', () => {
let useCase: GetLeagueFullConfigUseCase; let useCase: GetLeagueFullConfigUseCase;
let leagueRepository: any; let leagueRepository: { findById: Mock };
let seasonRepository: any; let seasonRepository: { findByLeagueId: Mock };
let leagueScoringConfigRepository: any; let leagueScoringConfigRepository: { findBySeasonId: Mock };
let gameRepository: any; let gameRepository: { findById: Mock };
beforeEach(() => { beforeEach(() => {
leagueRepository = { leagueRepository = {
@@ -32,10 +31,12 @@ describe('GetLeagueFullConfigUseCase', () => {
findById: vi.fn(), findById: vi.fn(),
}; };
useCase = new GetLeagueFullConfigUseCase(leagueRepository, useCase = new GetLeagueFullConfigUseCase(
seasonRepository, leagueRepository as unknown as LeagueRepository,
leagueScoringConfigRepository, seasonRepository as unknown as SeasonRepository,
gameRepository); leagueScoringConfigRepository as unknown as LeagueScoringConfigRepository,
gameRepository as unknown as GameRepository
);
}); });
it('should return league config when league exists', async () => { 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 { import {
GetLeagueMembershipsUseCase, GetLeagueMembershipsUseCase,
type GetLeagueMembershipsInput, type GetLeagueMembershipsInput,
type GetLeagueMembershipsResult,
type GetLeagueMembershipsErrorCode, type GetLeagueMembershipsErrorCode,
} from './GetLeagueMembershipsUseCase'; } from './GetLeagueMembershipsUseCase';
import { LeagueMembership } from '../../domain/entities/LeagueMembership'; import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import { Driver } from '../../domain/entities/Driver'; import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League'; import { League } from '../../domain/entities/League';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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', () => { describe('GetLeagueMembershipsUseCase', () => {
let useCase: GetLeagueMembershipsUseCase; let useCase: GetLeagueMembershipsUseCase;
@@ -32,9 +34,11 @@ describe('GetLeagueMembershipsUseCase', () => {
leagueRepository = { leagueRepository = {
findById: vi.fn(), findById: vi.fn(),
}; };
useCase = new GetLeagueMembershipsUseCase(leagueMembershipRepository as any, useCase = new GetLeagueMembershipsUseCase(
driverRepository as any, leagueMembershipRepository as unknown as LeagueMembershipRepository,
leagueRepository as any); driverRepository as unknown as DriverRepository,
leagueRepository as unknown as LeagueRepository
);
}); });
it('should return league memberships with drivers', async () => { 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 { import {
GetLeagueOwnerSummaryUseCase, GetLeagueOwnerSummaryUseCase,
type GetLeagueOwnerSummaryInput, type GetLeagueOwnerSummaryInput,
type GetLeagueOwnerSummaryResult,
type GetLeagueOwnerSummaryErrorCode, type GetLeagueOwnerSummaryErrorCode,
} from './GetLeagueOwnerSummaryUseCase'; } from './GetLeagueOwnerSummaryUseCase';
import { Driver } from '../../domain/entities/Driver'; import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League'; import { League } from '../../domain/entities/League';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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', () => { describe('GetLeagueOwnerSummaryUseCase', () => {
let useCase: GetLeagueOwnerSummaryUseCase; let useCase: GetLeagueOwnerSummaryUseCase;
@@ -40,10 +43,10 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
findByLeagueId: vi.fn(), findByLeagueId: vi.fn(),
}; };
useCase = new GetLeagueOwnerSummaryUseCase( useCase = new GetLeagueOwnerSummaryUseCase(
leagueRepository as any, leagueRepository as unknown as LeagueRepository,
driverRepository as any, driverRepository as unknown as DriverRepository,
leagueMembershipRepository as any, leagueMembershipRepository as unknown as LeagueMembershipRepository,
standingRepository as any standingRepository as unknown as StandingRepository
); );
}); });

View File

@@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { import {
GetLeagueScheduleUseCase, GetLeagueScheduleUseCase,
type GetLeagueScheduleInput, type GetLeagueScheduleInput,
type GetLeagueScheduleResult,
type GetLeagueScheduleErrorCode, type GetLeagueScheduleErrorCode,
} from './GetLeagueScheduleUseCase'; } from './GetLeagueScheduleUseCase';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 { Race } from '../../domain/entities/Race';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository'; import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
describe('GetLeagueScheduleUseCase', () => { describe('GetLeagueScheduleUseCase', () => {
let useCase: GetLeagueScheduleUseCase; let useCase: GetLeagueScheduleUseCase;
@@ -42,7 +43,7 @@ describe('GetLeagueScheduleUseCase', () => {
error: vi.fn(), error: vi.fn(),
} as unknown as Logger; } as unknown as Logger;
useCase = new GetLeagueScheduleUseCase(leagueRepository as unknown as LeagueRepository, useCase = new GetLeagueScheduleUseCase(leagueRepository as unknown as LeagueRepository,
seasonRepository as any, seasonRepository as unknown as SeasonRepository,
raceRepository as unknown as RaceRepository, raceRepository as unknown as RaceRepository,
logger); logger);
}); });
@@ -109,7 +110,7 @@ describe('GetLeagueScheduleUseCase', () => {
raceRepository.findByLeagueId.mockResolvedValue([janRace, febRace]); raceRepository.findByLeagueId.mockResolvedValue([janRace, febRace]);
// Season 1 covers January // 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); expect(resultSeason1.isOk()).toBe(true);
const resultValue1 = resultSeason1.unwrap(); const resultValue1 = resultSeason1.unwrap();
expect(resultValue1.seasonId).toBe('season-jan'); expect(resultValue1.seasonId).toBe('season-jan');
@@ -117,7 +118,7 @@ describe('GetLeagueScheduleUseCase', () => {
expect(resultValue1.races.map(r => r.race.id)).toEqual(['race-jan']); expect(resultValue1.races.map(r => r.race.id)).toEqual(['race-jan']);
// Season 2 covers February // 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); expect(resultSeason2.isOk()).toBe(true);
const resultValue2 = resultSeason2.unwrap(); const resultValue2 = resultSeason2.unwrap();
expect(resultValue2.seasonId).toBe('season-feb'); 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 type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League'; import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race'; import type { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season'; 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 = export type GetLeagueScheduleErrorCode =
| 'LEAGUE_NOT_FOUND' | 'LEAGUE_NOT_FOUND'
@@ -51,7 +54,7 @@ export class GetLeagueScheduleUseCase {
} }
const seasons = await this.seasonRepository.findByLeagueId(params.leagueId); 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) { if (!activeSeason) {
return Result.err({ return Result.err({
code: 'SEASON_NOT_FOUND', code: 'SEASON_NOT_FOUND',

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { import {
GetRacePenaltiesUseCase, GetRacePenaltiesUseCase,
type GetRacePenaltiesInput, type GetRacePenaltiesInput,
type GetRacePenaltiesResult,
type GetRacePenaltiesErrorCode, type GetRacePenaltiesErrorCode,
} from './GetRacePenaltiesUseCase'; } from './GetRacePenaltiesUseCase';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository'; 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 { Driver } from '../../domain/entities/Driver';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
describe('GetRaceProtestsUseCase', () => { describe('GetRaceProtestsUseCase', () => {
let useCase: GetRaceProtestsUseCase; let useCase: GetRaceProtestsUseCase;
let protestRepository: { findByRaceId: Mock }; let protestRepository: { findByRaceId: Mock };
@@ -17,8 +20,10 @@ describe('GetRaceProtestsUseCase', () => {
beforeEach(() => { beforeEach(() => {
protestRepository = { findByRaceId: vi.fn() }; protestRepository = { findByRaceId: vi.fn() };
driverRepository = { findById: vi.fn() }; driverRepository = { findById: vi.fn() };
useCase = new GetRaceProtestsUseCase(protestRepository as any, useCase = new GetRaceProtestsUseCase(
driverRepository as any); protestRepository as unknown as ProtestRepository,
driverRepository as unknown as DriverRepository
);
}); });
it('should return protests with drivers', async () => { it('should return protests with drivers', async () => {

View File

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

View File

@@ -27,11 +27,13 @@ describe('GetRaceResultsDetailUseCase', () => {
driverRepository = { findAll: vi.fn() }; driverRepository = { findAll: vi.fn() };
penaltyRepository = { findByRaceId: vi.fn() }; penaltyRepository = { findByRaceId: vi.fn() };
useCase = new GetRaceResultsDetailUseCase(raceRepository as any, useCase = new GetRaceResultsDetailUseCase(
leagueRepository as any, raceRepository as unknown as RaceRepository,
resultRepository as any, leagueRepository as unknown as LeagueRepository,
driverRepository as any, resultRepository as unknown as ResultRepository,
penaltyRepository as any); driverRepository as unknown as DriverRepository,
penaltyRepository as unknown as PenaltyRepository
);
}); });
it('presents race results detail when race exists', async () => { 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 { import {
GetRaceWithSOFUseCase, GetRaceWithSOFUseCase,
type GetRaceWithSOFInput, type GetRaceWithSOFInput,
type GetRaceWithSOFResult,
type GetRaceWithSOFErrorCode, type GetRaceWithSOFErrorCode,
} from './GetRaceWithSOFUseCase'; } from './GetRaceWithSOFUseCase';
import { Race } from '../../domain/entities/Race'; import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType'; import { SessionType } from '../../domain/value-objects/SessionType';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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', () => { describe('GetRaceWithSOFUseCase', () => {
let useCase: GetRaceWithSOFUseCase; let useCase: GetRaceWithSOFUseCase;
@@ -34,10 +36,10 @@ describe('GetRaceWithSOFUseCase', () => {
}; };
getDriverRating = vi.fn(); getDriverRating = vi.fn();
useCase = new GetRaceWithSOFUseCase( useCase = new GetRaceWithSOFUseCase(
raceRepository as any, raceRepository as unknown as RaceRepository,
registrationRepository as any, registrationRepository as unknown as RaceRegistrationRepository,
resultRepository as any, resultRepository as unknown as ResultRepository,
getDriverRating 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 { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race'; 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 = { export type GetRacesPageRaceItem = {
race: Race; race: Race;
@@ -35,16 +41,16 @@ export class GetRacesPageDataUseCase {
]); ]);
const leagueMap = new Map( 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 const filteredRaces = input.leagueId
? allRaces.filter(race => race.leagueId === input.leagueId) ? allRaces.filter((race: Race) => race.leagueId === input.leagueId)
: allRaces; : 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, race,
leagueName: leagueMap.get(race.leagueId) ?? 'Unknown League', 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 { import {
GetSeasonDetailsUseCase, GetSeasonDetailsUseCase,
type GetSeasonDetailsInput, type GetSeasonDetailsInput,
type GetSeasonDetailsResult,
type GetSeasonDetailsErrorCode, type GetSeasonDetailsErrorCode,
} from './GetSeasonDetailsUseCase'; } from './GetSeasonDetailsUseCase';
import { Season } from '../../domain/entities/season/Season'; import { Season } from '../../domain/entities/season/Season';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
describe('GetSeasonDetailsUseCase', () => { describe('GetSeasonDetailsUseCase', () => {
let useCase: GetSeasonDetailsUseCase; let useCase: GetSeasonDetailsUseCase;
@@ -19,7 +19,7 @@ describe('GetSeasonDetailsUseCase', () => {
findById: vi.fn(), 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 () => { 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 { import {
GetSeasonSponsorshipsUseCase, GetSeasonSponsorshipsUseCase,
type GetSeasonSponsorshipsInput, type GetSeasonSponsorshipsInput,
type GetSeasonSponsorshipsResult,
type GetSeasonSponsorshipsErrorCode, type GetSeasonSponsorshipsErrorCode,
} from './GetSeasonSponsorshipsUseCase'; } from './GetSeasonSponsorshipsUseCase';
import type { SeasonSponsorshipRepository } from '../../domain/repositories/SeasonSponsorshipRepository'; import type { SeasonSponsorshipRepository } from '../../domain/repositories/SeasonSponsorshipRepository';
@@ -52,11 +51,13 @@ describe('GetSeasonSponsorshipsUseCase', () => {
findByLeagueId: vi.fn(), findByLeagueId: vi.fn(),
}; };
useCase = new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepository as any, useCase = new GetSeasonSponsorshipsUseCase(
seasonRepository as any, seasonSponsorshipRepository as unknown as SeasonSponsorshipRepository,
leagueRepository as any, seasonRepository as unknown as SeasonRepository,
leagueMembershipRepository as any, leagueRepository as unknown as LeagueRepository,
raceRepository as any); leagueMembershipRepository as unknown as LeagueMembershipRepository,
raceRepository as unknown as RaceRepository
);
}); });
it('returns SEASON_NOT_FOUND when season does not exist', async () => { 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) {} constructor(private readonly sponsorRepository: SponsorRepository) {}
async execute(_input: GetSponsorsInput): Promise<Result<GetSponsorsResult, ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>>> { async execute(_input: GetSponsorsInput): Promise<Result<GetSponsorsResult, ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>>> {
void _input;
try { try {
const sponsors = await this.sponsorRepository.findAll(); const sponsors = await this.sponsorRepository.findAll();
return Result.ok({ sponsors }); return Result.ok({ sponsors });

View File

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

View File

@@ -7,6 +7,10 @@ import {
type GetTeamMembersErrorCode, type GetTeamMembersErrorCode,
type GetTeamMembersInput type GetTeamMembersInput
} from './GetTeamMembersUseCase'; } 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', () => { describe('GetTeamMembersUseCase', () => {
let useCase: GetTeamMembersUseCase; let useCase: GetTeamMembersUseCase;
@@ -41,10 +45,12 @@ describe('GetTeamMembersUseCase', () => {
warn: vi.fn(), warn: vi.fn(),
error: vi.fn(), error: vi.fn(),
}; };
useCase = new GetTeamMembersUseCase(membershipRepository as any, useCase = new GetTeamMembersUseCase(
driverRepository as any, membershipRepository as unknown as TeamMembershipRepository,
teamRepository as any, driverRepository as unknown as DriverRepository,
logger as any); teamRepository as unknown as TeamRepository,
logger as unknown as Logger
);
}); });
it('should return team members with driver entities', async () => { 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 { Driver } from '../../domain/entities/Driver';
import type { Team } from '../../domain/entities/Team'; import type { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership'; 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 = { export type GetTeamMembersInput = {
teamId: string; teamId: string;

View File

@@ -1,15 +1,21 @@
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
GetTeamMembershipUseCase,
type GetTeamMembershipErrorCode,
type GetTeamMembershipInput
} from './GetTeamMembershipUseCase';
describe('GetTeamMembershipUseCase', () => { describe('GetTeamMembershipUseCase', () => {
const mockGetMembership = vi.fn(); 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, getMembership: mockGetMembership,
getActiveMembershipForDriver: vi.fn(), getActiveMembershipForDriver: vi.fn(),
getTeamMembers: vi.fn(), getTeamMembers: vi.fn(),
@@ -32,7 +38,7 @@ describe('GetTeamMembershipUseCase', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); 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 () => { it('should return membership data when membership exists', async () => {

View File

@@ -1,5 +1,8 @@
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 = { export type GetTeamMembershipInput = {
teamId: string; teamId: string;
driverId: string; driverId: string;

View File

@@ -7,10 +7,15 @@ import {
type GetTeamsLeaderboardInput type GetTeamsLeaderboardInput
} from './GetTeamsLeaderboardUseCase'; } from './GetTeamsLeaderboardUseCase';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
describe('GetTeamsLeaderboardUseCase', () => { describe('GetTeamsLeaderboardUseCase', () => {
let useCase: GetTeamsLeaderboardUseCase; let useCase: GetTeamsLeaderboardUseCase;
let teamRepository: { let teamRepository: {
findAll: Mock; findAll: Mock;
findById: Mock;
}; };
let teamMembershipRepository: { let teamMembershipRepository: {
getTeamMembers: Mock; getTeamMembers: Mock;
@@ -26,6 +31,7 @@ describe('GetTeamsLeaderboardUseCase', () => {
beforeEach(() => { beforeEach(() => {
teamRepository = { teamRepository = {
findAll: vi.fn(), findAll: vi.fn(),
findById: vi.fn(),
}; };
teamMembershipRepository = { teamMembershipRepository = {
getTeamMembers: vi.fn(), getTeamMembers: vi.fn(),
@@ -37,10 +43,11 @@ describe('GetTeamsLeaderboardUseCase', () => {
warn: vi.fn(), warn: vi.fn(),
error: vi.fn(), error: vi.fn(),
}; };
useCase = new GetTeamsLeaderboardUseCase(teamRepository as any, useCase = new GetTeamsLeaderboardUseCase(
teamMembershipRepository as any, teamRepository as unknown as TeamRepository,
teamMembershipRepository as unknown as TeamMembershipRepository,
getDriverStats as any, 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 { SkillLevelService, type SkillLevel } from '@core/racing/domain/services/SkillLevelService';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
interface DriverStatsAdapter { interface DriverStatsAdapter {
rating: number | null; rating: number | null;
@@ -55,7 +57,7 @@ export class GetTeamsLeaderboardUseCase {
const items: TeamLeaderboardItem[] = []; const items: TeamLeaderboardItem[] = [];
await Promise.all( await Promise.all(
allTeams.map(async (team) => { allTeams.map(async (team: Team) => {
const memberships = await this.teamMembershipRepository.getTeamMembers(team.id); const memberships = await this.teamMembershipRepository.getTeamMembers(team.id);
const memberCount = memberships.length; const memberCount = memberships.length;

View File

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

View File

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

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