website refactor

This commit is contained in:
2026-01-16 15:20:25 +01:00
parent 7e02fc3ea5
commit 37b1aa626c
325 changed files with 2167 additions and 2782 deletions

View File

@@ -1,14 +1,9 @@
import type { NotificationService } from '@core/notifications/application/ports/NotificationService';
import type { WalletRepository } from '@core/payments/domain/repositories/WalletRepository';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { LeagueWallet } from '../../domain/entities/league-wallet/LeagueWallet';
import { Season } from '../../domain/entities/season/Season';
import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
import type { LeagueWalletRepository } from '../../domain/repositories/LeagueWalletRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { SeasonSponsorshipRepository } from '../../domain/repositories/SeasonSponsorshipRepository';
import type { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import { Money } from '../../domain/value-objects/Money';
import { AcceptSponsorshipRequestUseCase } from './AcceptSponsorshipRequestUseCase';

View File

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

View File

@@ -1,10 +1,7 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { ApplyForSponsorshipUseCase } from './ApplyForSponsorshipUseCase';
import type { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import type { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import type { SponsorRepository } from '../../domain/repositories/SponsorRepository';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { Money } from '../../domain/value-objects/Money';
import { ApplyForSponsorshipUseCase } from './ApplyForSponsorshipUseCase';
describe('ApplyForSponsorshipUseCase', () => {
let mockSponsorshipRequestRepo: {

View File

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

View File

@@ -1,9 +1,6 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { ApplyPenaltyUseCase, type ApplyPenaltyResult } from './ApplyPenaltyUseCase';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { ApplyPenaltyUseCase } from './ApplyPenaltyUseCase';
describe('ApplyPenaltyUseCase', () => {
let mockPenaltyRepo: {
create: Mock;
@@ -46,15 +43,11 @@ describe('ApplyPenaltyUseCase', () => {
});
it('should return error when race does not exist', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue(null);
@@ -68,19 +61,15 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('RACE_NOT_FOUND');
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
});
it('should return error when steward does not have authority', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -102,19 +91,15 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('INSUFFICIENT_AUTHORITY');
expect(result.unwrapErr().code).toBe('INSUFFICIENT_AUTHORITY');
});
it('should return error when protest does not exist', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -138,19 +123,15 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('PROTEST_NOT_FOUND');
expect(result.unwrapErr().code).toBe('PROTEST_NOT_FOUND');
});
it('should return error when protest is not upheld', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -174,19 +155,15 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('PROTEST_NOT_UPHELD');
expect(result.unwrapErr().code).toBe('PROTEST_NOT_UPHELD');
});
it('should return error when protest is not for this race', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -210,19 +187,15 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(false);
expect(result.error!.code).toBe('PROTEST_NOT_FOR_RACE');
expect(result.unwrapErr().code).toBe('PROTEST_NOT_FOR_RACE');
});
it('should create penalty and return result on success', async () => {
const output: { present: Mock } = {
present: vi.fn(),
};
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as unknown as IPenaltyRepository,
mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository,
mockLogger as unknown as Logger);
const useCase = new ApplyPenaltyUseCase(mockPenaltyRepo as any,
mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any,
mockLogger as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -246,22 +219,11 @@ describe('ApplyPenaltyUseCase', () => {
});
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = (expect(presented).toEqual({ penaltyId: expect.any(String) });
const presented = result.unwrap();
expect(presented.penaltyId).toBeDefined();
expect(mockPenaltyRepo.create).toHaveBeenCalledTimes(1);
const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as unknown as {
leagueId: unknown;
raceId: unknown;
driverId: unknown;
type: string;
value?: number;
reason: string;
issuedBy: unknown;
status: unknown;
notes?: string;
};
const createdPenalty = (mockPenaltyRepo.create as Mock).mock.calls[0]?.[0] as any;
type ToStringable = { toString(): string };
const asString = (value: unknown): string => {
@@ -287,4 +249,4 @@ describe('ApplyPenaltyUseCase', () => {
expect(asString(createdPenalty.status)).toBe('pending');
expect(createdPenalty.notes).toBe('Test notes');
});
});
});

View File

@@ -5,15 +5,11 @@
* The penalty can be standalone or linked to an upheld protest.
*/
import { Penalty } from '../../domain/entities/penalty/Penalty';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { randomUUID } from 'crypto';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import { Result } from '@/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto';
import { Penalty } from '../../domain/entities/penalty/Penalty';
export interface ApplyPenaltyInput {
raceId: string;

View File

@@ -1,9 +1,8 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CancelRaceUseCase, type CancelRaceResult } from './CancelRaceUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType';
import { CancelRaceUseCase } from './CancelRaceUseCase';
describe('CancelRaceUseCase', () => {
let useCase: CancelRaceUseCase;
let raceRepository: {
@@ -16,6 +15,7 @@ describe('CancelRaceUseCase', () => {
info: Mock;
error: Mock;
};
beforeEach(() => {
raceRepository = {
findById: vi.fn(),
@@ -27,8 +27,8 @@ describe('CancelRaceUseCase', () => {
info: vi.fn(),
error: vi.fn(),
};
useCase = new CancelRaceUseCase(raceRepository as unknown as IRaceRepository,
logger as unknown as Logger);
useCase = new CancelRaceUseCase(raceRepository as any,
logger as any);
});
it('should cancel race successfully', async () => {
@@ -48,14 +48,14 @@ describe('CancelRaceUseCase', () => {
const result = await useCase.execute({ raceId, cancelledById: 'admin-1' });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(raceRepository.findById).toHaveBeenCalledWith(raceId);
expect(raceRepository.update).toHaveBeenCalledTimes(1);
const updatedRace = (raceRepository.update as Mock).mock.calls[0]?.[0] as Race;
expect(updatedRace.id).toBe(raceId);
expect(updatedRace.status.toString()).toBe('cancelled');
const presented = (expect(presented.race.id).toBe(raceId);
expect(presented.race.id).toBe(raceId);
expect(presented.race.status.toString()).toBe('cancelled');
});
@@ -67,7 +67,7 @@ describe('CancelRaceUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
});
});
it('should return domain error if race is already cancelled', async () => {
const raceId = 'race-1';
@@ -91,7 +91,7 @@ describe('CancelRaceUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toContain('already cancelled');
}
});
});
it('should return domain error if race is completed', async () => {
const raceId = 'race-1';
@@ -115,5 +115,5 @@ describe('CancelRaceUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toContain('completed race');
}
});
});
});
});

View File

@@ -1,5 +1,3 @@
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';

View File

@@ -1,13 +1,9 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CloseRaceEventStewardingUseCase, type CloseRaceEventStewardingResult } from './CloseRaceEventStewardingUseCase';
import type { RaceEventRepository } from '../../domain/repositories/RaceEventRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { DomainEventPublisher } from '@core/shared/domain/DomainEvent';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { RaceEvent } from '../../domain/entities/RaceEvent';
import { Session } from '../../domain/entities/Session';
import { SessionType } from '../../domain/value-objects/SessionType';
import { CloseRaceEventStewardingUseCase } from './CloseRaceEventStewardingUseCase';
describe('CloseRaceEventStewardingUseCase', () => {
let useCase: CloseRaceEventStewardingUseCase;
let raceEventRepository: {
@@ -27,6 +23,7 @@ describe('CloseRaceEventStewardingUseCase', () => {
let logger: {
error: Mock;
};
beforeEach(() => {
raceEventRepository = {
findAwaitingStewardingClose: vi.fn(),
@@ -45,11 +42,11 @@ describe('CloseRaceEventStewardingUseCase', () => {
logger = {
error: vi.fn(),
};
useCase = new CloseRaceEventStewardingUseCase(logger as unknown as Logger,
raceEventRepository as unknown as IRaceEventRepository,
raceRegistrationRepository as unknown as IRaceRegistrationRepository,
penaltyRepository as unknown as IPenaltyRepository,
domainEventPublisher as unknown as DomainEventPublisher);
useCase = new CloseRaceEventStewardingUseCase(logger as any,
raceEventRepository as any,
raceRegistrationRepository as any,
penaltyRepository as any,
domainEventPublisher as any);
});
it('should close stewarding for expired events successfully', async () => {
@@ -82,33 +79,27 @@ describe('CloseRaceEventStewardingUseCase', () => {
const result = await useCase.execute({ raceId: 'event-1', closedById: 'admin-1' });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(raceEventRepository.findAwaitingStewardingClose).toHaveBeenCalled();
expect(raceEventRepository.update).toHaveBeenCalledWith(
expect.objectContaining({ status: 'closed' })
);
expect(domainEventPublisher.publish).toHaveBeenCalled();
const presentedRace = (status?: unknown;
};
const presentedId =
presentedRace?.id && typeof presentedRace.id === 'object' && typeof presentedRace.id.toString === 'function'
? presentedRace.id.toString()
: presentedRace?.id;
expect(presentedId).toBe('event-1');
expect(presentedRace?.status).toBe('closed');
expect(presented.race.id).toBe('event-1');
expect(presented.race.status).toBe('closed');
});
it('should handle no expired events', async () => {
it('should return error when no expired events', async () => {
raceEventRepository.findAwaitingStewardingClose.mockResolvedValue([]);
const result = await useCase.execute({ raceId: 'event-1', closedById: 'admin-1' });
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
expect(raceEventRepository.update).not.toHaveBeenCalled();
expect(domainEventPublisher.publish).not.toHaveBeenCalled();
});
});
it('should return error when repository throws', async () => {
raceEventRepository.findAwaitingStewardingClose.mockRejectedValue(new Error('DB error'));
@@ -121,5 +112,5 @@ describe('CloseRaceEventStewardingUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toContain('DB error');
}
});
});
});
});

View File

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

View File

@@ -4,6 +4,7 @@ import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
describe('CompleteRaceUseCase', () => {
let useCase: CompleteRaceUseCase;
let raceRepository: {
@@ -21,7 +22,6 @@ describe('CompleteRaceUseCase', () => {
save: Mock;
};
let getDriverRating: Mock;
let output: { present: Mock };
beforeEach(() => {
raceRepository = {
@@ -39,11 +39,10 @@ describe('CompleteRaceUseCase', () => {
save: vi.fn(),
};
getDriverRating = vi.fn();
output = { present: vi.fn() };
useCase = new CompleteRaceUseCase(raceRepository as unknown as IRaceRepository,
raceRegistrationRepository as unknown as IRaceRegistrationRepository,
resultRepository as unknown as IResultRepository,
standingRepository as unknown as IStandingRepository,
useCase = new CompleteRaceUseCase(raceRepository as any,
raceRegistrationRepository as any,
resultRepository as any,
standingRepository as any,
getDriverRating);
});
@@ -73,7 +72,8 @@ describe('CompleteRaceUseCase', () => {
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(presented.raceId).toBe('race-1');
expect(raceRepository.findById).toHaveBeenCalledWith('race-1');
expect(raceRegistrationRepository.getRegisteredDrivers).toHaveBeenCalledWith('race-1');
expect(getDriverRating).toHaveBeenCalledTimes(2);
@@ -81,7 +81,7 @@ describe('CompleteRaceUseCase', () => {
expect(standingRepository.save).toHaveBeenCalledTimes(2);
expect(mockRace.complete).toHaveBeenCalled();
expect(raceRepository.update).toHaveBeenCalledWith({ id: 'race-1', status: 'completed' });
});
});
it('should return error when race does not exist', async () => {
const command: CompleteRaceInput = {
@@ -94,7 +94,7 @@ describe('CompleteRaceUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('RACE_NOT_FOUND');
});
});
it('should return error when no registered drivers', async () => {
const command: CompleteRaceInput = {
@@ -114,7 +114,7 @@ describe('CompleteRaceUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('NO_REGISTERED_DRIVERS');
});
});
it('should return error when repository throws', async () => {
const command: CompleteRaceInput = {
@@ -138,5 +138,5 @@ describe('CompleteRaceUseCase', () => {
const error = result.unwrapErr();
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details?.message).toBe('DB error');
});
});
});
});

View File

@@ -1,19 +1,10 @@
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
export type CreateLeagueSeasonScheduleRaceInput = {
leagueId: string;
seasonId: string;
track: string;
car: string;
scheduledAt: Date;
};
export type CreateLeagueSeasonScheduleRaceResult = {
raceId: string;
@@ -96,9 +87,7 @@ export class CreateLeagueSeasonScheduleRaceUseCase {
}
}
private isWithinSeasonWindow(_season: Season, _scheduledAt: Date): boolean {
// Implementation would check if scheduledAt is within season's schedule window
// For now, return true as a placeholder
return true;
private isWithinSeasonWindow(season: Season, scheduledAt: Date): boolean {
return scheduledAt >= season.startDate && scheduledAt <= season.endDate;
}
}

View File

@@ -1,12 +1,8 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
CreateLeagueWithSeasonAndScoringUseCase,
type CreateLeagueWithSeasonAndScoringCommand,
type CreateLeagueWithSeasonAndScoringResult,
CreateLeagueWithSeasonAndScoringUseCase,
type CreateLeagueWithSeasonAndScoringCommand
} from './CreateLeagueWithSeasonAndScoringUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository';
describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
let useCase: CreateLeagueWithSeasonAndScoringUseCase;
@@ -26,7 +22,6 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
warn: Mock;
error: Mock;
};
let output: { present: Mock } ;
beforeEach(() => {
leagueRepository = {
@@ -45,12 +40,11 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
output = { present: vi.fn() } as unknown as typeof output;
useCase = new CreateLeagueWithSeasonAndScoringUseCase(leagueRepository as unknown as ILeagueRepository,
seasonRepository as unknown as ISeasonRepository,
leagueScoringConfigRepository as unknown as ILeagueScoringConfigRepository,
useCase = new CreateLeagueWithSeasonAndScoringUseCase(leagueRepository as any,
seasonRepository as any,
leagueScoringConfigRepository as any,
getLeagueScoringPresetById,
logger as unknown as Logger);
logger as any);
});
it('should create league, season, and scoring successfully', async () => {
@@ -82,11 +76,11 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = (expect(presented?.league.id.toString()).toBeDefined();
expect(presented?.season.id).toBeDefined();
expect(presented?.scoringConfig.seasonId.toString()).toBe(presented?.season.id);
expect(presented.league.id).toBeDefined();
expect(presented.season.id).toBeDefined();
expect(presented.scoringConfig.seasonId.toString()).toBe(presented.season.id);
expect(leagueRepository.create).toHaveBeenCalledTimes(1);
expect(seasonRepository.create).toHaveBeenCalledTimes(1);
expect(leagueScoringConfigRepository.save).toHaveBeenCalledTimes(1);
@@ -113,7 +107,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('League name is required');
}
});
});
it('should return error when ownerId is empty', async () => {
const command = {
@@ -136,7 +130,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('League ownerId is required');
}
});
});
it('should return error when gameId is empty', async () => {
const command = {
@@ -159,7 +153,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('gameId is required');
}
});
});
it('should return error when visibility is missing', async () => {
const command: Partial<CreateLeagueWithSeasonAndScoringCommand> = {
@@ -180,7 +174,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('visibility is required');
}
});
});
it('should return error when maxDrivers is invalid', async () => {
const command = {
@@ -204,7 +198,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('maxDrivers must be greater than 0 when provided');
}
});
});
it('should return error when ranked league has insufficient drivers', async () => {
const command = {
@@ -228,7 +222,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toContain('Ranked leagues require at least 10 drivers');
}
});
});
it('should return error when scoring preset is unknown', async () => {
const command = {
@@ -254,7 +248,7 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('Unknown scoring preset: unknown-preset');
}
});
});
it('should return error when repository throws', async () => {
const command = {
@@ -285,5 +279,5 @@ describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
expect(err.details.message).toBe('DB error');
}
});
});
});
});

View File

@@ -1,21 +1,18 @@
import { v4 as uuidv4 } from 'uuid';
import { League } from '../../domain/entities/League';
import { Season } from '../../domain/entities/season/Season';
import { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository';
import type { Logger } from '@core/shared/application';
import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig';
import type { SessionType } from '../../domain/types/SessionType';
import type { BonusRule } from '../../domain/types/BonusRule';
import { PointsTable } from '../../domain/value-objects/PointsTable';
import {
LeagueVisibility,
MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility';
import { League } from '@/racing/domain/entities/League';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuidv4 } from 'uuid';
import { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig';
import { Season } from '../../domain/entities/season/Season';
import type { BonusRule } from '../../domain/types/BonusRule';
import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig';
import type { SessionType } from '../../domain/types/SessionType';
import {
LeagueVisibility,
MIN_RANKED_LEAGUE_DRIVERS,
} from '../../domain/value-objects/LeagueVisibility';
import { PointsTable } from '../../domain/value-objects/PointsTable';
export type CreateLeagueWithSeasonAndScoringCommand = {
name: string;

View File

@@ -1,5 +1,4 @@
import { describe, it, expect, vi, Mock } from 'vitest';
import { describe, it, expect, vi, Mock, beforeEach } from 'vitest';
import { Season } from '@core/racing/domain/entities/season/Season';
import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
@@ -75,7 +74,7 @@ type CreateSeasonErrorCode = ApplicationErrorCode<'LEAGUE_NOT_FOUND' | 'VALIDATI
describe('CreateSeasonForLeagueUseCase', () => {
const mockLeagueFindById = vi.fn();
const mockLeagueRepo: ILeagueRepository = {
const mockLeagueRepo: any = {
findById: mockLeagueFindById,
findAll: vi.fn(),
findByOwnerId: vi.fn(),
@@ -88,7 +87,7 @@ describe('CreateSeasonForLeagueUseCase', () => {
const mockSeasonFindById = vi.fn();
const mockSeasonAdd = vi.fn();
const mockSeasonRepo: ISeasonRepository = {
const mockSeasonRepo: any = {
findById: mockSeasonFindById,
findByLeagueId: vi.fn(),
create: vi.fn(),
@@ -98,11 +97,8 @@ describe('CreateSeasonForLeagueUseCase', () => {
listActiveByLeague: vi.fn(),
};
let output: { present: Mock } ;
beforeEach(() => {
vi.clearAllMocks();
output = { present: vi.fn() } as unknown as typeof output;
});
it('creates a planned Season for an existing league with config-derived props', async () => {
@@ -125,8 +121,6 @@ describe('CreateSeasonForLeagueUseCase', () => {
strategy: 'dropWorstN',
n: 2,
},
// Intentionally omit seasonStartDate / raceStartTime to avoid schedule derivation,
// focusing this test on scoring/drop/stewarding/maxDrivers mapping.
timings: {
qualifyingMinutes: 10,
mainRaceMinutes: 30,
@@ -141,13 +135,13 @@ describe('CreateSeasonForLeagueUseCase', () => {
config,
};
const result: Result<void, CreateSeasonErrorCode> = await useCase.execute(command);
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = (expect(presented?.season).toBeInstanceOf(Season);
expect(presented?.league.id).toBe('league-1');
expect(presented.season).toBeInstanceOf(Season);
expect(presented.league.id).toBe('league-1');
});
it('clones configuration from a source season when sourceSeasonId is provided', async () => {
@@ -172,15 +166,15 @@ describe('CreateSeasonForLeagueUseCase', () => {
sourceSeasonId: 'source-season',
};
const result: Result<void, CreateSeasonErrorCode> = await useCase.execute(command);
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = (expect(presented?.season.maxDrivers).toBe(40);
expect(presented.season.maxDrivers).toBe(40);
});
it('returns error when league not found and does not call output', async () => {
it('returns error when league not found', async () => {
mockLeagueFindById.mockResolvedValue(null);
const useCase = new CreateSeasonForLeagueUseCase(mockLeagueRepo, mockSeasonRepo);
@@ -191,15 +185,15 @@ describe('CreateSeasonForLeagueUseCase', () => {
gameId: 'iracing',
};
const result: Result<void, CreateSeasonErrorCode> = await useCase.execute(command);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details?.message).toBe('League not found: missing-league');
});
});
it('returns validation error when source season is missing and does not call output', async () => {
it('returns validation error when source season is missing', async () => {
mockLeagueFindById.mockResolvedValue({ id: 'league-1' });
mockSeasonFindById.mockResolvedValue(undefined);
@@ -212,11 +206,11 @@ describe('CreateSeasonForLeagueUseCase', () => {
sourceSeasonId: 'missing-source',
};
const result: Result<void, CreateSeasonErrorCode> = await useCase.execute(command);
const result = await useCase.execute(command);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr();
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.details?.message).toBe('Source Season not found: missing-source');
});
})
});
});

View File

@@ -1,7 +1,6 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { CreateSponsorUseCase, type CreateSponsorInput } from './CreateSponsorUseCase';
import type { SponsorRepository } from '../../domain/repositories/SponsorRepository';
import type { Logger } from '@core/shared/application';
describe('CreateSponsorUseCase', () => {
let useCase: CreateSponsorUseCase;

View File

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

View File

@@ -1,12 +1,9 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
CreateTeamUseCase,
type CreateTeamInput,
type CreateTeamResult,
CreateTeamUseCase,
type CreateTeamInput
} from './CreateTeamUseCase';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/application';
describe('CreateTeamUseCase', () => {
let useCase: CreateTeamUseCase;
let teamRepository: {
@@ -22,7 +19,6 @@ describe('CreateTeamUseCase', () => {
warn: Mock;
error: Mock;
};
let output: { present: Mock };
beforeEach(() => {
teamRepository = {
@@ -38,10 +34,9 @@ describe('CreateTeamUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
output = { present: vi.fn() };
useCase = new CreateTeamUseCase(teamRepository as unknown as ITeamRepository,
membershipRepository as unknown as ITeamMembershipRepository,
logger as unknown as Logger);
useCase = new CreateTeamUseCase(teamRepository as any,
membershipRepository as any,
logger as any);
});
it('should create team successfully', async () => {
@@ -69,10 +64,11 @@ describe('CreateTeamUseCase', () => {
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(presented.team.id).toBe('team-uuid');
expect(teamRepository.create).toHaveBeenCalledTimes(1);
expect(membershipRepository.saveMembership).toHaveBeenCalledTimes(1);
});
});
it('should return error when driver already belongs to a team', async () => {
const command: CreateTeamInput = {
@@ -100,7 +96,7 @@ describe('CreateTeamUseCase', () => {
);
expect(teamRepository.create).not.toHaveBeenCalled();
expect(membershipRepository.saveMembership).not.toHaveBeenCalled();
});
});
it('should return error when repository throws', async () => {
const command: CreateTeamInput = {
@@ -119,5 +115,5 @@ describe('CreateTeamUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
expect(result.unwrapErr().details?.message).toBe('DB error');
});
});
});
});

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ describe('FileProtestUseCase', () => {
let mockLeagueMembershipRepo: {
getLeagueMembers: Mock;
};
beforeEach(() => {
mockProtestRepo = {
create: vi.fn(),
@@ -25,12 +26,12 @@ describe('FileProtestUseCase', () => {
mockLeagueMembershipRepo = {
getLeagueMembers: vi.fn(),
};
});
});
it('should return error when race does not exist', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository);
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
mockRaceRepo.findById.mockResolvedValue(null);
@@ -45,12 +46,12 @@ describe('FileProtestUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<FileProtestErrorCode, { message: string }>;
expect(err.code).toBe('RACE_NOT_FOUND');
expect(err.details?.message).toBe('Race not found');
});
});
it('should return error when protesting against self', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository);
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
@@ -65,12 +66,12 @@ describe('FileProtestUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<FileProtestErrorCode, { message: string }>;
expect(err.code).toBe('SELF_PROTEST');
expect(err.details?.message).toBe('Cannot file a protest against yourself');
});
});
it('should return error when protesting driver is not an active member', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository);
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -88,12 +89,12 @@ describe('FileProtestUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<FileProtestErrorCode, { message: string }>;
expect(err.code).toBe('NOT_MEMBER');
expect(err.details?.message).toBe('Protesting driver is not an active member of this league');
});
});
it('should create protest and return protestId on success', async () => {
const useCase = new FileProtestUseCase(mockProtestRepo as unknown as IProtestRepository,
mockRaceRepo as unknown as IRaceRepository,
mockLeagueMembershipRepo as unknown as ILeagueMembershipRepository);
const useCase = new FileProtestUseCase(mockProtestRepo as any,
mockRaceRepo as any,
mockLeagueMembershipRepo as any);
mockRaceRepo.findById.mockResolvedValue({ id: 'race1', leagueId: 'league1' });
mockLeagueMembershipRepo.getLeagueMembers.mockResolvedValue([
@@ -111,21 +112,9 @@ describe('FileProtestUseCase', () => {
} as FileProtestInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(mockProtestRepo.create).toHaveBeenCalledTimes(1);
const created = (mockProtestRepo.create as unknown as Mock).mock.calls[0]?.[0] as unknown as {
raceId: { toString(): string };
protestingDriverId: { toString(): string };
accusedDriverId: { toString(): string };
comment?: string;
proofVideoUrl: { toString(): string };
status: { toString(): string };
incident: {
lap: { toNumber(): number };
description: { toString(): string };
timeInRace?: unknown;
};
};
const created = (mockProtestRepo.create as unknown as Mock).mock.calls[0]?.[0] as any;
expect(created.raceId.toString()).toBe('race1');
expect(created.protestingDriverId.toString()).toBe('driver1');
@@ -136,16 +125,14 @@ describe('FileProtestUseCase', () => {
expect(created.incident.lap.toNumber()).toBe(5);
expect(created.incident.description.toString()).toBe('Collision');
expect(created.incident.timeInRace).toBeUndefined();
const presented = (expect(presented.protest.raceId.toString()).toBe('race1');
expect(presented.protest.raceId.toString()).toBe('race1');
expect(presented.protest.protestingDriverId.toString()).toBe('driver1');
expect(presented.protest.accusedDriverId.toString()).toBe('driver2');
expect(presented.protest.incident.lap.toNumber()).toBe(5);
expect(presented.protest.incident.description.toString()).toBe('Collision');
expect(presented.protest.incident.timeInRace).toBeUndefined();
expect(presented.protest.comment).toBe('Test comment');
expect(presented.protest.proofVideoUrl).toBeDefined();
expect(presented.protest.proofVideoUrl!.toString()).toBe('http://example.com/video');
});
});
});

View File

@@ -16,23 +16,23 @@ describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => {
let mockSeasonRepo: { findByLeagueId: Mock };
let mockScoringConfigRepo: { findBySeasonId: Mock };
let mockGameRepo: { findById: Mock };
beforeEach(() => {
mockLeagueRepo = { findAll: vi.fn() };
mockMembershipRepo = { getLeagueMembers: vi.fn() };
mockSeasonRepo = { findByLeagueId: vi.fn() };
mockScoringConfigRepo = { findBySeasonId: vi.fn() };
mockGameRepo = { findById: vi.fn() };
output = { present: vi.fn() } as unknown as typeof output;
});
it('should return enriched leagues with capacity and scoring', async () => {
const useCase = new GetAllLeaguesWithCapacityAndScoringUseCase(mockLeagueRepo as unknown as ILeagueRepository,
mockMembershipRepo as unknown as ILeagueMembershipRepository,
mockSeasonRepo as unknown as ISeasonRepository,
mockScoringConfigRepo as unknown as ILeagueScoringConfigRepository,
mockGameRepo as unknown as IGameRepository,
{ getPresetById: vi.fn().mockReturnValue({ id: 'preset1', name: 'Default' }) },
output,
const useCase = new GetAllLeaguesWithCapacityAndScoringUseCase(
mockLeagueRepo as any,
mockMembershipRepo as any,
mockSeasonRepo as any,
mockScoringConfigRepo as any,
mockGameRepo as any,
{ getPresetById: vi.fn().mockReturnValue({ id: 'preset1', name: 'Default' }) }
);
const league = { id: 'league1', name: 'Test League', settings: { maxDrivers: 30 } };
@@ -53,19 +53,18 @@ describe('GetAllLeaguesWithCapacityAndScoringUseCase', () => {
const result = await useCase.execute({} as GetAllLeaguesWithCapacityAndScoringInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented =
expect(presented?.leagues).toHaveLength(1);
expect(presented.leagues).toHaveLength(1);
const [summary] = presented?.leagues ?? [];
const summary = presented.leagues[0];
expect(summary?.league).toEqual(league);
expect(summary?.currentDrivers).toBe(2);
expect(summary?.maxDrivers).toBe(30);
expect(summary?.season).toEqual(season);
expect(summary?.scoringConfig).toEqual(scoringConfig);
expect(summary?.game).toEqual(game);
expect(summary?.preset).toEqual({ id: 'preset1', name: 'Default' });
expect(summary.league).toEqual(league);
expect(summary.currentDrivers).toBe(2);
expect(summary.maxDrivers).toBe(30);
expect(summary.season).toEqual(season);
expect(summary.scoringConfig).toEqual(scoringConfig);
expect(summary.game).toEqual(game);
expect(summary.preset).toEqual({ id: 'preset1', name: 'Default' });
});
});

View File

@@ -1,18 +1,15 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
GetAllRacesPageDataUseCase,
type GetAllRacesPageDataResult,
type GetAllRacesPageDataInput,
} from './GetAllRacesPageDataUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/application';
import { Race } from '../../domain/entities/Race';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { League } from '../../domain/entities/League';
import { Race } from '../../domain/entities/Race';
import {
GetAllRacesPageDataUseCase,
type GetAllRacesPageDataInput
} from './GetAllRacesPageDataUseCase';
describe('GetAllRacesPageDataUseCase', () => {
const mockRaceFindAll = vi.fn();
const mockRaceRepo: IRaceRepository = {
const mockRaceRepo: any = {
findById: vi.fn(),
findAll: mockRaceFindAll,
findByLeagueId: vi.fn(),
@@ -27,7 +24,7 @@ describe('GetAllRacesPageDataUseCase', () => {
};
const mockLeagueFindAll = vi.fn();
const mockLeagueRepo: ILeagueRepository = {
const mockLeagueRepo: any = {
findById: vi.fn(),
findAll: mockLeagueFindAll,
findByOwnerId: vi.fn(),
@@ -47,9 +44,9 @@ describe('GetAllRacesPageDataUseCase', () => {
beforeEach(() => {
vi.clearAllMocks();
});
});
it('should present races and filters data', async () => {
it('should return races and filters data', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
@@ -95,8 +92,8 @@ describe('GetAllRacesPageDataUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.races).toEqual([
const presented = result.unwrap();
expect(presented.races).toEqual([
{
id: 'race2',
track: 'Track B',
@@ -134,7 +131,7 @@ describe('GetAllRacesPageDataUseCase', () => {
});
});
it('should present empty result when no races or leagues', async () => {
it('should return empty result when no races or leagues', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
@@ -145,8 +142,8 @@ describe('GetAllRacesPageDataUseCase', () => {
const result = await useCase.execute({});
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.races).toEqual([]);
const presented = result.unwrap();
expect(presented.races).toEqual([]);
expect(presented.filters).toEqual({
statuses: [
{ value: 'all', label: 'All Statuses' },
@@ -159,7 +156,7 @@ describe('GetAllRacesPageDataUseCase', () => {
});
});
it('should return error when repository throws and not present data', async () => {
it('should return error when repository throws', async () => {
const useCase = new GetAllRacesPageDataUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
@@ -173,5 +170,5 @@ describe('GetAllRacesPageDataUseCase', () => {
const err = result.unwrapErr();
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});

View File

@@ -1,6 +1,3 @@
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceStatusValue } from '../../domain/entities/Race';

View File

@@ -1,18 +1,17 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
GetAllRacesUseCase,
type GetAllRacesResult,
type GetAllRacesInput,
} from './GetAllRacesUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/application';
import { Race } from '../../domain/entities/Race';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { League } from '../../domain/entities/League';
import { Race } from '../../domain/entities/Race';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import {
GetAllRacesUseCase,
type GetAllRacesInput
} from './GetAllRacesUseCase';
describe('GetAllRacesUseCase', () => {
const mockRaceFindAll = vi.fn();
const mockRaceRepo: IRaceRepository = {
const mockRaceRepo: RaceRepository = {
findById: vi.fn(),
findAll: mockRaceFindAll,
findByLeagueId: vi.fn(),
@@ -27,7 +26,7 @@ describe('GetAllRacesUseCase', () => {
};
const mockLeagueFindAll = vi.fn();
const mockLeagueRepo: ILeagueRepository = {
const mockLeagueRepo: LeagueRepository = {
findById: vi.fn(),
findAll: mockLeagueFindAll,
findByOwnerId: vi.fn(),
@@ -47,13 +46,12 @@ describe('GetAllRacesUseCase', () => {
beforeEach(() => {
vi.clearAllMocks();
});
});
it('should present domain races and leagues data', async () => {
it('should return domain races and leagues data', async () => {
const useCase = new GetAllRacesUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
useCase.setOutput(output);
const race1 = Race.create({
id: 'race1',
@@ -95,17 +93,16 @@ describe('GetAllRacesUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.totalCount).toBe(2);
const presented = result.unwrap();
expect(presented.totalCount).toBe(2);
expect(presented.races).toEqual([race1, race2]);
expect(presented.leagues).toEqual([league1, league2]);
});
it('should present empty result when no races or leagues', async () => {
it('should return empty result when no races or leagues', async () => {
const useCase = new GetAllRacesUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
useCase.setOutput(output);
mockRaceFindAll.mockResolvedValue([]);
mockLeagueFindAll.mockResolvedValue([]);
@@ -113,13 +110,13 @@ describe('GetAllRacesUseCase', () => {
const result = await useCase.execute({});
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.totalCount).toBe(0);
const presented = result.unwrap();
expect(presented.totalCount).toBe(0);
expect(presented.races).toEqual([]);
expect(presented.leagues).toEqual([]);
});
it('should return error when repository throws and not present data', async () => {
it('should return error when repository throws', async () => {
const useCase = new GetAllRacesUseCase(mockRaceRepo,
mockLeagueRepo,
mockLogger);
@@ -133,5 +130,5 @@ describe('GetAllRacesUseCase', () => {
const err = result.unwrapErr();
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});

View File

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

View File

@@ -1,13 +1,13 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { GetAllTeamsUseCase, type GetAllTeamsInput, type GetAllTeamsResult } from './GetAllTeamsUseCase';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamStatsRepository } from '../../domain/repositories/TeamStatsRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { Logger } from '@core/shared/application';
import { GetAllTeamsUseCase, type GetAllTeamsInput } from './GetAllTeamsUseCase';
describe('GetAllTeamsUseCase', () => {
const mockTeamFindAll = vi.fn();
const mockTeamRepo: ITeamRepository = {
const mockTeamRepo: TeamRepository = {
findById: vi.fn(),
findAll: mockTeamFindAll,
findByLeagueId: vi.fn(),
@@ -18,7 +18,7 @@ describe('GetAllTeamsUseCase', () => {
};
const mockTeamMembershipCountByTeamId = vi.fn();
const mockTeamMembershipRepo: ITeamMembershipRepository = {
const mockTeamMembershipRepo: TeamMembershipRepository = {
getMembership: vi.fn(),
getActiveMembershipForDriver: vi.fn(),
getTeamMembers: vi.fn(),
@@ -30,28 +30,13 @@ describe('GetAllTeamsUseCase', () => {
removeJoinRequest: vi.fn(),
};
const mockTeamStatsRepo: ITeamStatsRepository = {
const mockTeamStatsRepo: TeamStatsRepository = {
getTeamStats: vi.fn(),
saveTeamStats: vi.fn(),
getAllStats: vi.fn(),
clear: vi.fn(),
};
const mockResultRepo: IResultRepository = {
findAll: vi.fn(),
findById: vi.fn(),
findByRaceId: vi.fn(),
findByDriverId: vi.fn(),
findByDriverIdAndLeagueId: vi.fn(),
create: vi.fn(),
createMany: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
deleteByRaceId: vi.fn(),
exists: vi.fn(),
existsByRaceId: vi.fn(),
};
const mockLogger: Logger = {
debug: vi.fn(),
info: vi.fn(),
@@ -61,20 +46,19 @@ describe('GetAllTeamsUseCase', () => {
beforeEach(() => {
vi.clearAllMocks();
});
});
it('should return teams data', async () => {
const useCase = new GetAllTeamsUseCase(mockTeamRepo,
mockTeamMembershipRepo,
mockTeamStatsRepo,
mockResultRepo,
mockLogger);
const team1 = {
id: 'team1',
name: { props: 'Team One' },
tag: { props: 'TO' },
description: { props: 'Description One' },
id: { toString: () => 'team1' },
name: { toString: () => 'Team One' },
tag: { toString: () => 'TO' },
description: { toString: () => 'Description One' },
ownerId: { toString: () => 'owner1' },
leagues: [{ toString: () => 'league1' }],
createdAt: { toDate: () => new Date('2023-01-01T00:00:00Z') },
@@ -83,10 +67,10 @@ describe('GetAllTeamsUseCase', () => {
isRecruiting: false,
};
const team2 = {
id: 'team2',
name: { props: 'Team Two' },
tag: { props: 'TT' },
description: { props: 'Description Two' },
id: { toString: () => 'team2' },
name: { toString: () => 'Team Two' },
tag: { toString: () => 'TT' },
description: { toString: () => 'Description Two' },
ownerId: { toString: () => 'owner2' },
leagues: [{ toString: () => 'league2' }],
createdAt: { toDate: () => new Date('2023-01-02T00:00:00Z') },
@@ -98,7 +82,6 @@ describe('GetAllTeamsUseCase', () => {
mockTeamFindAll.mockResolvedValue([team1, team2]);
mockTeamMembershipCountByTeamId.mockImplementation((id: string) => Promise.resolve(id === 'team1' ? 5 : 3));
// Provide precomputed stats so the use case doesn't compute from results.
(mockTeamStatsRepo.getTeamStats as unknown as Mock).mockImplementation((teamId: string) =>
Promise.resolve(
teamId === 'team1'
@@ -126,62 +109,21 @@ describe('GetAllTeamsUseCase', () => {
const result = await useCase.execute({} as GetAllTeamsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented).toEqual({
teams: [
{
id: 'team1',
name: 'Team One',
tag: 'TO',
description: 'Description One',
ownerId: 'owner1',
leagues: ['league1'],
createdAt: new Date('2023-01-01T00:00:00Z'),
memberCount: 5,
totalWins: 2,
totalRaces: 10,
performanceLevel: 'intermediate',
specialization: 'mixed',
region: 'EU',
languages: ['en'],
logoRef: team1.logoRef,
logoUrl: null,
rating: 1200,
category: undefined,
isRecruiting: false,
},
{
id: 'team2',
name: 'Team Two',
tag: 'TT',
description: 'Description Two',
ownerId: 'owner2',
leagues: ['league2'],
createdAt: new Date('2023-01-02T00:00:00Z'),
memberCount: 3,
totalWins: 5,
totalRaces: 20,
performanceLevel: 'advanced',
specialization: 'mixed',
region: 'US',
languages: ['en', 'de'],
logoRef: team2.logoRef,
logoUrl: null,
rating: 1400,
category: undefined,
isRecruiting: true,
},
],
totalCount: 2,
});
expect(presented.totalCount).toBe(2);
expect(presented.teams[0].team).toBe(team1);
expect(presented.teams[0].memberCount).toBe(5);
expect(presented.teams[0].rating).toBe(1200);
expect(presented.teams[1].team).toBe(team2);
expect(presented.teams[1].memberCount).toBe(3);
expect(presented.teams[1].rating).toBe(1400);
});
it('should return empty result when no teams', async () => {
const useCase = new GetAllTeamsUseCase(mockTeamRepo,
mockTeamMembershipRepo,
mockTeamStatsRepo,
mockResultRepo,
mockLogger);
mockTeamFindAll.mockResolvedValue([]);
@@ -189,19 +131,16 @@ describe('GetAllTeamsUseCase', () => {
const result = await useCase.execute({} as GetAllTeamsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented).toEqual({
teams: [],
totalCount: 0,
});
expect(presented.teams).toEqual([]);
expect(presented.totalCount).toBe(0);
});
it('should return error when repository throws', async () => {
const useCase = new GetAllTeamsUseCase(mockTeamRepo,
mockTeamMembershipRepo,
mockTeamStatsRepo,
mockResultRepo,
mockLogger);
const error = new Error('Repository error');
@@ -214,6 +153,5 @@ describe('GetAllTeamsUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});

View File

@@ -1,10 +1,7 @@
import { Team } from '@/racing/domain/entities/Team';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { TeamStatsRepository } from '../../domain/repositories/TeamStatsRepository';
import type { Team } from '../../domain/entities/Team';
export interface GetAllTeamsInput {}

View File

@@ -1,8 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { GetDriverLiveriesUseCase, type GetDriverLiveriesInput } from './GetDriverLiveriesUseCase';
import type { LiveryRepository } from '../../domain/repositories/LiveryRepository';
import type { Logger } from '@core/shared/domain/Logger';
import { describe, expect, it, vi } from 'vitest';
import type { DriverLivery } from '../../domain/entities/DriverLivery';
import type { Logger } from '@core/shared/application';
import { GetDriverLiveriesUseCase, type GetDriverLiveriesInput } from './GetDriverLiveriesUseCase';
describe('GetDriverLiveriesUseCase', () => {
const mockLiveryRepository: ILiveryRepository = {

View File

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

View File

@@ -1,43 +1,42 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { GetDriverTeamUseCase, type GetDriverTeamInput, type GetDriverTeamResult } from './GetDriverTeamUseCase';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { GetDriverTeamUseCase, type GetDriverTeamInput } from './GetDriverTeamUseCase';
describe('GetDriverTeamUseCase', () => {
const mockFindById = vi.fn();
const mockGetActiveMembershipForDriver = vi.fn();
const mockTeamRepo: ITeamRepository = {
findById: mockFindById,
findAll: vi.fn(),
findByLeagueId: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
};
const mockMembershipRepo: ITeamMembershipRepository = {
getActiveMembershipForDriver: mockGetActiveMembershipForDriver,
getMembership: vi.fn(),
getTeamMembers: vi.fn(),
saveMembership: vi.fn(),
removeMembership: vi.fn(),
getJoinRequests: vi.fn(),
countByTeamId: vi.fn(),
saveJoinRequest: vi.fn(),
removeJoinRequest: vi.fn(),
};
const mockLogger: Logger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
let mockTeamRepo: any;
let mockMembershipRepo: any;
let mockLogger: Logger;
beforeEach(() => {
vi.clearAllMocks();
});
mockTeamRepo = {
findById: vi.fn(),
findAll: vi.fn(),
findByLeagueId: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
};
mockMembershipRepo = {
getActiveMembershipForDriver: vi.fn(),
getMembership: vi.fn(),
getTeamMembers: vi.fn(),
saveMembership: vi.fn(),
removeMembership: vi.fn(),
getJoinRequests: vi.fn(),
countByTeamId: vi.fn(),
saveJoinRequest: vi.fn(),
removeJoinRequest: vi.fn(),
};
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
});
it('should return driver team data when membership and team exist', async () => {
const useCase = new GetDriverTeamUseCase(mockTeamRepo,
@@ -48,15 +47,15 @@ describe('GetDriverTeamUseCase', () => {
const membership = { id: 'membership1', driverId, teamId: 'team1' };
const team = { id: 'team1', name: 'Team One' };
mockGetActiveMembershipForDriver.mockResolvedValue(membership);
mockFindById.mockResolvedValue(team);
mockMembershipRepo.getActiveMembershipForDriver.mockResolvedValue(membership);
mockTeamRepo.findById.mockResolvedValue(team);
const input: GetDriverTeamInput = { driverId };
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = (expect(presented.driverId).toBe(driverId);
const presented = result.unwrap();
expect(presented.driverId).toBe(driverId);
expect(presented.team).toBe(team);
expect(presented.membership).toBe(membership);
});
@@ -68,7 +67,7 @@ describe('GetDriverTeamUseCase', () => {
const driverId = 'driver1';
mockGetActiveMembershipForDriver.mockResolvedValue(null);
mockMembershipRepo.getActiveMembershipForDriver.mockResolvedValue(null);
const input: GetDriverTeamInput = { driverId };
const result = await useCase.execute(input);
@@ -76,7 +75,7 @@ describe('GetDriverTeamUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('MEMBERSHIP_NOT_FOUND');
expect(result.unwrapErr().details.message).toBe('No active membership found for driver driver1');
});
});
it('should return error when team not found', async () => {
const useCase = new GetDriverTeamUseCase(mockTeamRepo,
@@ -86,8 +85,8 @@ describe('GetDriverTeamUseCase', () => {
const driverId = 'driver1';
const membership = { id: 'membership1', driverId, teamId: 'team1' };
mockGetActiveMembershipForDriver.mockResolvedValue(membership);
mockFindById.mockResolvedValue(null);
mockMembershipRepo.getActiveMembershipForDriver.mockResolvedValue(membership);
mockTeamRepo.findById.mockResolvedValue(null);
const input: GetDriverTeamInput = { driverId };
const result = await useCase.execute(input);
@@ -95,7 +94,7 @@ describe('GetDriverTeamUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('TEAM_NOT_FOUND');
expect(result.unwrapErr().details.message).toBe('Team not found for teamId team1');
});
});
it('should return error when repository throws', async () => {
const useCase = new GetDriverTeamUseCase(mockTeamRepo,
@@ -105,7 +104,7 @@ describe('GetDriverTeamUseCase', () => {
const driverId = 'driver1';
const error = new Error('Repository error');
mockGetActiveMembershipForDriver.mockRejectedValue(error);
mockMembershipRepo.getActiveMembershipForDriver.mockRejectedValue(error);
const input: GetDriverTeamInput = { driverId };
const result = await useCase.execute(input);
@@ -113,5 +112,5 @@ describe('GetDriverTeamUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
expect(result.unwrapErr().details.message).toBe('Repository error');
});
});
});
});

View File

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

View File

@@ -1,13 +1,9 @@
import { describe, it, expect, vi } from 'vitest';
import type { Logger } from '@core/shared/domain/Logger';
import { describe, expect, it, vi } from 'vitest';
import {
GetDriversLeaderboardUseCase,
type GetDriversLeaderboardInput,
type GetDriversLeaderboardResult
GetDriversLeaderboardUseCase,
type GetDriversLeaderboardInput
} from './GetDriversLeaderboardUseCase';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { RankingUseCase } from './RankingUseCase';
import type { DriverStatsUseCase } from './DriverStatsUseCase';
import type { Logger } from '@core/shared/application';
describe('GetDriversLeaderboardUseCase', () => {
const mockDriverFindAll = vi.fn();

View File

@@ -1,25 +1,18 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
GetEntitySponsorshipPricingUseCase,
type GetEntitySponsorshipPricingInput,
type GetEntitySponsorshipPricingResult,
} from './GetEntitySponsorshipPricingUseCase';
import type { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
GetEntitySponsorshipPricingUseCase,
type GetEntitySponsorshipPricingInput
} from './GetEntitySponsorshipPricingUseCase';
describe('GetEntitySponsorshipPricingUseCase', () => {
let mockSponsorshipPricingRepo: ISponsorshipPricingRepository;
let mockSponsorshipPricingRepo: any;
let mockLogger: Logger;
let mockFindByEntity: Mock;
let mockFindPendingByEntity: Mock;
let mockFindBySeasonId: Mock;
};
beforeEach(() => {
mockFindByEntity = vi.fn();
mockFindPendingByEntity = vi.fn();
mockFindBySeasonId = vi.fn();
mockSponsorshipPricingRepo = {
findByEntity: mockFindByEntity,
findAll: vi.fn(),
@@ -29,18 +22,17 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
save: vi.fn(),
exists: vi.fn(),
findAcceptingApplications: vi.fn(),
} as ISponsorshipPricingRepository;
};
mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
output = { present: vi.fn() } as unknown as typeof output;
});
it('should return PRICING_NOT_CONFIGURED when no pricing found', async () => {
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as any,
mockLogger);
const dto: GetEntitySponsorshipPricingInput = {
@@ -59,10 +51,10 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
>;
expect(err.code).toBe('PRICING_NOT_CONFIGURED');
expect(err.details.message).toContain('No sponsorship pricing configured');
});
});
it('should return pricing data when found', async () => {
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as any,
mockLogger);
const dto: GetEntitySponsorshipPricingInput = {
@@ -88,14 +80,12 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
};
mockFindByEntity.mockResolvedValue(pricing);
mockFindPendingByEntity.mockResolvedValue([]);
mockFindBySeasonId.mockResolvedValue([]);
const result = await useCase.execute(dto);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = (expect(presented.entityType).toBe('season');
const presented = result.unwrap();
expect(presented.entityType).toBe('season');
expect(presented.entityId).toBe('season1');
expect(presented.acceptingApplications).toBe(true);
expect(presented.customRequirements).toBe('Some requirements');
@@ -116,7 +106,7 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
});
it('should return error when repository throws', async () => {
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as unknown as ISponsorshipPricingRepository,
const useCase = new GetEntitySponsorshipPricingUseCase(mockSponsorshipPricingRepo as any,
mockLogger);
const dto: GetEntitySponsorshipPricingInput = {
@@ -137,5 +127,5 @@ describe('GetEntitySponsorshipPricingUseCase', () => {
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});

View File

@@ -5,8 +5,6 @@
* Used by sponsors to see available slots and prices.
*/
import type { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SponsorableEntityType } from '../../domain/entities/SponsorshipRequest';

View File

@@ -1,18 +1,15 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetLeagueAdminPermissionsUseCase,
type GetLeagueAdminPermissionsInput,
type GetLeagueAdminPermissionsResult,
type GetLeagueAdminPermissionsErrorCode,
} from './GetLeagueAdminPermissionsUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
GetLeagueAdminPermissionsUseCase,
type GetLeagueAdminPermissionsErrorCode,
type GetLeagueAdminPermissionsInput
} from './GetLeagueAdminPermissionsUseCase';
describe('GetLeagueAdminPermissionsUseCase', () => {
let mockLeagueRepo: ILeagueRepository;
let mockMembershipRepo: ILeagueMembershipRepository;
let mockLeagueRepo: any;
let mockMembershipRepo: any;
let mockFindById: Mock;
let mockGetMembership: Mock;
const logger: Logger = {
@@ -34,7 +31,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
exists: vi.fn(),
findByOwnerId: vi.fn(),
searchByName: vi.fn(),
} as ILeagueRepository;
};
mockMembershipRepo = {
getMembership: mockGetMembership,
@@ -46,9 +43,8 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
removeJoinRequest: vi.fn(),
countByLeagueId: vi.fn(),
getLeagueMembers: vi.fn(),
} as ILeagueMembershipRepository;
});
};
});
const createUseCase = () => new GetLeagueAdminPermissionsUseCase(mockLeagueRepo,
mockMembershipRepo,
@@ -59,7 +55,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
performerDriverId: 'driver1',
};
it('returns LEAGUE_NOT_FOUND when league does not exist and does not call output', async () => {
it('returns LEAGUE_NOT_FOUND when league does not exist', async () => {
mockFindById.mockResolvedValue(null);
const useCase = createUseCase();
@@ -69,9 +65,9 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminPermissionsErrorCode, { message: string }>;
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details.message).toBe('League not found');
});
});
it('returns USER_NOT_MEMBER when membership is missing and does not call output', async () => {
it('returns USER_NOT_MEMBER when membership is missing', async () => {
mockFindById.mockResolvedValue({ id: 'league1' });
mockGetMembership.mockResolvedValue(null);
@@ -82,9 +78,9 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminPermissionsErrorCode, { message: string }>;
expect(err.code).toBe('USER_NOT_MEMBER');
expect(err.details.message).toBe('User is not a member of this league');
});
});
it('returns USER_NOT_MEMBER when membership is not active and does not call output', async () => {
it('returns USER_NOT_MEMBER when membership is not active', async () => {
mockFindById.mockResolvedValue({ id: 'league1' });
mockGetMembership.mockResolvedValue({ status: 'inactive', role: 'admin' });
@@ -95,9 +91,9 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminPermissionsErrorCode, { message: string }>;
expect(err.code).toBe('USER_NOT_MEMBER');
expect(err.details.message).toBe('User is not a member of this league');
});
});
it('returns USER_NOT_MEMBER when role is member and does not call output', async () => {
it('returns USER_NOT_MEMBER when role is member', async () => {
mockFindById.mockResolvedValue({ id: 'league1' });
mockGetMembership.mockResolvedValue({ status: 'active', role: 'member' });
@@ -108,10 +104,10 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminPermissionsErrorCode, { message: string }>;
expect(err.code).toBe('USER_NOT_MEMBER');
expect(err.details.message).toBe('User is not a member of this league');
});
});
it('returns admin permissions for admin role and calls output once', async () => {
const league = { id: 'league1' } as unknown as { id: string };
it('returns admin permissions for admin role', async () => {
const league = { id: 'league1' } as any;
mockFindById.mockResolvedValue(league);
mockGetMembership.mockResolvedValue({ status: 'active', role: 'admin' });
@@ -119,9 +115,9 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.league).toBe(league);
expect(presented.league).toBe(league);
expect(presented.permissions).toEqual({
canManageSchedule: true,
canManageMembers: true,
@@ -130,8 +126,8 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
});
});
it('returns admin permissions for owner role and calls output once', async () => {
const league = { id: 'league1' } as unknown as { id: string };
it('returns admin permissions for owner role', async () => {
const league = { id: 'league1' } as any;
mockFindById.mockResolvedValue(league);
mockGetMembership.mockResolvedValue({ status: 'active', role: 'owner' });
@@ -139,9 +135,9 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.league).toBe(league);
expect(presented.league).toBe(league);
expect(presented.permissions).toEqual({
canManageSchedule: true,
canManageMembers: true,
@@ -150,7 +146,7 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
});
});
it('wraps repository errors in REPOSITORY_ERROR and does not call output', async () => {
it('wraps repository errors in REPOSITORY_ERROR', async () => {
const error = new Error('repo failed');
mockFindById.mockRejectedValue(error);
@@ -161,5 +157,5 @@ describe('GetLeagueAdminPermissionsUseCase', () => {
const err = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminPermissionsErrorCode, { message: string }>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('repo failed');
});
});
});

View File

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

View File

@@ -9,8 +9,9 @@ import type { LeagueRepository } from '../../domain/repositories/LeagueRepositor
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueAdminUseCase', () => {
let mockLeagueRepo: ILeagueRepository;
let mockLeagueRepo: any;
let mockFindById: Mock;
beforeEach(() => {
mockFindById = vi.fn();
mockLeagueRepo = {
@@ -22,9 +23,8 @@ describe('GetLeagueAdminUseCase', () => {
exists: vi.fn(),
findByOwnerId: vi.fn(),
searchByName: vi.fn(),
} as ILeagueRepository;
});
};
});
const createUseCase = () => new GetLeagueAdminUseCase(mockLeagueRepo);
@@ -42,7 +42,7 @@ describe('GetLeagueAdminUseCase', () => {
const error = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminErrorCode, { message: string }>;
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toBe('League not found');
});
});
it('should return league data when league found', async () => {
const league = { id: 'league1', ownerId: 'owner1' };
@@ -52,8 +52,8 @@ describe('GetLeagueAdminUseCase', () => {
const result = await useCase.execute(params);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.league.id).toBe('league1');
const presented = result.unwrap();
expect(presented.league.id).toBe('league1');
expect(presented.league.ownerId).toBe('owner1');
});
@@ -68,5 +68,5 @@ describe('GetLeagueAdminUseCase', () => {
const error = result.unwrapErr() as ApplicationErrorCode<GetLeagueAdminErrorCode, { message: string }>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('Repository failure');
});
});
});
});

View File

@@ -1,17 +1,17 @@
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { DriverRatingPort } from '../ports/DriverRatingPort';
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
GetLeagueDriverSeasonStatsUseCase,
type GetLeagueDriverSeasonStatsErrorCode,
type GetLeagueDriverSeasonStatsInput,
type GetLeagueDriverSeasonStatsResult,
} from './GetLeagueDriverSeasonStatsUseCase';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { DriverRatingPort } from '../ports/DriverRatingPort';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueDriverSeasonStatsUseCase', () => {
const mockStandingFindByLeagueId = vi.fn();
@@ -20,15 +20,15 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const mockRaceFindByLeagueId = vi.fn();
const mockDriverRatingGetRating = vi.fn();
const mockDriverFindById = vi.fn();
const mockTeamFindById = vi.fn();
let useCase: GetLeagueDriverSeasonStatsUseCase;
let standingRepository: IStandingRepository;
let resultRepository: IResultRepository;
let penaltyRepository: IPenaltyRepository;
let raceRepository: IRaceRepository;
let driverRepository: IDriverRepository;
let driverRatingPort: DriverRatingPort;
let standingRepository: any;
let resultRepository: any;
let penaltyRepository: any;
let raceRepository: any;
let driverRepository: any;
let driverRatingPort: any;
beforeEach(() => {
mockStandingFindByLeagueId.mockReset();
mockResultFindByDriverIdAndLeagueId.mockReset();
@@ -36,7 +36,6 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
mockRaceFindByLeagueId.mockReset();
mockDriverRatingGetRating.mockReset();
mockDriverFindById.mockReset();
mockTeamFindById.mockReset();
standingRepository = {
findByLeagueId: mockStandingFindByLeagueId,
@@ -103,8 +102,6 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
updateDriverRating: vi.fn(),
};
};
useCase = new GetLeagueDriverSeasonStatsUseCase(standingRepository,
resultRepository,
penaltyRepository,
@@ -182,8 +179,8 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.leagueId).toBe('league-1');
const presented = result.unwrap();
expect(presented.leagueId).toBe('league-1');
expect(presented.stats).toHaveLength(2);
expect(presented.stats[0]).toEqual({
@@ -231,8 +228,8 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented?.stats[0]?.penaltyPoints).toBe(0);
const presented = result.unwrap();
expect(presented.stats[0]?.penaltyPoints).toBe(0);
});
it('should return LEAGUE_NOT_FOUND when no standings are found', async () => {
@@ -250,7 +247,7 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
>;
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details.message).toBe('League not found');
});
});
it('should return REPOSITORY_ERROR when an unexpected error occurs', async () => {
const input: GetLeagueDriverSeasonStatsInput = { leagueId: 'league-1' };
@@ -265,8 +262,7 @@ describe('GetLeagueDriverSeasonStatsUseCase', () => {
GetLeagueDriverSeasonStatsErrorCode,
{ message: string }
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('repository failure');
});
});
});

View File

@@ -13,30 +13,23 @@ import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorC
describe('GetLeagueFullConfigUseCase', () => {
let useCase: GetLeagueFullConfigUseCase;
let leagueRepository: ILeagueRepository & { findById: ReturnType<typeof vi.fn> };
let seasonRepository: ISeasonRepository & { findByLeagueId: ReturnType<typeof vi.fn> };
let leagueScoringConfigRepository: ILeagueScoringConfigRepository & {
findBySeasonId: ReturnType<typeof vi.fn>;
};
let gameRepository: IGameRepository & { findById: ReturnType<typeof vi.fn> };
let leagueRepository: any;
let seasonRepository: any;
let leagueScoringConfigRepository: any;
let gameRepository: any;
beforeEach(() => {
leagueRepository = {
findById: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};
seasonRepository = {
findByLeagueId: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};
leagueScoringConfigRepository = {
findBySeasonId: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};
gameRepository = {
findById: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};
useCase = new GetLeagueFullConfigUseCase(leagueRepository,
@@ -78,9 +71,7 @@ describe('GetLeagueFullConfigUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const firstCall = const presented = firstCall[0] as GetLeagueFullConfigResult;
const presented = result.unwrap();
expect(presented.config.league).toEqual(mockLeague);
expect(presented.config.activeSeason).toEqual(mockSeasons[0]);
@@ -88,7 +79,7 @@ describe('GetLeagueFullConfigUseCase', () => {
expect(presented.config.game).toEqual(mockGame);
});
it('should return error when league not found and not call presenter', async () => {
it('should return error when league not found', async () => {
const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };
leagueRepository.findById.mockResolvedValue(null);
@@ -103,7 +94,7 @@ describe('GetLeagueFullConfigUseCase', () => {
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toBe('League not found');
});
});
it('should handle no active season', async () => {
const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };
@@ -121,8 +112,7 @@ describe('GetLeagueFullConfigUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const firstCall = const presented = firstCall[0] as GetLeagueFullConfigResult;
const presented = result.unwrap();
expect(presented.config.league).toEqual(mockLeague);
expect(presented.config.activeSeason).toBeUndefined();
@@ -130,7 +120,7 @@ describe('GetLeagueFullConfigUseCase', () => {
expect(presented.config.game).toBeUndefined();
});
it('should return repository error when repository throws and not call presenter', async () => {
it('should return repository error when repository throws', async () => {
const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };
const thrownError = new Error('Repository failure');
@@ -146,5 +136,5 @@ describe('GetLeagueFullConfigUseCase', () => {
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('Repository failure');
});
});
});

View File

@@ -5,9 +5,6 @@ import {
type GetLeagueMembershipsResult,
type GetLeagueMembershipsErrorCode,
} from './GetLeagueMembershipsUseCase';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
@@ -24,6 +21,7 @@ describe('GetLeagueMembershipsUseCase', () => {
let leagueRepository: {
findById: Mock;
};
beforeEach(() => {
leagueMembershipRepository = {
getLeagueMembers: vi.fn(),
@@ -34,12 +32,9 @@ describe('GetLeagueMembershipsUseCase', () => {
leagueRepository = {
findById: vi.fn(),
};
output = {
present: vi.fn(),
};
useCase = new GetLeagueMembershipsUseCase(leagueMembershipRepository as unknown as ILeagueMembershipRepository,
driverRepository as unknown as IDriverRepository,
leagueRepository as unknown as ILeagueRepository);
useCase = new GetLeagueMembershipsUseCase(leagueMembershipRepository as any,
driverRepository as any,
leagueRepository as any);
});
it('should return league memberships with drivers', async () => {
@@ -90,14 +85,14 @@ describe('GetLeagueMembershipsUseCase', () => {
const result = await useCase.execute({ leagueId } as GetLeagueMembershipsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented?.league).toEqual(league);
expect(presented?.memberships).toHaveLength(2);
expect(presented?.memberships[0]?.membership).toEqual(memberships[0]);
expect(presented?.memberships[0]?.driver).toEqual(driver1);
expect(presented?.memberships[1]?.membership).toEqual(memberships[1]);
expect(presented?.memberships[1]?.driver).toEqual(driver2);
expect(presented.league).toEqual(league);
expect(presented.memberships).toHaveLength(2);
expect(presented.memberships[0]?.membership).toEqual(memberships[0]);
expect(presented.memberships[0]?.driver).toEqual(driver1);
expect(presented.memberships[1]?.membership).toEqual(memberships[1]);
expect(presented.memberships[1]?.driver).toEqual(driver2);
});
it('should handle drivers not found', async () => {
@@ -125,12 +120,12 @@ describe('GetLeagueMembershipsUseCase', () => {
const result = await useCase.execute({ leagueId } as GetLeagueMembershipsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented?.league).toEqual(league);
expect(presented?.memberships).toHaveLength(1);
expect(presented?.memberships[0]?.membership).toEqual(memberships[0]);
expect(presented?.memberships[0]?.driver).toBeNull();
expect(presented.league).toEqual(league);
expect(presented.memberships).toHaveLength(1);
expect(presented.memberships[0]?.membership).toEqual(memberships[0]);
expect(presented.memberships[0]?.driver).toBeNull();
});
it('should return error when league not found', async () => {
@@ -149,7 +144,7 @@ describe('GetLeagueMembershipsUseCase', () => {
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details?.message).toBe('League not found');
});
});
it('should return repository error on unexpected failure', async () => {
const leagueId = 'league-1';
@@ -169,5 +164,5 @@ describe('GetLeagueMembershipsUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details?.message).toBe('Database connection failed');
});
});
});
});

View File

@@ -5,11 +5,10 @@ import {
type GetLeagueOwnerSummaryResult,
type GetLeagueOwnerSummaryErrorCode,
} from './GetLeagueOwnerSummaryUseCase';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { Driver } from '../../domain/entities/Driver';
import { League } from '../../domain/entities/League';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueOwnerSummaryUseCase', () => {
let useCase: GetLeagueOwnerSummaryUseCase;
let leagueRepository: {
@@ -18,6 +17,14 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
let driverRepository: {
findById: Mock;
};
let leagueMembershipRepository: {
getLeagueMembers: Mock;
};
let standingRepository: {
findByDriverIdAndLeagueId: Mock;
findByLeagueId: Mock;
};
beforeEach(() => {
leagueRepository = {
findById: vi.fn(),
@@ -25,8 +32,19 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
driverRepository = {
findById: vi.fn(),
};
useCase = new GetLeagueOwnerSummaryUseCase(leagueRepository as unknown as ILeagueRepository,
driverRepository as unknown as IDriverRepository);
leagueMembershipRepository = {
getLeagueMembers: vi.fn(),
};
standingRepository = {
findByDriverIdAndLeagueId: vi.fn(),
findByLeagueId: vi.fn(),
};
useCase = new GetLeagueOwnerSummaryUseCase(
leagueRepository as any,
driverRepository as any,
leagueMembershipRepository as any,
standingRepository as any
);
});
it('should return owner summary when league and owner exist', async () => {
@@ -48,15 +66,17 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
leagueRepository.findById.mockResolvedValue(league);
driverRepository.findById.mockResolvedValue(driver);
leagueMembershipRepository.getLeagueMembers.mockResolvedValue([]);
standingRepository.findByDriverIdAndLeagueId.mockResolvedValue(null);
const result = await useCase.execute({ leagueId } as GetLeagueOwnerSummaryInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented?.league).toBe(league);
expect(presented?.owner).toBe(driver);
expect(presented?.rating).toBe(0);
expect(presented?.rank).toBe(0);
const presented = result.unwrap();
expect(presented.league).toBe(league);
expect(presented.owner).toBe(driver);
expect(presented.rating).toBeNull();
expect(presented.rank).toBeNull();
});
it('should return error when league does not exist', async () => {
@@ -73,7 +93,7 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
>;
expect(errorResult.code).toBe('LEAGUE_NOT_FOUND');
expect(errorResult.details.message).toBe('League not found');
});
});
it('should return error when owner does not exist', async () => {
const leagueId = 'league-1';
@@ -98,7 +118,7 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
>;
expect(errorResult.code).toBe('OWNER_NOT_FOUND');
expect(errorResult.details.message).toBe('League owner not found');
});
});
it('should return repository error when repository throws', async () => {
const leagueId = 'league-1';
@@ -115,5 +135,5 @@ describe('GetLeagueOwnerSummaryUseCase', () => {
expect(errorResult.code).toBe('REPOSITORY_ERROR');
expect(errorResult.details.message).toBe('DB failure');
});
});
});

View File

@@ -1,13 +1,10 @@
import type { Logger } from '@core/shared/application';
import { SeasonScheduleGenerator } from '@/racing/domain/services/SeasonScheduleGenerator';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
export type GetLeagueScheduleErrorCode =
| 'LEAGUE_NOT_FOUND'

View File

@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Result } from '@core/shared/domain/Result';
import { GetLeagueScoringConfigUseCase } from './GetLeagueScoringConfigUseCase';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
@@ -12,39 +13,39 @@ import type { LeagueScoringPreset } from '../../domain/types/LeagueScoringPreset
describe('GetLeagueScoringConfigUseCase', () => {
let useCase: GetLeagueScoringConfigUseCase;
let mockLeagueRepository: jest.Mocked<ILeagueRepository>;
let mockSeasonRepository: jest.Mocked<ISeasonRepository>;
let mockLeagueScoringConfigRepository: jest.Mocked<ILeagueScoringConfigRepository>;
let mockGameRepository: jest.Mocked<IGameRepository>;
let mockPresetProvider: { getPresetById: jest.Mock };
let mockLeagueRepository: any;
let mockSeasonRepository: any;
let mockLeagueScoringConfigRepository: any;
let mockGameRepository: any;
let mockPresetProvider: { getPresetById: any };
beforeEach(() => {
mockLeagueRepository = {
findById: jest.fn(),
exists: jest.fn(),
save: jest.fn(),
findAll: jest.fn(),
} as any;
findById: vi.fn(),
exists: vi.fn(),
save: vi.fn(),
findAll: vi.fn(),
};
mockSeasonRepository = {
findByLeagueId: jest.fn(),
save: jest.fn(),
findById: jest.fn(),
} as any;
findByLeagueId: vi.fn(),
save: vi.fn(),
findById: vi.fn(),
};
mockLeagueScoringConfigRepository = {
findBySeasonId: jest.fn(),
save: jest.fn(),
} as any;
findBySeasonId: vi.fn(),
save: vi.fn(),
};
mockGameRepository = {
findById: jest.fn(),
save: jest.fn(),
findAll: jest.fn(),
} as any;
findById: vi.fn(),
save: vi.fn(),
findAll: vi.fn(),
};
mockPresetProvider = {
getPresetById: jest.fn(),
getPresetById: vi.fn(),
};
useCase = new GetLeagueScoringConfigUseCase(
@@ -80,7 +81,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isOk()).toBe(true);
const value = result.value as any;
const value = result.unwrap() as any;
expect(value.league).toBe(mockLeague);
expect(value.season).toBe(mockSeason);
expect(value.scoringConfig).toBe(mockScoringConfig);
@@ -94,7 +95,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'non-existent' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'LEAGUE_NOT_FOUND',
details: { message: 'League not found' },
});
@@ -109,7 +110,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'NO_SEASONS',
details: { message: 'No seasons found for league' },
});
@@ -129,7 +130,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'NO_ACTIVE_SEASON',
details: { message: 'No active season found for league' },
});
@@ -150,7 +151,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'NO_SCORING_CONFIG',
details: { message: 'Scoring configuration not found' },
});
@@ -177,7 +178,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'GAME_NOT_FOUND',
details: { message: 'Game not found for season' },
});
@@ -205,7 +206,7 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isOk()).toBe(true);
const value = result.value as any;
const value = result.unwrap() as any;
expect(value.preset).toBeUndefined();
});
@@ -215,9 +216,9 @@ describe('GetLeagueScoringConfigUseCase', () => {
const result = await useCase.execute({ leagueId: 'league-1' });
expect(result.isErr()).toBe(true);
expect(result.value).toEqual({
expect(result.unwrapErr()).toEqual({
code: 'REPOSITORY_ERROR',
details: { message: 'Database error' },
});
});
});
});

View File

@@ -74,7 +74,7 @@ export class GetLeagueScoringConfigUseCase {
return (seasonStatus as { toString: () => string }).toString() === 'active';
}
return false;
}) ?? seasons[0];
});
if (!activeSeason) {
return Result.err({

View File

@@ -8,7 +8,7 @@ import {
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { Season } from '../../domain/entities/season';
import { Season } from '../../domain/entities/season/Season';
import { League } from '../../domain/entities/League';
describe('GetLeagueSeasonsUseCase', () => {
@@ -18,7 +18,7 @@ describe('GetLeagueSeasonsUseCase', () => {
};
let leagueRepository: {
findById: Mock;
};
exists: Mock;
};
beforeEach(() => {
@@ -28,15 +28,14 @@ describe('GetLeagueSeasonsUseCase', () => {
leagueRepository = {
findById: vi.fn(),
exists: vi.fn(),
};
};
useCase = new GetLeagueSeasonsUseCase(seasonRepository as unknown as ISeasonRepository,
leagueRepository as unknown as ILeagueRepository);
useCase = new GetLeagueSeasonsUseCase(leagueRepository as any,
seasonRepository as any);
});
it('should present seasons with correct isParallelActive flags on success', async () => {
it('should return seasons with correct isParallelActive flags on success', async () => {
const leagueId = 'league-1';
const league = League.create({
id: leagueId,
@@ -64,24 +63,24 @@ describe('GetLeagueSeasonsUseCase', () => {
}),
];
leagueRepository.exists.mockResolvedValue(true);
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findByLeagueId.mockResolvedValue(seasons);
const result = await useCase.execute({ leagueId } satisfies GetLeagueSeasonsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented?.league).toBe(league);
expect(presented?.seasons).toHaveLength(2);
expect(presented.seasons).toHaveLength(2);
expect(presented?.seasons[0]?.season).toBe(seasons[0]);
expect(presented?.seasons[0]?.isPrimary).toBe(false);
expect(presented?.seasons[0]?.isParallelActive).toBe(false);
expect(presented.seasons[0]?.season).toBe(seasons[0]);
expect(presented.seasons[0]?.isPrimary).toBe(true);
expect(presented.seasons[0]?.isParallelActive).toBe(false);
expect(presented?.seasons[1]?.season).toBe(seasons[1]);
expect(presented?.seasons[1]?.isPrimary).toBe(false);
expect(presented?.seasons[1]?.isParallelActive).toBe(false);
expect(presented.seasons[1]?.season).toBe(seasons[1]);
expect(presented.seasons[1]?.isPrimary).toBe(false);
expect(presented.seasons[1]?.isParallelActive).toBe(false);
});
it('should set isParallelActive true for active seasons when multiple active', async () => {
@@ -110,23 +109,24 @@ describe('GetLeagueSeasonsUseCase', () => {
}),
];
leagueRepository.exists.mockResolvedValue(true);
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findByLeagueId.mockResolvedValue(seasons);
const result = await useCase.execute({ leagueId } satisfies GetLeagueSeasonsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented?.seasons).toHaveLength(2);
expect(presented?.seasons[0]?.isParallelActive).toBe(true);
expect(presented?.seasons[1]?.isParallelActive).toBe(true);
expect(presented.seasons).toHaveLength(2);
expect(presented.seasons[0]?.isParallelActive).toBe(true);
expect(presented.seasons[1]?.isParallelActive).toBe(true);
});
it('should return LEAGUE_NOT_FOUND error when league does not exist', async () => {
const leagueId = 'missing-league';
leagueRepository.findById.mockResolvedValue(null);
leagueRepository.exists.mockResolvedValue(false);
const result = await useCase.execute({ leagueId } satisfies GetLeagueSeasonsInput);
@@ -138,13 +138,13 @@ describe('GetLeagueSeasonsUseCase', () => {
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details.message).toBe('League not found');
});
});
it('should return REPOSITORY_ERROR when repository throws', async () => {
const leagueId = 'league-1';
const errorMessage = 'DB error';
leagueRepository.findById.mockRejectedValue(new Error(errorMessage));
leagueRepository.exists.mockRejectedValue(new Error(errorMessage));
const result = await useCase.execute({ leagueId } satisfies GetLeagueSeasonsInput);
@@ -156,5 +156,5 @@ describe('GetLeagueSeasonsUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe(errorMessage);
});
});
});
});

View File

@@ -6,8 +6,6 @@ import {
type GetLeagueStandingsResult,
type GetLeagueStandingsErrorCode,
} from './GetLeagueStandingsUseCase';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import { Standing } from '../../domain/entities/Standing';
import { Driver } from '../../domain/entities/Driver';
@@ -19,6 +17,7 @@ describe('GetLeagueStandingsUseCase', () => {
let driverRepository: {
findById: Mock;
};
beforeEach(() => {
standingRepository = {
findByLeagueId: vi.fn(),
@@ -26,15 +25,12 @@ describe('GetLeagueStandingsUseCase', () => {
driverRepository = {
findById: vi.fn(),
};
output = {
present: vi.fn(),
};
useCase = new GetLeagueStandingsUseCase(standingRepository as unknown as IStandingRepository,
driverRepository as unknown as IDriverRepository);
useCase = new GetLeagueStandingsUseCase(standingRepository as any,
driverRepository as any);
});
it('should present standings with drivers mapped and return ok result', async () => {
it('should return standings with drivers mapped', async () => {
const leagueId = 'league-1';
const standings = [
Standing.create({
@@ -75,9 +71,9 @@ describe('GetLeagueStandingsUseCase', () => {
const result = await useCase.execute({ leagueId } satisfies GetLeagueStandingsInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.standings).toHaveLength(2);
expect(presented.standings).toHaveLength(2);
expect(presented.standings[0]).toEqual({
driverId: 'driver-1',
driver: driver1,
@@ -92,7 +88,7 @@ describe('GetLeagueStandingsUseCase', () => {
});
});
it('should return repository error and not call output when repository fails', async () => {
it('should return repository error when repository fails', async () => {
const leagueId = 'league-1';
standingRepository.findByLeagueId.mockRejectedValue(new Error('DB error'));
@@ -106,5 +102,5 @@ describe('GetLeagueStandingsUseCase', () => {
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('DB error');
});
});
});
});

View File

@@ -5,8 +5,8 @@ import {
type GetLeagueStatsResult,
type GetLeagueStatsErrorCode,
} from './GetLeagueStatsUseCase';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetLeagueStatsUseCase', () => {
@@ -18,6 +18,7 @@ describe('GetLeagueStatsUseCase', () => {
findByLeagueId: Mock;
};
let getDriverRating: Mock;
beforeEach(() => {
leagueMembershipRepository = {
getLeagueMembers: vi.fn(),
@@ -26,8 +27,8 @@ describe('GetLeagueStatsUseCase', () => {
findByLeagueId: vi.fn(),
};
getDriverRating = vi.fn();
useCase = new GetLeagueStatsUseCase(leagueMembershipRepository as unknown as ILeagueMembershipRepository,
raceRepository as unknown as IRaceRepository,
useCase = new GetLeagueStatsUseCase(leagueMembershipRepository as any,
raceRepository as any,
getDriverRating);
});
@@ -52,8 +53,8 @@ describe('GetLeagueStatsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = (expect(presented.leagueId).toBe(input.leagueId);
const presented = result.unwrap();
expect(presented.leagueId).toBe(input.leagueId);
expect(presented.driverCount).toBe(3);
expect(presented.raceCount).toBe(2);
expect(presented.averageRating).toBe(1550); // (1500 + 1600) / 2
@@ -71,8 +72,8 @@ describe('GetLeagueStatsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = (expect(presented.leagueId).toBe(input.leagueId);
const presented = result.unwrap();
expect(presented.leagueId).toBe(input.leagueId);
expect(presented.driverCount).toBe(1);
expect(presented.raceCount).toBe(1);
expect(presented.averageRating).toBe(0);
@@ -92,7 +93,7 @@ describe('GetLeagueStatsUseCase', () => {
>;
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toBe('League not found');
});
});
it('should return error when repository fails', async () => {
const input: GetLeagueStatsInput = { leagueId: 'league-1' };
@@ -107,5 +108,5 @@ describe('GetLeagueStatsUseCase', () => {
>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('DB error');
});
});
});

View File

@@ -40,9 +40,9 @@ describe('GetLeagueWalletUseCase', () => {
findByWalletId: vi.fn(),
};
useCase = new GetLeagueWalletUseCase(leagueRepository as unknown as ILeagueRepository,
leagueWalletRepository as unknown as ILeagueWalletRepository,
transactionRepository as unknown as ITransactionRepository);
useCase = new GetLeagueWalletUseCase(leagueRepository as any,
leagueWalletRepository as any,
transactionRepository as any);
});
it('returns mapped wallet data when wallet exists', async () => {
@@ -123,8 +123,8 @@ describe('GetLeagueWalletUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = (expect(presented.wallet).toBe(wallet);
const presented = result.unwrap();
expect(presented.wallet).toBe(wallet);
expect(presented.transactions).toHaveLength(transactions.length);
expect(presented.transactions[0]!.id).toEqual(
transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0]!
@@ -171,7 +171,7 @@ describe('GetLeagueWalletUseCase', () => {
expect(err.code).toBe('WALLET_NOT_FOUND');
expect(err.details.message).toBe('League wallet not found');
});
});
it('returns league not found when league does not exist', async () => {
const leagueId = 'league-missing';
@@ -190,7 +190,7 @@ describe('GetLeagueWalletUseCase', () => {
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details.message).toBe('League not found');
});
});
it('returns repository error when repository throws', async () => {
const leagueId = 'league-1';
@@ -209,5 +209,5 @@ describe('GetLeagueWalletUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
});
});
});

View File

@@ -5,8 +5,6 @@ import {
type GetRaceProtestsResult,
type GetRaceProtestsErrorCode,
} from './GetRaceProtestsUseCase';
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import { Protest } from '../../domain/entities/Protest';
import { Driver } from '../../domain/entities/Driver';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -15,11 +13,12 @@ describe('GetRaceProtestsUseCase', () => {
let useCase: GetRaceProtestsUseCase;
let protestRepository: { findByRaceId: Mock };
let driverRepository: { findById: Mock };
beforeEach(() => {
protestRepository = { findByRaceId: vi.fn() };
driverRepository = { findById: vi.fn() };
useCase = new GetRaceProtestsUseCase(protestRepository as unknown as IProtestRepository,
driverRepository as unknown as IDriverRepository);
useCase = new GetRaceProtestsUseCase(protestRepository as any,
driverRepository as any);
});
it('should return protests with drivers', async () => {
@@ -66,10 +65,7 @@ describe('GetRaceProtestsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetRaceProtestsResult;
const presented = result.unwrap();
expect(presented.protests).toHaveLength(1);
expect(presented.protests[0]).toEqual(protest);
@@ -85,10 +81,7 @@ describe('GetRaceProtestsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetRaceProtestsResult;
const presented = result.unwrap();
expect(presented.protests).toEqual([]);
expect(presented.drivers).toEqual([]);
@@ -109,5 +102,5 @@ describe('GetRaceProtestsUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
});
});
});
});

View File

@@ -5,25 +5,23 @@ import {
type GetRaceRegistrationsResult,
type GetRaceRegistrationsErrorCode,
} from './GetRaceRegistrationsUseCase';
import type { RaceRepository } from '@core/racing/domain/repositories/RaceRepository';
import type { RaceRegistrationRepository } from '@core/racing/domain/repositories/RaceRegistrationRepository';
import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration';
import { Race } from '@core/racing/domain/entities/Race';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result } from '@core/shared/domain/Result';
describe('GetRaceRegistrationsUseCase', () => {
let useCase: GetRaceRegistrationsUseCase;
let raceRepository: { findById: Mock };
let registrationRepository: { findByRaceId: Mock };
beforeEach(() => {
raceRepository = { findById: vi.fn() };
registrationRepository = { findByRaceId: vi.fn() };
useCase = new GetRaceRegistrationsUseCase(raceRepository as unknown as IRaceRepository,
registrationRepository as unknown as IRaceRegistrationRepository);
useCase = new GetRaceRegistrationsUseCase(raceRepository as any,
registrationRepository as any);
});
it('should present race and registrations on success', async () => {
it('should return race and registrations on success', async () => {
const input: GetRaceRegistrationsInput = { raceId: 'race-1' };
const race = Race.create({
@@ -42,14 +40,10 @@ describe('GetRaceRegistrationsUseCase', () => {
raceRepository.findById.mockResolvedValue(race);
registrationRepository.findByRaceId.mockResolvedValue(registrations);
const result: Result<void, ApplicationErrorCode<GetRaceRegistrationsErrorCode, { message: string }>> =
await useCase.execute(input);
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetRaceRegistrationsResult;
const presented = result.unwrap();
expect(presented.race).toEqual(race);
expect(presented.registrations).toHaveLength(2);
@@ -62,8 +56,7 @@ describe('GetRaceRegistrationsUseCase', () => {
raceRepository.findById.mockResolvedValue(null);
const result: Result<void, ApplicationErrorCode<GetRaceRegistrationsErrorCode, { message: string }>> =
await useCase.execute(input);
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr() as ApplicationErrorCode<
@@ -73,15 +66,14 @@ describe('GetRaceRegistrationsUseCase', () => {
expect(err.code).toBe('RACE_NOT_FOUND');
expect(err.details?.message).toBe('Race not found');
});
});
it('should return REPOSITORY_ERROR when repository throws', async () => {
const input: GetRaceRegistrationsInput = { raceId: 'race-1' };
raceRepository.findById.mockRejectedValue(new Error('DB failure'));
const result: Result<void, ApplicationErrorCode<GetRaceRegistrationsErrorCode, { message: string }>> =
await useCase.execute(input);
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const err = result.unwrapErr() as ApplicationErrorCode<
@@ -91,5 +83,5 @@ describe('GetRaceRegistrationsUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details?.message).toBe('DB failure');
});
});
});
});

View File

@@ -19,19 +19,19 @@ describe('GetRaceResultsDetailUseCase', () => {
let resultRepository: { findByRaceId: Mock };
let driverRepository: { findAll: Mock };
let penaltyRepository: { findByRaceId: Mock };
beforeEach(() => {
raceRepository = { findById: vi.fn() };
leagueRepository = { findById: vi.fn() };
resultRepository = { findByRaceId: vi.fn() };
driverRepository = { findAll: vi.fn() };
penaltyRepository = { findByRaceId: vi.fn() };
};
useCase = new GetRaceResultsDetailUseCase(raceRepository as unknown as IRaceRepository,
leagueRepository as unknown as ILeagueRepository,
resultRepository as unknown as IResultRepository,
driverRepository as unknown as IDriverRepository,
penaltyRepository as unknown as IPenaltyRepository);
useCase = new GetRaceResultsDetailUseCase(raceRepository as any,
leagueRepository as any,
resultRepository as any,
driverRepository as any,
penaltyRepository as any);
});
it('presents race results detail when race exists', async () => {
@@ -104,19 +104,18 @@ describe('GetRaceResultsDetailUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = (expect(presented.race).toEqual(race);
expect(presented.race).toEqual(race);
expect(presented.league).toEqual(league);
expect(presented.results).toEqual(results);
expect(presented.drivers).toEqual(drivers);
expect(presented.penalties).toEqual(penalties);
expect(presented.pointsSystem).toBeDefined();
expect(presented.fastestLapTime).toBe(120);
expect(presented.currentDriverId).toBe('driver-1');
});
it('returns error when race not found and does not present data', async () => {
it('returns error when race not found', async () => {
const input: GetRaceResultsDetailInput = { raceId: 'race-1' };
raceRepository.findById.mockResolvedValue(null);
@@ -131,7 +130,7 @@ describe('GetRaceResultsDetailUseCase', () => {
expect(error.code).toBe('RACE_NOT_FOUND');
expect(error.details.message).toBe('Race not found');
});
});
it('returns repository error when an unexpected error occurs', async () => {
const input: GetRaceResultsDetailInput = { raceId: 'race-1' };
@@ -148,5 +147,5 @@ describe('GetRaceResultsDetailUseCase', () => {
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('Database failure');
});
});
});

View File

@@ -5,9 +5,6 @@ import {
type GetRaceWithSOFResult,
type GetRaceWithSOFErrorCode,
} from './GetRaceWithSOFUseCase';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import { ResultRepository } from '../../domain/repositories/ResultRepository';
import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -24,6 +21,7 @@ describe('GetRaceWithSOFUseCase', () => {
findByRaceId: Mock;
};
let getDriverRating: Mock;
beforeEach(() => {
raceRepository = {
findById: vi.fn(),
@@ -35,13 +33,12 @@ describe('GetRaceWithSOFUseCase', () => {
findByRaceId: vi.fn(),
};
getDriverRating = vi.fn();
output = {
present: vi.fn(),
};
useCase = new GetRaceWithSOFUseCase(raceRepository as unknown as IRaceRepository,
registrationRepository as unknown as IRaceRegistrationRepository,
resultRepository as unknown as IResultRepository,
getDriverRating);
useCase = new GetRaceWithSOFUseCase(
raceRepository as any,
registrationRepository as any,
resultRepository as any,
getDriverRating
);
});
it('should return error when race not found', async () => {
@@ -56,7 +53,7 @@ describe('GetRaceWithSOFUseCase', () => {
>;
expect(err.code).toBe('RACE_NOT_FOUND');
expect(err.details?.message).toBe('Race with id race-1 not found');
});
});
it('should return race with stored SOF when available', async () => {
const race = Race.create({
@@ -74,23 +71,15 @@ describe('GetRaceWithSOFUseCase', () => {
raceRepository.findById.mockResolvedValue(race);
registrationRepository.getRegisteredDrivers.mockResolvedValue([
'driver-1',
'driver-2',
'driver-3',
'driver-4',
'driver-5',
'driver-6',
'driver-7',
'driver-8',
'driver-9',
'driver-10',
'driver-1', 'driver-2', 'driver-3', 'driver-4', 'driver-5',
'driver-6', 'driver-7', 'driver-8', 'driver-9', 'driver-10',
]);
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = expect(presented.race.id).toBe('race-1');
const presented = result.unwrap();
expect(presented.race.id).toBe('race-1');
expect(presented.race.leagueId).toBe('league-1');
expect(presented.strengthOfField).toBe(1500);
expect(presented.registeredCount).toBe(10);
@@ -124,8 +113,8 @@ describe('GetRaceWithSOFUseCase', () => {
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = expect(presented.strengthOfField).toBe(1500); // average
const presented = result.unwrap();
expect(presented.strengthOfField).toBe(1500); // average
expect(presented.participantCount).toBe(2);
expect(registrationRepository.getRegisteredDrivers).toHaveBeenCalledWith('race-1');
expect(resultRepository.findByRaceId).not.toHaveBeenCalled();
@@ -160,8 +149,8 @@ describe('GetRaceWithSOFUseCase', () => {
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = expect(presented.strengthOfField).toBe(1500);
const presented = result.unwrap();
expect(presented.strengthOfField).toBe(1500);
expect(presented.participantCount).toBe(2);
expect(resultRepository.findByRaceId).toHaveBeenCalledWith('race-1');
expect(registrationRepository.getRegisteredDrivers).not.toHaveBeenCalled();
@@ -191,8 +180,8 @@ describe('GetRaceWithSOFUseCase', () => {
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = expect(presented.strengthOfField).toBe(1400); // only one rating
const presented = result.unwrap();
expect(presented.strengthOfField).toBe(1400); // only one rating
expect(presented.participantCount).toBe(2);
});
@@ -213,12 +202,12 @@ describe('GetRaceWithSOFUseCase', () => {
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const [[presented]] = expect(presented.strengthOfField).toBe(null);
const presented = result.unwrap();
expect(presented.strengthOfField).toBe(null);
expect(presented.participantCount).toBe(0);
});
it('should wrap repository errors in REPOSITORY_ERROR and not call output', async () => {
it('should wrap repository errors in REPOSITORY_ERROR', async () => {
raceRepository.findById.mockRejectedValue(new Error('boom'));
const result = await useCase.execute({ raceId: 'race-1' } as GetRaceWithSOFInput);
@@ -230,5 +219,5 @@ describe('GetRaceWithSOFUseCase', () => {
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details?.message).toBe('boom');
});
});
});

View File

@@ -1,15 +1,13 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetRacesPageDataUseCase,
type GetRacesPageDataResult,
type GetRacesPageDataInput,
type GetRacesPageDataErrorCode,
} from './GetRacesPageDataUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/domain/Logger';
import type { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
GetRacesPageDataUseCase,
type GetRacesPageDataErrorCode,
type GetRacesPageDataInput,
type GetRacesPageDataResult,
} from './GetRacesPageDataUseCase';
describe('GetRacesPageDataUseCase', () => {
let useCase: GetRacesPageDataUseCase;

View File

@@ -1,13 +1,8 @@
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
export type GetRacesPageDataInput = {
leagueId: string;
};
export type GetRacesPageRaceItem = {
race: Race;

View File

@@ -5,38 +5,24 @@ import {
type GetSeasonDetailsResult,
type GetSeasonDetailsErrorCode,
} from './GetSeasonDetailsUseCase';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { Season } from '../../domain/entities/season/Season';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
describe('GetSeasonDetailsUseCase', () => {
let useCase: GetSeasonDetailsUseCase;
let leagueRepository: {
findById: Mock;
};
let seasonRepository: {
findById: Mock;
};
};
beforeEach(() => {
leagueRepository = {
findById: vi.fn(),
};
seasonRepository = {
findById: vi.fn(),
};
output = {
present: vi.fn(),
};
useCase = new GetSeasonDetailsUseCase(leagueRepository as unknown as ILeagueRepository,
seasonRepository as unknown as ISeasonRepository);
useCase = new GetSeasonDetailsUseCase(seasonRepository as any);
});
it('returns full details for a season belonging to the league', async () => {
const league = { id: 'league-1' };
it('returns full details for a season', async () => {
const season = Season.create({
id: 'season-1',
leagueId: 'league-1',
@@ -45,58 +31,30 @@ describe('GetSeasonDetailsUseCase', () => {
status: 'planned',
}).withMaxDrivers(24);
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(season);
const input: GetSeasonDetailsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented =
(expect(presented).toBeDefined();
expect(presented?.leagueId).toBe('league-1');
expect(presented?.season.id).toBe('season-1');
expect(presented?.season.leagueId).toBe('league-1');
expect(presented?.season.gameId).toBe('iracing');
expect(presented?.season.name).toBe('Detailed Season');
expect(presented?.season.status.toString()).toBe('planned');
expect(presented?.season.maxDrivers).toBe(24);
expect(presented).toBeDefined();
expect(presented.season.id).toBe('season-1');
expect(presented.season.leagueId).toBe('league-1');
expect(presented.season.gameId).toBe('iracing');
expect(presented.season.name).toBe('Detailed Season');
expect(presented.season.status.toString()).toBe('planned');
expect(presented.season.maxDrivers).toBe(24);
});
it('returns error when league not found', async () => {
leagueRepository.findById.mockResolvedValue(null);
const input: GetSeasonDetailsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
GetSeasonDetailsErrorCode,
{ message: string }
>;
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toBe('League not found: league-1');
});
it('returns error when season not found', async () => {
const league = { id: 'league-1' };
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(null);
const input: GetSeasonDetailsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
@@ -110,51 +68,15 @@ describe('GetSeasonDetailsUseCase', () => {
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(error.details.message).toBe(
'Season season-1 does not belong to league league-1',
);
});
it('returns error when season belongs to different league', async () => {
const league = { id: 'league-1' };
const season = Season.create({
id: 'season-1',
leagueId: 'league-2',
gameId: 'iracing',
name: 'Season',
status: 'active',
});
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(season);
const input: GetSeasonDetailsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
GetSeasonDetailsErrorCode,
{ message: string }
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(error.details.message).toBe(
'Season season-1 does not belong to league league-1',
);
});
expect(error.details.message).toBe('Season not found');
});
it('returns repository error when an unexpected exception occurs', async () => {
leagueRepository.findById.mockRejectedValue(
seasonRepository.findById.mockRejectedValue(
new Error('Unexpected repository failure'),
);
const input: GetSeasonDetailsInput = {
leagueId: 'league-1',
seasonId: 'season-1',
};
@@ -169,5 +91,5 @@ describe('GetSeasonDetailsUseCase', () => {
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toBe('Unexpected repository failure');
});
});
});
});

View File

@@ -52,11 +52,11 @@ describe('GetSeasonSponsorshipsUseCase', () => {
findByLeagueId: vi.fn(),
};
useCase = new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepository as unknown as ISeasonSponsorshipRepository,
seasonRepository as unknown as ISeasonRepository,
leagueRepository as unknown as ILeagueRepository,
leagueMembershipRepository as unknown as ILeagueMembershipRepository,
raceRepository as unknown as IRaceRepository);
useCase = new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepository as any,
seasonRepository as any,
leagueRepository as any,
leagueMembershipRepository as any,
raceRepository as any);
});
it('returns SEASON_NOT_FOUND when season does not exist', async () => {
@@ -73,7 +73,7 @@ describe('GetSeasonSponsorshipsUseCase', () => {
>;
expect(err.code).toBe('SEASON_NOT_FOUND');
expect(err.details.message).toBe('Season not found');
});
});
it('returns LEAGUE_NOT_FOUND when league for season does not exist', async () => {
const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };
@@ -98,7 +98,7 @@ describe('GetSeasonSponsorshipsUseCase', () => {
>;
expect(err.code).toBe('LEAGUE_NOT_FOUND');
expect(err.details.message).toBe('League not found for season');
});
});
it('presents sponsorship details with computed metrics', async () => {
const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };
@@ -149,8 +149,9 @@ describe('GetSeasonSponsorshipsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
const presented = result.unwrap();
const presented = (expect(presented.seasonId).toBe('season-1');
expect(presented.seasonId).toBe('season-1');
expect(presented.sponsorships).toHaveLength(1);
const detail = presented.sponsorships[0]!;
@@ -187,5 +188,5 @@ describe('GetSeasonSponsorshipsUseCase', () => {
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
});
});
});
});

View File

@@ -1,24 +1,18 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { GetSponsorsUseCase } from './GetSponsorsUseCase';
import { SponsorRepository } from '../../domain/repositories/SponsorRepository';
import { Sponsor } from '../../domain/entities/sponsor/Sponsor';
describe('GetSponsorsUseCase', () => {
let useCase: GetSponsorsUseCase;
let sponsorRepository: {
findAll: Mock;
};
let output: {
present: Mock;
};
beforeEach(() => {
sponsorRepository = {
findAll: vi.fn(),
};
output = {
present: vi.fn(),
};
useCase = new GetSponsorsUseCase(sponsorRepository as unknown as ISponsorRepository);
useCase = new GetSponsorsUseCase(sponsorRepository as any);
});
it('should return all sponsors', async () => {
@@ -41,8 +35,9 @@ describe('GetSponsorsUseCase', () => {
const result = await useCase.execute();
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
});
const presented = result.unwrap();
expect(presented.sponsors).toEqual(sponsors);
});
it('should return error on repository failure', async () => {
sponsorRepository.findAll.mockRejectedValue(new Error('DB error'));
@@ -52,7 +47,7 @@ describe('GetSponsorsUseCase', () => {
expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toEqual({
code: 'REPOSITORY_ERROR',
message: 'Failed to fetch sponsors',
details: { message: 'DB error' },
});
});
});
});
});

View File

@@ -1,6 +1,7 @@
import type { Sponsor } from '../../domain/entities/sponsor/Sponsor';
import type { SponsorRepository } from '../../domain/repositories/SponsorRepository';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
export interface GetSponsorsInput {}
@@ -8,11 +9,20 @@ export interface GetSponsorsResult {
sponsors: Sponsor[];
}
export class GetSponsorsUseCase {
constructor(private readonly sponsorRepository: ISponsorRepository) {}
export type GetSponsorsErrorCode = 'REPOSITORY_ERROR';
async execute(_input: GetSponsorsInput): Promise<Result<GetSponsorsResult, never>> {
const sponsors = await this.sponsorRepository.findAll();
return Result.ok({ sponsors });
export class GetSponsorsUseCase {
constructor(private readonly sponsorRepository: SponsorRepository) {}
async execute(_input: GetSponsorsInput): Promise<Result<GetSponsorsResult, ApplicationErrorCode<GetSponsorsErrorCode, { message: string }>>> {
try {
const sponsors = await this.sponsorRepository.findAll();
return Result.ok({ sponsors });
} catch (error) {
return Result.err({
code: 'REPOSITORY_ERROR',
details: { message: error instanceof Error ? error.message : 'Unknown error' },
});
}
}
}
}

View File

@@ -5,8 +5,6 @@ import {
type GetTeamDetailsResult,
type GetTeamDetailsErrorCode,
} from './GetTeamDetailsUseCase';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -19,6 +17,7 @@ describe('GetTeamDetailsUseCase', () => {
let membershipRepository: {
getMembership: Mock;
};
beforeEach(() => {
teamRepository = {
findById: vi.fn(),
@@ -26,8 +25,8 @@ describe('GetTeamDetailsUseCase', () => {
membershipRepository = {
getMembership: vi.fn(),
};
useCase = new GetTeamDetailsUseCase(teamRepository as unknown as ITeamRepository,
membershipRepository as unknown as ITeamMembershipRepository);
useCase = new GetTeamDetailsUseCase(teamRepository as any,
membershipRepository as any);
});
it('should return team details with membership', async () => {
@@ -57,9 +56,7 @@ describe('GetTeamDetailsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetTeamDetailsResult;
const presented = result.unwrap();
expect(presented.team).toBe(team);
expect(presented.membership).toEqual(membership);
expect(presented.canManage).toBe(false);
@@ -92,9 +89,7 @@ describe('GetTeamDetailsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetTeamDetailsResult;
const presented = result.unwrap();
expect(presented.canManage).toBe(true);
});
@@ -114,7 +109,7 @@ describe('GetTeamDetailsUseCase', () => {
>;
expect(errorResult.code).toBe('TEAM_NOT_FOUND');
expect(errorResult.details?.message).toBe('Team not found');
});
});
it('should return error on repository failure', async () => {
const teamId = 'team-1';
@@ -132,5 +127,5 @@ describe('GetTeamDetailsUseCase', () => {
>;
expect(errorResult.code).toBe('REPOSITORY_ERROR');
expect(errorResult.details?.message).toBe('DB error');
});
});
});
});

View File

@@ -34,9 +34,9 @@ describe('GetTeamJoinRequestsUseCase', () => {
findById: vi.fn(),
};
useCase = new GetTeamJoinRequestsUseCase(membershipRepository as unknown as ITeamMembershipRepository,
driverRepository as unknown as IDriverRepository,
teamRepository as unknown as ITeamRepository);
useCase = new GetTeamJoinRequestsUseCase(membershipRepository as any,
driverRepository as any,
teamRepository as any);
});
it('should return join requests with drivers when team exists', async () => {

View File

@@ -1,17 +1,12 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
GetTeamMembersUseCase,
type GetTeamMembersInput,
type GetTeamMembersResult,
type GetTeamMembersErrorCode,
} from './GetTeamMembersUseCase';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { Driver } from '../../domain/entities/Driver';
import { Team } from '../../domain/entities/Team';
import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import {
GetTeamMembersUseCase,
type GetTeamMembersErrorCode,
type GetTeamMembersInput
} from './GetTeamMembersUseCase';
describe('GetTeamMembersUseCase', () => {
let useCase: GetTeamMembersUseCase;
@@ -46,10 +41,10 @@ describe('GetTeamMembersUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
useCase = new GetTeamMembersUseCase(membershipRepository as unknown as ITeamMembershipRepository,
driverRepository as unknown as IDriverRepository,
teamRepository as unknown as ITeamRepository,
logger as unknown as Logger);
useCase = new GetTeamMembersUseCase(membershipRepository as any,
driverRepository as any,
teamRepository as any,
logger as any);
});
it('should return team members with driver entities', async () => {

View File

@@ -1,12 +1,8 @@
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import type { TeamMembership } from '../../domain/types/TeamMembership';
import type { Team } from '../../domain/entities/Team';
import type { Driver } from '../../domain/entities/Driver';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export type GetTeamMembersInput = {
teamId: string;

View File

@@ -1,17 +1,15 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
GetTeamMembershipUseCase,
type GetTeamMembershipInput,
type GetTeamMembershipResult,
type GetTeamMembershipErrorCode,
} from './GetTeamMembershipUseCase';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
GetTeamMembershipUseCase,
type GetTeamMembershipErrorCode,
type GetTeamMembershipInput
} from './GetTeamMembershipUseCase';
describe('GetTeamMembershipUseCase', () => {
const mockGetMembership = vi.fn();
const mockMembershipRepo: ITeamMembershipRepository = {
const mockMembershipRepo: any = {
getMembership: mockGetMembership,
getActiveMembershipForDriver: vi.fn(),
getTeamMembers: vi.fn(),
@@ -34,13 +32,10 @@ describe('GetTeamMembershipUseCase', () => {
beforeEach(() => {
vi.clearAllMocks();
};
useCase = new GetTeamMembershipUseCase(mockMembershipRepo, mockLogger);
});
it('should present membership data when membership exists', async () => {
it('should return membership data when membership exists', async () => {
const teamId = 'team1';
const driverId = 'driver1';
const membership = {
@@ -58,16 +53,16 @@ describe('GetTeamMembershipUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.membership).toEqual({
expect(presented.membership).toEqual({
role: 'manager',
joinedAt: '2023-01-01T00:00:00.000Z',
isActive: true,
});
});
it('should present null membership when no membership exists', async () => {
it('should return null membership when no membership exists', async () => {
const teamId = 'team1';
const driverId = 'driver1';
@@ -78,9 +73,9 @@ describe('GetTeamMembershipUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.membership).toBeNull();
expect(presented.membership).toBeNull();
});
it('should map driver role to member', async () => {
@@ -101,9 +96,9 @@ describe('GetTeamMembershipUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.membership?.role).toBe('member');
expect(presented.membership?.role).toBe('member');
});
it('should return error when repository throws', async () => {
@@ -126,6 +121,5 @@ describe('GetTeamMembershipUseCase', () => {
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});
});

View File

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

View File

@@ -1,15 +1,11 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
GetTeamsLeaderboardUseCase,
type GetTeamsLeaderboardResult,
type GetTeamsLeaderboardInput,
type GetTeamsLeaderboardErrorCode,
} from './GetTeamsLeaderboardUseCase';
import { TeamRepository } from '../../domain/repositories/TeamRepository';
import { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import { Team } from '../../domain/entities/Team';
import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { Team } from '../../domain/entities/Team';
import {
GetTeamsLeaderboardUseCase,
type GetTeamsLeaderboardErrorCode,
type GetTeamsLeaderboardInput
} from './GetTeamsLeaderboardUseCase';
describe('GetTeamsLeaderboardUseCase', () => {
let useCase: GetTeamsLeaderboardUseCase;
@@ -26,6 +22,7 @@ describe('GetTeamsLeaderboardUseCase', () => {
warn: Mock;
error: Mock;
};
beforeEach(() => {
teamRepository = {
findAll: vi.fn(),
@@ -40,11 +37,10 @@ describe('GetTeamsLeaderboardUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
useCase = new GetTeamsLeaderboardUseCase(teamRepository as unknown as ITeamRepository,
teamMembershipRepository as unknown as ITeamMembershipRepository,
getDriverStats as unknown as (driverId: string) => { rating: number | null; wins: number; totalRaces: number } | null,
logger as unknown as Logger,
output,
useCase = new GetTeamsLeaderboardUseCase(teamRepository as any,
teamMembershipRepository as any,
getDriverStats as any,
logger as any
);
});
@@ -91,10 +87,7 @@ describe('GetTeamsLeaderboardUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = (expect(presentedRaw).toBeDefined();
const presented = presentedRaw as GetTeamsLeaderboardResult;
const presented = result.unwrap();
expect(presented.recruitingCount).toBe(2); // both teams are recruiting
expect(presented.items).toHaveLength(2);
@@ -134,5 +127,5 @@ describe('GetTeamsLeaderboardUseCase', () => {
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('Repository error');
});
});
});

View File

@@ -1,10 +1,8 @@
import type { TeamRepository } from '@core/racing/domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '@core/racing/domain/repositories/TeamMembershipRepository';
import { Team } from '@/racing/domain/entities/Team';
import { SkillLevelService, type SkillLevel } from '@core/racing/domain/services/SkillLevelService';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import type { Team } from '@core/racing/domain/entities/Team';
interface DriverStatsAdapter {
rating: number | null;

View File

@@ -35,7 +35,7 @@ describe('GetTotalLeaguesUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
expect(result.unwrap()).toEqual({ totalLeagues: 3 });
});
it('should return error on repository failure', async () => {

View File

@@ -46,9 +46,7 @@ describe('GetTotalRacesUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const payloadRaw = expect(payloadRaw).toBeDefined();
const payload = payloadRaw as GetTotalRacesResult;
const payload = result.unwrap();
expect(payload.totalRaces).toBe(2);
});

View File

@@ -1,18 +1,13 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
ImportRaceResultsApiUseCase,
type ImportRaceResultsApiInput,
type ImportRaceResultsApiResult,
type ImportRaceResultsApiErrorCode,
} from './ImportRaceResultsApiUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
ImportRaceResultsApiUseCase,
type ImportRaceResultsApiErrorCode,
type ImportRaceResultsApiInput,
type ImportRaceResultsApiResult,
} from './ImportRaceResultsApiUseCase';
describe('ImportRaceResultsApiUseCase', () => {
let useCase: ImportRaceResultsApiUseCase;
@@ -165,15 +160,12 @@ describe('ImportRaceResultsApiUseCase', () => {
standingRepository.recalculate.mockResolvedValue(undefined);
const result: Result<
void,
ImportRaceResultsApiResult,
ApplicationErrorCode<ImportRaceResultsApiErrorCode, { message: string }>
> = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as ImportRaceResultsApiResult;
const presented = result.unwrap();
expect(presented.success).toBe(true);
expect(presented.raceId).toBe('race-1');

View File

@@ -1,12 +1,6 @@
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import { Result as RaceResult } from '../../domain/entities/result/Result';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result as RaceResult } from '../../domain/entities/result/Result';
export type ImportRaceResultDTO = {
id: string;

View File

@@ -1,17 +1,11 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
ImportRaceResultsUseCase,
type ImportRaceResultsInput,
type ImportRaceResultsResult,
type ImportRaceResultsErrorCode,
} from './ImportRaceResultsUseCase';
import { RaceRepository } from '../../domain/repositories/RaceRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { ResultRepository } from '../../domain/repositories/ResultRepository';
import { DriverRepository } from '../../domain/repositories/DriverRepository';
import { StandingRepository } from '../../domain/repositories/StandingRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
ImportRaceResultsUseCase,
type ImportRaceResultsErrorCode,
type ImportRaceResultsInput
} from './ImportRaceResultsUseCase';
describe('ImportRaceResultsUseCase', () => {
let useCase: ImportRaceResultsUseCase;

View File

@@ -1,12 +1,6 @@
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import { Result as RaceResult } from '../../domain/entities/result/Result';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result as RaceResult } from '../../domain/entities/result/Result';
export type ImportRaceResultRow = {
id: string;

View File

@@ -1,13 +1,10 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
IsDriverRegisteredForRaceUseCase,
type IsDriverRegisteredForRaceInput,
type IsDriverRegisteredForRaceErrorCode,
type IsDriverRegisteredForRaceResult,
} from './IsDriverRegisteredForRaceUseCase';
import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
IsDriverRegisteredForRaceUseCase,
type IsDriverRegisteredForRaceErrorCode,
type IsDriverRegisteredForRaceInput
} from './IsDriverRegisteredForRaceUseCase';
describe('IsDriverRegisteredForRaceUseCase', () => {
let useCase: IsDriverRegisteredForRaceUseCase;
let registrationRepository: {
@@ -29,11 +26,8 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
warn: vi.fn(),
error: vi.fn(),
};
output = {
present: vi.fn(),
};
useCase = new IsDriverRegisteredForRaceUseCase(registrationRepository as unknown as IRaceRegistrationRepository,
logger as unknown as Logger);
useCase = new IsDriverRegisteredForRaceUseCase(registrationRepository as any,
logger as any);
});
it('should return true when driver is registered', async () => {

View File

@@ -49,9 +49,9 @@ describe('JoinLeagueUseCase', () => {
const result = await useCase.execute(command);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.membership.id).toBe('membership-1');
expect(presented.membership.id).toBe('membership-1');
expect(presented.membership.leagueId.toString()).toBe('league-1');
expect(presented.membership.driverId.toString()).toBe('driver-1');
expect(presented.membership.role.toString()).toBe('member');

View File

@@ -1,8 +1,7 @@
import type { Logger } from '@core/shared/application';
import type { LeagueMembershipRepository } from '@core/racing/domain/repositories/LeagueMembershipRepository';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
import { Result } from '@core/shared/domain/Result';
import { Result } from '@/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { LeagueMembership } from '../../domain/entities/LeagueMembership';
export type JoinLeagueErrorCode = 'ALREADY_MEMBER' | 'REPOSITORY_ERROR';

View File

@@ -1,15 +1,12 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
JoinTeamUseCase,
type JoinTeamInput,
type JoinTeamResult,
type JoinTeamErrorCode,
} from './JoinTeamUseCase';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { Logger } from '@core/shared/application';
import { Team } from '../../domain/entities/Team';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { Team } from '../../domain/entities/Team';
import {
JoinTeamUseCase,
type JoinTeamErrorCode,
type JoinTeamInput
} from './JoinTeamUseCase';
describe('JoinTeamUseCase', () => {
let useCase: JoinTeamUseCase;
@@ -46,7 +43,14 @@ describe('JoinTeamUseCase', () => {
it('should successfully join a team', async () => {
const input: JoinTeamInput = { teamId: 'team-1', driverId: 'driver-1' };
const team = Team.create({ id: 'team-1', name: 'Test Team', ownerId: 'owner-1' });
const team = Team.create({
id: 'team-1',
name: 'Test Team',
tag: 'TT',
description: 'Test Description',
ownerId: 'owner-1',
leagues: [],
});
const membership = {
teamId: 'team-1',
driverId: 'driver-1',

View File

@@ -1,10 +1,8 @@
import type { Logger } from '@core/shared/application';
import { TeamMembership } from '@/racing/domain/types/TeamMembership';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export type JoinTeamErrorCode =
| 'ALREADY_IN_TEAM'

View File

@@ -1,37 +1,31 @@
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Race } from '../../domain/entities/Race';
import { Season } from '../../domain/entities/season/Season';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import {
CreateLeagueSeasonScheduleRaceUseCase,
type CreateLeagueSeasonScheduleRaceErrorCode,
type CreateLeagueSeasonScheduleRaceResult,
CreateLeagueSeasonScheduleRaceUseCase,
type CreateLeagueSeasonScheduleRaceErrorCode
} from './CreateLeagueSeasonScheduleRaceUseCase';
import {
UpdateLeagueSeasonScheduleRaceUseCase,
type UpdateLeagueSeasonScheduleRaceErrorCode,
type UpdateLeagueSeasonScheduleRaceResult,
} from './UpdateLeagueSeasonScheduleRaceUseCase';
import {
DeleteLeagueSeasonScheduleRaceUseCase,
type DeleteLeagueSeasonScheduleRaceErrorCode,
type DeleteLeagueSeasonScheduleRaceResult,
DeleteLeagueSeasonScheduleRaceUseCase,
type DeleteLeagueSeasonScheduleRaceErrorCode
} from './DeleteLeagueSeasonScheduleRaceUseCase';
import {
PublishLeagueSeasonScheduleUseCase,
type PublishLeagueSeasonScheduleErrorCode,
type PublishLeagueSeasonScheduleResult,
PublishLeagueSeasonScheduleUseCase,
type PublishLeagueSeasonScheduleErrorCode
} from './PublishLeagueSeasonScheduleUseCase';
import {
UnpublishLeagueSeasonScheduleUseCase,
type UnpublishLeagueSeasonScheduleErrorCode,
type UnpublishLeagueSeasonScheduleResult,
UnpublishLeagueSeasonScheduleUseCase,
type UnpublishLeagueSeasonScheduleErrorCode
} from './UnpublishLeagueSeasonScheduleUseCase';
import {
UpdateLeagueSeasonScheduleRaceUseCase,
type UpdateLeagueSeasonScheduleRaceErrorCode
} from './UpdateLeagueSeasonScheduleRaceUseCase';
function createLogger(): Logger {
return {
@@ -62,7 +56,6 @@ describe('CreateLeagueSeasonScheduleRaceUseCase', () => {
beforeEach(() => {
seasonRepository = { findById: vi.fn() };
raceRepository = { create: vi.fn() };
output = { present: vi.fn() };
logger = createLogger();
});
@@ -71,8 +64,8 @@ describe('CreateLeagueSeasonScheduleRaceUseCase', () => {
seasonRepository.findById.mockResolvedValue(season);
raceRepository.create.mockImplementation(async (race: Race) => race);
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger,
{ generateRaceId: () => 'race-123' },
);
@@ -100,8 +93,8 @@ describe('CreateLeagueSeasonScheduleRaceUseCase', () => {
const season = createSeasonWithinWindow({ leagueId: 'other-league' });
seasonRepository.findById.mockResolvedValue(season);
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger,
{ generateRaceId: () => 'race-123' },
);
@@ -127,8 +120,8 @@ describe('CreateLeagueSeasonScheduleRaceUseCase', () => {
const season = createSeasonWithinWindow();
seasonRepository.findById.mockResolvedValue(season);
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new CreateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger,
{ generateRaceId: () => 'race-123' },
);
@@ -159,7 +152,6 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
beforeEach(() => {
seasonRepository = { findById: vi.fn() };
raceRepository = { findById: vi.fn(), update: vi.fn() };
output = { present: vi.fn() };
logger = createLogger();
});
@@ -177,8 +169,8 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
raceRepository.findById.mockResolvedValue(existing);
raceRepository.update.mockImplementation(async (race: Race) => race);
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const newScheduledAt = new Date('2025-01-20T20:00:00Z');
@@ -193,20 +185,16 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
expect(result.isOk()).toBe(true);
expect(raceRepository.update).toHaveBeenCalledTimes(1);
const updated = raceRepository.update.mock.calls[0]?.[0] as Race;
expect(updated.id).toBe('race-1');
expect(updated.leagueId).toBe('league-1');
expect(updated.track).toBe('New Track');
expect(updated.car).toBe('New Car');
expect(updated.scheduledAt.getTime()).toBe(newScheduledAt.getTime());
const presented = result.unwrap();
expect(presented.success).toBe(true);
});
it('returns SEASON_NOT_FOUND when season does not belong to league and does not read/update race', async () => {
const season = createSeasonWithinWindow({ leagueId: 'other-league' });
seasonRepository.findById.mockResolvedValue(season);
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -224,7 +212,7 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(raceRepository.findById).not.toHaveBeenCalled();
expect(raceRepository.update).not.toHaveBeenCalled();
});
});
it('returns RACE_OUTSIDE_SEASON_WINDOW when updated scheduledAt is outside window and does not update', async () => {
const season = createSeasonWithinWindow();
@@ -239,8 +227,8 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
});
raceRepository.findById.mockResolvedValue(existing);
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -257,15 +245,15 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
>;
expect(error.code).toBe('RACE_OUTSIDE_SEASON_WINDOW');
expect(raceRepository.update).not.toHaveBeenCalled();
});
});
it('returns RACE_NOT_FOUND when race does not exist for league and does not update', async () => {
const season = createSeasonWithinWindow();
seasonRepository.findById.mockResolvedValue(season);
raceRepository.findById.mockResolvedValue(null);
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new UpdateLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -282,7 +270,7 @@ describe('UpdateLeagueSeasonScheduleRaceUseCase', () => {
>;
expect(error.code).toBe('RACE_NOT_FOUND');
expect(raceRepository.update).not.toHaveBeenCalled();
});
});
});
describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
@@ -293,7 +281,6 @@ describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
beforeEach(() => {
seasonRepository = { findById: vi.fn() };
raceRepository = { findById: vi.fn(), delete: vi.fn() };
output = { present: vi.fn() };
logger = createLogger();
});
@@ -311,8 +298,8 @@ describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
raceRepository.findById.mockResolvedValue(existing);
raceRepository.delete.mockResolvedValue(undefined);
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -330,8 +317,8 @@ describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
const season = createSeasonWithinWindow({ leagueId: 'other-league' });
seasonRepository.findById.mockResolvedValue(season);
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -348,15 +335,15 @@ describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(raceRepository.findById).not.toHaveBeenCalled();
expect(raceRepository.delete).not.toHaveBeenCalled();
});
});
it('returns RACE_NOT_FOUND when race does not exist for league and does not delete', async () => {
const season = createSeasonWithinWindow();
seasonRepository.findById.mockResolvedValue(season);
raceRepository.findById.mockResolvedValue(null);
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as unknown as ISeasonRepository,
raceRepository as unknown as IRaceRepository,
const useCase = new DeleteLeagueSeasonScheduleRaceUseCase(seasonRepository as any,
raceRepository as any,
logger);
const result = await useCase.execute({
@@ -372,7 +359,7 @@ describe('DeleteLeagueSeasonScheduleRaceUseCase', () => {
>;
expect(error.code).toBe('RACE_NOT_FOUND');
expect(raceRepository.delete).not.toHaveBeenCalled();
});
});
});
describe('PublishLeagueSeasonScheduleUseCase', () => {
@@ -381,7 +368,6 @@ describe('PublishLeagueSeasonScheduleUseCase', () => {
beforeEach(() => {
seasonRepository = { findById: vi.fn(), update: vi.fn() };
output = { present: vi.fn() };
logger = createLogger();
});
@@ -390,24 +376,23 @@ describe('PublishLeagueSeasonScheduleUseCase', () => {
seasonRepository.findById.mockResolvedValue(season);
seasonRepository.update.mockResolvedValue(undefined);
const useCase = new PublishLeagueSeasonScheduleUseCase(seasonRepository as unknown as ISeasonRepository,
const useCase = new PublishLeagueSeasonScheduleUseCase(seasonRepository as any,
logger);
const result = await useCase.execute({ leagueId: 'league-1', seasonId: 'season-1' });
expect(result.isOk()).toBe(true);
expect(seasonRepository.update).toHaveBeenCalledTimes(1);
const updatedSeason = seasonRepository.update.mock.calls[0]?.[0] as Season;
expect(updatedSeason.id).toBe('season-1');
expect(updatedSeason.leagueId).toBe('league-1');
expect(updatedSeason.schedulePublished).toBe(true);
const presented = result.unwrap();
expect(presented.seasonId).toBe('season-1');
expect(presented.published).toBe(true);
});
it('returns SEASON_NOT_FOUND when season does not belong to league and does not update', async () => {
const season = createSeasonWithinWindow({ leagueId: 'other-league' });
seasonRepository.findById.mockResolvedValue(season);
const useCase = new PublishLeagueSeasonScheduleUseCase(seasonRepository as unknown as ISeasonRepository,
const useCase = new PublishLeagueSeasonScheduleUseCase(seasonRepository as any,
logger);
const result = await useCase.execute({ leagueId: 'league-1', seasonId: 'season-1' });
@@ -419,7 +404,7 @@ describe('PublishLeagueSeasonScheduleUseCase', () => {
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(seasonRepository.update).not.toHaveBeenCalled();
});
});
});
describe('UnpublishLeagueSeasonScheduleUseCase', () => {
@@ -428,7 +413,6 @@ describe('UnpublishLeagueSeasonScheduleUseCase', () => {
beforeEach(() => {
seasonRepository = { findById: vi.fn(), update: vi.fn() };
output = { present: vi.fn() };
logger = createLogger();
});
@@ -437,24 +421,23 @@ describe('UnpublishLeagueSeasonScheduleUseCase', () => {
seasonRepository.findById.mockResolvedValue(season);
seasonRepository.update.mockResolvedValue(undefined);
const useCase = new UnpublishLeagueSeasonScheduleUseCase(seasonRepository as unknown as ISeasonRepository,
const useCase = new UnpublishLeagueSeasonScheduleUseCase(seasonRepository as any,
logger);
const result = await useCase.execute({ leagueId: 'league-1', seasonId: 'season-1' });
expect(result.isOk()).toBe(true);
expect(seasonRepository.update).toHaveBeenCalledTimes(1);
const updatedSeason = seasonRepository.update.mock.calls[0]?.[0] as Season;
expect(updatedSeason.id).toBe('season-1');
expect(updatedSeason.leagueId).toBe('league-1');
expect(updatedSeason.schedulePublished).toBe(false);
const presented = result.unwrap();
expect(presented.seasonId).toBe('season-1');
expect(presented.published).toBe(false);
});
it('returns SEASON_NOT_FOUND when season does not belong to league and does not update', async () => {
const season = createSeasonWithinWindow({ leagueId: 'other-league' });
seasonRepository.findById.mockResolvedValue(season);
const useCase = new UnpublishLeagueSeasonScheduleUseCase(seasonRepository as unknown as ISeasonRepository,
const useCase = new UnpublishLeagueSeasonScheduleUseCase(seasonRepository as any,
logger);
const result = await useCase.execute({ leagueId: 'league-1', seasonId: 'season-1' });
@@ -466,5 +449,5 @@ describe('UnpublishLeagueSeasonScheduleUseCase', () => {
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
expect(seasonRepository.update).not.toHaveBeenCalled();
});
});
});
});

View File

@@ -53,9 +53,9 @@ describe('LeaveTeamUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
const presented = expect(presented.team.id).toBe('team-1');
expect(presented.team.id).toBe('team-1');
expect(presented.previousMembership).toEqual(membership);
});

View File

@@ -1,10 +1,8 @@
import type { Logger } from '@core/shared/application';
import { TeamMembership } from '@/racing/domain/types/TeamMembership';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Team } from '../../domain/entities/Team';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
import type { TeamMembership } from '../../domain/types/TeamMembership';
export type LeaveTeamErrorCode =
| 'TEAM_NOT_FOUND'

View File

@@ -7,7 +7,7 @@ import {
} from './ListLeagueScoringPresetsUseCase';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Result } from '@core/shared/domain/Result';
describe('ListLeagueScoringPresetsUseCase', () => {
let useCase: ListLeagueScoringPresetsUseCase;
beforeEach(() => {
@@ -47,21 +47,17 @@ describe('ListLeagueScoringPresetsUseCase', () => {
createConfig: vi.fn(),
},
];
useCase = new ListLeagueScoringPresetsUseCase(mockPresets);
useCase = new ListLeagueScoringPresetsUseCase(mockPresets as any);
});
it('should list presets successfully', async () => {
const input: ListLeagueScoringPresetsInput = {};
const result: Result<
void,
ApplicationErrorCode<ListLeagueScoringPresetsErrorCode, { message: string }>
> = await useCase.execute(input);
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const firstCall = const presented = firstCall[0] as ListLeagueScoringPresetsResult;
const presented = result.unwrap();
expect(presented).toEqual({
presets: [
@@ -101,21 +97,18 @@ describe('ListLeagueScoringPresetsUseCase', () => {
});
});
it('should wrap repository errors in ApplicationErrorCode and not call output', async () => {
it('should return repository error when mapping fails', async () => {
const failingPresets = {
map: () => {
throw new Error('Repository failure');
},
} as unknown as never[];
} as any;
useCase = new ListLeagueScoringPresetsUseCase(failingPresets);
const input: ListLeagueScoringPresetsInput = {};
const result: Result<
void,
ApplicationErrorCode<ListLeagueScoringPresetsErrorCode, { message: string }>
> = await useCase.execute(input);
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
@@ -126,5 +119,5 @@ describe('ListLeagueScoringPresetsUseCase', () => {
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details?.message).toBe('Repository failure');
});
});
});
});

View File

@@ -19,8 +19,7 @@ describe('ManageSeasonLifecycleUseCase', () => {
findById: Mock;
update: Mock;
};
};
beforeEach(() => {
leagueRepository = {
findById: vi.fn(),
@@ -29,11 +28,10 @@ describe('ManageSeasonLifecycleUseCase', () => {
findById: vi.fn(),
update: vi.fn(),
};
};
useCase = new ManageSeasonLifecycleUseCase(leagueRepository as unknown as ILeagueRepository,
seasonRepository as unknown as ISeasonRepository);
useCase = new ManageSeasonLifecycleUseCase(leagueRepository as any,
seasonRepository as any);
});
it('applies activate → complete → archive transitions and persists state', async () => {
const league = { id: 'league-1' };
let currentSeason = Season.create({
@@ -43,57 +41,48 @@ describe('ManageSeasonLifecycleUseCase', () => {
name: 'Lifecycle Season',
status: 'planned',
});
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockImplementation(() => Promise.resolve(currentSeason));
seasonRepository.update.mockImplementation((s) => {
currentSeason = s;
return Promise.resolve(s);
});
const activateInput: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: currentSeason.id,
transition: 'activate',
};
const activated = await useCase.execute(activateInput);
expect(activated.isOk()).toBe(true);
expect(activated.unwrap()).toBeUndefined();
const [firstCall] = const [firstArg] = firstCall as [ManageSeasonLifecycleResult];
let presented = firstArg;
let presented = activated.unwrap();
expect(presented.season.status.toString()).toBe('active');
(const completeInput: ManageSeasonLifecycleInput = {
const completeInput: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: currentSeason.id,
transition: 'complete',
};
const completed = await useCase.execute(completeInput);
expect(completed.isOk()).toBe(true);
expect(completed.unwrap()).toBeUndefined();
{
const [[arg]] = presented = arg;
}
presented = completed.unwrap();
expect(presented.season.status.toString()).toBe('completed');
(const archiveInput: ManageSeasonLifecycleInput = {
const archiveInput: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: currentSeason.id,
transition: 'archive',
};
const archived = await useCase.execute(archiveInput);
expect(archived.isOk()).toBe(true);
expect(archived.unwrap()).toBeUndefined();
{
const presentedRaw = expect(presentedRaw).toBeDefined();
presented = presentedRaw as ManageSeasonLifecycleResult;
}
presented = archived.unwrap();
expect(presented.season.status.toString()).toBe('archived');
});
it('propagates domain invariant errors for invalid transitions', async () => {
const league = { id: 'league-1' };
const season = Season.create({
@@ -103,16 +92,16 @@ describe('ManageSeasonLifecycleUseCase', () => {
name: 'Lifecycle Season',
status: 'planned',
});
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(season);
const completeInput: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: season.id,
transition: 'complete',
};
const result = await useCase.execute(completeInput);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
@@ -120,17 +109,17 @@ describe('ManageSeasonLifecycleUseCase', () => {
{ message: string }
>;
expect(error.code).toEqual('INVALID_TRANSITION');
});
});
it('returns error when league not found', async () => {
leagueRepository.findById.mockResolvedValue(null);
const input: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: 'season-1',
transition: 'activate',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
@@ -139,19 +128,19 @@ describe('ManageSeasonLifecycleUseCase', () => {
>;
expect(error.code).toEqual('LEAGUE_NOT_FOUND');
expect(error.details).toEqual({ message: 'League not found: league-1' });
});
});
it('returns error when season not found', async () => {
const league = { id: 'league-1' };
leagueRepository.findById.mockResolvedValue(league);
seasonRepository.findById.mockResolvedValue(null);
const input: ManageSeasonLifecycleInput = {
leagueId: 'league-1',
seasonId: 'season-1',
transition: 'activate',
};
const result = await useCase.execute(input);
expect(result.isErr()).toBe(true);
const error = result.unwrapErr() as ApplicationErrorCode<
@@ -162,5 +151,5 @@ describe('ManageSeasonLifecycleUseCase', () => {
expect(error.details).toEqual({
message: 'Season season-1 does not belong to league league-1',
});
});
});
});
});

View File

@@ -1,12 +1,11 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
PreviewLeagueScheduleUseCase,
type PreviewLeagueScheduleResult,
type PreviewLeagueScheduleInput,
type PreviewLeagueScheduleErrorCode,
} from './PreviewLeagueScheduleUseCase';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
PreviewLeagueScheduleUseCase,
type PreviewLeagueScheduleErrorCode,
type PreviewLeagueScheduleInput
} from './PreviewLeagueScheduleUseCase';
describe('PreviewLeagueScheduleUseCase', () => {
let useCase: PreviewLeagueScheduleUseCase;

View File

@@ -1,14 +1,14 @@
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay';
import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone';
import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy';
import { WeekdaySet } from '../../domain/value-objects/WeekdaySet';
import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern';
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import { ALL_WEEKDAYS, type Weekday } from '../../domain/types/Weekday';
import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone';
import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern';
import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay';
import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy';
import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import { WeekdaySet } from '../../domain/value-objects/WeekdaySet';
export type SeasonScheduleConfig = {
seasonStartDate: string;
recurrenceStrategy: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday';

View File

@@ -1,13 +1,8 @@
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
export type PublishLeagueSeasonScheduleInput = {
leagueId: string;
seasonId: string;
};
export type PublishLeagueSeasonScheduleResult = {
success: true;

View File

@@ -1,10 +1,7 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { QuickPenaltyUseCase, type QuickPenaltyInput, type QuickPenaltyResult, type QuickPenaltyErrorCode } from './QuickPenaltyUseCase';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { QuickPenaltyUseCase, type QuickPenaltyErrorCode, type QuickPenaltyInput } from './QuickPenaltyUseCase';
describe('QuickPenaltyUseCase', () => {
let useCase: QuickPenaltyUseCase;

View File

@@ -5,14 +5,11 @@
* Designed for fast, common penalty scenarios like track limits, warnings, etc.
*/
import { Penalty } from '../../domain/entities/penalty/Penalty';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { randomUUID } from 'crypto';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import { Result } from '@/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { randomUUID } from 'crypto';
import { Penalty } from '../../domain/entities/penalty/Penalty';
export type QuickPenaltyErrorCode = 'RACE_NOT_FOUND' | 'UNAUTHORIZED' | 'UNKNOWN_INFRACTION' | 'REPOSITORY_ERROR';

View File

@@ -1,22 +1,12 @@
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
RecalculateChampionshipStandingsUseCase,
type RecalculateChampionshipStandingsInput,
type RecalculateChampionshipStandingsResult,
type RecalculateChampionshipStandingsErrorCode,
} from './RecalculateChampionshipStandingsUseCase';
import type { SeasonRepository } from '../../domain/repositories/SeasonRepository';
import type { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { ChampionshipStandingRepository } from '../../domain/repositories/ChampionshipStandingRepository';
import type { Penalty } from '../../domain/entities/Penalty';
import { EventScoringService } from '../../domain/services/EventScoringService';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { ChampionshipAggregator } from '../../domain/services/ChampionshipAggregator';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import type { Penalty } from '../../domain/entities/Penalty';
import {
RecalculateChampionshipStandingsUseCase,
type RecalculateChampionshipStandingsErrorCode,
type RecalculateChampionshipStandingsInput
} from './RecalculateChampionshipStandingsUseCase';
describe('RecalculateChampionshipStandingsUseCase', () => {
let useCase: RecalculateChampionshipStandingsUseCase;
@@ -30,7 +20,6 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
let eventScoringService: { scoreSession: Mock };
let championshipAggregator: { aggregate: Mock };
let logger: Logger;
};
beforeEach(() => {
leagueRepository = { findById: vi.fn() };
@@ -47,18 +36,17 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
output = { present: vi.fn() } as unknown as typeof output;
} as any;
useCase = new RecalculateChampionshipStandingsUseCase(leagueRepository as unknown as ILeagueRepository,
seasonRepository as unknown as ISeasonRepository,
leagueScoringConfigRepository as unknown as ILeagueScoringConfigRepository,
raceRepository as unknown as IRaceRepository,
resultRepository as unknown as IResultRepository,
penaltyRepository as unknown as IPenaltyRepository,
championshipStandingRepository as unknown as IChampionshipStandingRepository,
eventScoringService as unknown as EventScoringService,
championshipAggregator as unknown as ChampionshipAggregator,
useCase = new RecalculateChampionshipStandingsUseCase(leagueRepository as any,
seasonRepository as any,
leagueScoringConfigRepository as any,
raceRepository as any,
resultRepository as any,
penaltyRepository as any,
championshipStandingRepository as any,
eventScoringService as any,
championshipAggregator as any,
logger);
});
@@ -79,7 +67,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
>;
expect(error.code).toBe('LEAGUE_NOT_FOUND');
expect(error.details.message).toContain('league-1');
});
});
it('returns season not found error when season does not exist', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
@@ -98,7 +86,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
{ message: string }
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
});
});
it('returns season not found error when season belongs to different league', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
@@ -117,9 +105,9 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
{ message: string }
>;
expect(error.code).toBe('SEASON_NOT_FOUND');
});
});
it('recalculates standings successfully and presents result', async () => {
it('recalculates standings successfully and returns result', async () => {
const league = { id: 'league-1' };
const season = { id: 'season-1', leagueId: 'league-1' };
const championship = {
@@ -161,9 +149,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as RecalculateChampionshipStandingsResult;
const presented = result.unwrap();
expect(presented.leagueId).toBe('league-1');
expect(presented.seasonId).toBe('season-1');
expect(presented.entries).toHaveLength(1);
@@ -175,7 +161,7 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
});
});
it('wraps repository failures in REPOSITORY_ERROR and does not call output', async () => {
it('wraps repository failures in REPOSITORY_ERROR', async () => {
leagueRepository.findById.mockResolvedValue({ id: 'league-1' });
seasonRepository.findById.mockResolvedValue({ id: 'season-1', leagueId: 'league-1' });
leagueScoringConfigRepository.findBySeasonId.mockImplementation(() => {
@@ -196,5 +182,5 @@ describe('RecalculateChampionshipStandingsUseCase', () => {
>;
expect(error.code).toBe('REPOSITORY_ERROR');
expect(error.details.message).toContain('boom');
});
});
});

View File

@@ -1,20 +1,13 @@
import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository';
import type { LeagueScoringConfigRepository } from '@core/racing/domain/repositories/LeagueScoringConfigRepository';
import type { RaceRepository } from '@core/racing/domain/repositories/RaceRepository';
import type { ResultRepository } from '@core/racing/domain/repositories/ResultRepository';
import type { PenaltyRepository } from '@core/racing/domain/repositories/PenaltyRepository';
import type { ChampionshipStandingRepository } from '@core/racing/domain/repositories/ChampionshipStandingRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
import { ChampionshipStanding } from '@/racing/domain/entities/championship/ChampionshipStanding';
import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator';
import { EventScoringService } from '@core/racing/domain/services/EventScoringService';
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import type { ChampionshipStanding } from '@core/racing/domain/entities/championship/ChampionshipStanding';
import { EventScoringService } from '@core/racing/domain/services/EventScoringService';
import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
export type RecalculateChampionshipStandingsInput = {
leagueId: string;
@@ -132,7 +125,7 @@ export class RecalculateChampionshipStandingsUseCase {
})),
};
return Result.ok(undefined);
return Result.ok(result);
} catch (error) {
const err = error as Error;
this.logger.error('Failed to recalculate championship standings', err, {

View File

@@ -86,15 +86,13 @@ describe('RegisterForRaceUseCase', () => {
const result = await useCase.execute(buildInput());
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as RegisterForRaceResult;
const presented = result.unwrap();
expect(presented).toEqual<RegisterForRaceResult>({
raceId: 'race-1',
driverId: 'driver-1',
status: 'registered',
});
expect(registrationRepository.register).toHaveBeenCalled();
});
it('wraps unexpected repository errors', async () => {

View File

@@ -1,14 +1,13 @@
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import {
RejectSponsorshipRequestUseCase,
type RejectSponsorshipRequestInput,
type RejectSponsorshipRequestResult,
type RejectSponsorshipRequestErrorCode,
RejectSponsorshipRequestUseCase,
type RejectSponsorshipRequestErrorCode,
type RejectSponsorshipRequestInput,
type RejectSponsorshipRequestResult,
} from './RejectSponsorshipRequestUseCase';
import type { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
describe('RejectSponsorshipRequestUseCase', () => {
let useCase: RejectSponsorshipRequestUseCase;

View File

@@ -4,16 +4,10 @@
* Allows an entity owner to reject a sponsorship request.
*/
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
export type RejectSponsorshipRequestInput = {
requestId: string;
respondedBy: string;
reason?: string;
};
export type RejectSponsorshipRequestResult = {
requestId: string;

View File

@@ -78,8 +78,8 @@ describe('RejectTeamJoinRequestUseCase', () => {
const result = await useCase.execute(input);
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = expect(presented.teamId).toBe('team-1');
const presented = result.unwrap();
expect(presented.teamId).toBe('team-1');
expect(presented.requestId).toBe('req-1');
expect(presented.status).toBe('rejected');
expect(membershipRepository.removeJoinRequest).toHaveBeenCalledWith('req-1');

View File

@@ -1,15 +1,7 @@
import type { Logger } from '@core/shared/application';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { TeamMembershipRepository } from '../../domain/repositories/TeamMembershipRepository';
import type { TeamRepository } from '../../domain/repositories/TeamRepository';
export type RejectTeamJoinRequestInput = {
teamId: string;
managerId: string;
requestId: string;
reason?: string;
};
export type RejectTeamJoinRequestResult = {
teamId: string;

View File

@@ -40,8 +40,9 @@ describe('RemoveLeagueMemberUseCase', () => {
expect(result.isOk()).toBe(true);
const removeResult = result.unwrap();
expect(removeResult.success).toBe(true);
expect(removeResult.message).toBeDefined();
expect(removeResult.leagueId).toBe(leagueId);
expect(removeResult.memberId).toBe(targetDriverId);
expect(removeResult.removedRole).toBe('member');
expect(leagueMembershipRepository.saveMembership).toHaveBeenCalledTimes(1);
const savedMembership = leagueMembershipRepository.saveMembership.mock.calls[0]?.[0];

View File

@@ -1,15 +1,12 @@
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
ReopenRaceUseCase,
type ReopenRaceInput,
type ReopenRaceResult,
type ReopenRaceErrorCode,
} from './ReopenRaceUseCase';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/application';
import { Race } from '../../domain/entities/Race';
import { SessionType } from '../../domain/value-objects/SessionType';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import {
ReopenRaceUseCase,
type ReopenRaceErrorCode,
type ReopenRaceInput
} from './ReopenRaceUseCase';
describe('ReopenRaceUseCase', () => {
let raceRepository: {
@@ -37,8 +34,8 @@ describe('ReopenRaceUseCase', () => {
error: vi.fn(),
};
useCase = new ReopenRaceUseCase(raceRepository as unknown as IRaceRepository,
logger as unknown as Logger);
useCase = new ReopenRaceUseCase(raceRepository as any,
logger as any);
});
it('returns RACE_NOT_FOUND when race does not exist', async () => {
@@ -55,9 +52,9 @@ describe('ReopenRaceUseCase', () => {
>;
expect(err.code).toBe('RACE_NOT_FOUND');
expect(err.details.message).toContain('race-404');
});
});
it('reopens a completed race, persists, and presents the result', async () => {
it('reopens a completed race, persists, and returns the result', async () => {
const race = Race.create({
id: 'race-1',
leagueId: 'league-1',
@@ -81,7 +78,8 @@ describe('ReopenRaceUseCase', () => {
expect(updatedRace.id).toBe('race-1');
expect(updatedRace.status.toString()).toBe('scheduled');
const presented = (expect(presented.race.id).toBe('race-1');
const presented = result.unwrap();
expect(presented.race.id).toBe('race-1');
expect(presented.race.status.toString()).toBe('scheduled');
expect(logger.info).toHaveBeenCalled();
@@ -110,7 +108,7 @@ describe('ReopenRaceUseCase', () => {
>;
expect(err.code).toBe('INVALID_RACE_STATE');
expect(err.details.message).toContain('already scheduled');
});
});
it('returns INVALID_RACE_STATE when race is running', async () => {
const race = Race.create({
@@ -135,7 +133,7 @@ describe('ReopenRaceUseCase', () => {
>;
expect(err.code).toBe('INVALID_RACE_STATE');
expect(err.details.message).toContain('running race');
});
});
it('returns REPOSITORY_ERROR when repository throws unexpected error', async () => {
raceRepository.findById.mockRejectedValue(new Error('DB error'));
@@ -150,5 +148,5 @@ describe('ReopenRaceUseCase', () => {
>;
expect(err.code).toBe('REPOSITORY_ERROR');
expect(err.details.message).toBe('DB error');
});
});
});
});

View File

@@ -1,5 +1,3 @@
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';

View File

@@ -132,12 +132,10 @@ describe('RequestProtestDefenseUseCase', () => {
const result = await useCase.execute(createInput());
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBeUndefined();
const presented = result.unwrap();
expect(protestRepository.update).toHaveBeenCalledWith(updatedProtest);
const presentedRaw = expect(presentedRaw).toBeDefined();
const presented = presentedRaw as RequestProtestDefenseResult;
expect(presented).toEqual({
leagueId: 'league-1',
protestId: 'protest-1',

View File

@@ -4,10 +4,6 @@
* Allows a steward to review a protest and make a decision (uphold or dismiss).
*/
import type { ProtestRepository } from '../../domain/repositories/ProtestRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

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