fix seeds

This commit is contained in:
2025-12-27 01:53:36 +01:00
parent 901fb1ac83
commit 15435c93fc
12 changed files with 259 additions and 200 deletions

View File

@@ -1,4 +1,5 @@
import { Driver } from '@core/racing/domain/entities/Driver'; import { Driver } from '@core/racing/domain/entities/Driver';
import { faker } from '@faker-js/faker';
export class RacingDriverFactory { export class RacingDriverFactory {
constructor( constructor(
@@ -7,7 +8,7 @@ export class RacingDriverFactory {
) {} ) {}
create(): Driver[] { create(): Driver[] {
const countries = ['DE', 'NL', 'FR', 'GB', 'US', 'CA', 'SE', 'NO', 'IT', 'ES'] as const; const countries = ['DE', 'NL', 'FR', 'GB', 'US', 'CA', 'SE', 'NO', 'IT', 'ES', 'AU', 'BR', 'JP', 'KR', 'RU', 'PL', 'CZ', 'HU', 'AT', 'CH'] as const;
return Array.from({ length: this.driverCount }, (_, idx) => { return Array.from({ length: this.driverCount }, (_, idx) => {
const i = idx + 1; const i = idx + 1;
@@ -15,15 +16,11 @@ export class RacingDriverFactory {
return Driver.create({ return Driver.create({
id: `driver-${i}`, id: `driver-${i}`,
iracingId: String(100000 + i), iracingId: String(100000 + i),
name: `Driver ${i}`, name: faker.person.fullName(),
country: countries[idx % countries.length]!, country: faker.helpers.arrayElement(countries),
bio: `Demo driver #${i} seeded for in-memory mode.`, bio: faker.lorem.sentences(2),
joinedAt: this.addDays(this.baseDate, -90 + i), joinedAt: faker.date.past({ years: 2, refDate: this.baseDate }),
}); });
}); });
} }
private addDays(date: Date, days: number): Date {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
}
} }

View File

@@ -1,100 +1,58 @@
import { League } from '@core/racing/domain/entities/League'; import { League } from '@core/racing/domain/entities/League';
import { Driver } from '@core/racing/domain/entities/Driver';
import { faker } from '@faker-js/faker';
export class RacingLeagueFactory { export class RacingLeagueFactory {
constructor(private readonly baseDate: Date) {} constructor(
private readonly baseDate: Date,
private readonly drivers: Driver[],
) {}
create(): League[] { create(): League[] {
const createdAtBase = this.baseDate; const leagueCount = 20;
const pointsSystems = ['f1-2024', 'indycar', 'custom'] as const;
const qualifyingFormats = ['open', 'single-lap'] as const;
return [ return Array.from({ length: leagueCount }, (_, idx) => {
League.create({ const i = idx + 1;
id: 'league-1', const owner = faker.helpers.arrayElement(this.drivers);
name: 'GridPilot Sprint Series', const socialLinks: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string } = {};
description: 'Weekly sprint races with stable grids.', if (faker.datatype.boolean()) socialLinks.discordUrl = faker.internet.url();
ownerId: 'driver-1', if (faker.datatype.boolean()) socialLinks.youtubeUrl = faker.internet.url();
settings: { if (faker.datatype.boolean()) socialLinks.websiteUrl = faker.internet.url();
pointsSystem: 'f1-2024',
maxDrivers: 24,
sessionDuration: 60,
qualifyingFormat: 'open',
},
createdAt: this.addDays(createdAtBase, -200),
socialLinks: {
discordUrl: 'https://discord.gg/gridpilot-demo',
youtubeUrl: 'https://youtube.com/@gridpilot-demo',
websiteUrl: 'https://gridpilot-demo.example.com',
},
}),
League.create({
id: 'league-2',
name: 'GridPilot Endurance Cup',
description: 'Longer races with strategy and consistency.',
ownerId: 'driver-2',
settings: {
pointsSystem: 'indycar',
maxDrivers: 32,
sessionDuration: 120,
qualifyingFormat: 'open',
},
createdAt: this.addDays(createdAtBase, -180),
socialLinks: { discordUrl: 'https://discord.gg/gridpilot-endurance' },
}),
League.create({
id: 'league-3',
name: 'GridPilot Club Ladder',
description: 'Casual ladder with fast onboarding.',
ownerId: 'driver-3',
settings: {
pointsSystem: 'f1-2024',
maxDrivers: 48,
sessionDuration: 45,
qualifyingFormat: 'single-lap',
},
createdAt: this.addDays(createdAtBase, -160),
}),
League.create({
id: 'league-4',
name: 'Nordic Night Series',
description: 'Evening races with tight fields.',
ownerId: 'driver-4',
settings: {
pointsSystem: 'f1-2024',
maxDrivers: 32,
sessionDuration: 60,
qualifyingFormat: 'open',
},
createdAt: this.addDays(createdAtBase, -150),
}),
League.create({
id: 'league-5',
name: 'Demo League (Admin)',
description: 'Primary demo league owned by driver-1.',
ownerId: 'driver-1',
settings: {
pointsSystem: 'f1-2024',
maxDrivers: 24,
sessionDuration: 60,
qualifyingFormat: 'open',
},
createdAt: this.addDays(createdAtBase, -140),
}),
League.create({
id: 'league-6',
name: 'Sim Racing Alliance',
description: 'Mixed-format season with community events.',
ownerId: 'driver-5',
settings: {
pointsSystem: 'indycar',
maxDrivers: 40,
sessionDuration: 90,
qualifyingFormat: 'open',
},
createdAt: this.addDays(createdAtBase, -130),
}),
];
}
private addDays(date: Date, days: number): Date { const leagueData: {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000); id: string;
name: string;
description: string;
ownerId: string;
settings: {
pointsSystem: 'f1-2024' | 'indycar' | 'custom';
maxDrivers: number;
sessionDuration: number;
qualifyingFormat: 'open' | 'single-lap';
};
createdAt: Date;
socialLinks?: { discordUrl?: string; youtubeUrl?: string; websiteUrl?: string };
} = {
id: `league-${i}`,
name: faker.company.name() + ' Racing League',
description: faker.lorem.sentences(2),
ownerId: owner.id.toString(),
settings: {
pointsSystem: faker.helpers.arrayElement(pointsSystems),
maxDrivers: faker.number.int({ min: 20, max: 50 }),
sessionDuration: faker.number.int({ min: 30, max: 180 }),
qualifyingFormat: faker.helpers.arrayElement(qualifyingFormats),
},
createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
};
if (Object.keys(socialLinks).length > 0) {
leagueData.socialLinks = socialLinks;
}
return League.create(leagueData);
});
} }
} }

View File

@@ -1,36 +1,31 @@
import { League } from '@core/racing/domain/entities/League'; import { League } from '@core/racing/domain/entities/League';
import { Race } from '@core/racing/domain/entities/Race'; import { Race } from '@core/racing/domain/entities/Race';
import { Track } from '@core/racing/domain/entities/Track';
export class RacingRaceFactory { export class RacingRaceFactory {
constructor(private readonly baseDate: Date) {} constructor(private readonly baseDate: Date) {}
create(leagues: League[]): Race[] { create(leagues: League[], tracks: Track[]): Race[] {
const tracks = [
'Monza GP',
'Spa-Francorchamps',
'Suzuka',
'Mount Panorama',
'Silverstone GP',
'Interlagos',
'Imola',
'Laguna Seca',
];
const cars = ['GT3 Porsche 911', 'GT3 BMW M4', 'LMP3 Prototype', 'GT4 Alpine', 'Touring Civic']; const cars = ['GT3 Porsche 911', 'GT3 BMW M4', 'LMP3 Prototype', 'GT4 Alpine', 'Touring Civic'];
const leagueIds = leagues.map((l) => l.id.toString()); const leagueIds = leagues.map((l) => l.id.toString());
const trackIds = tracks.map((t) => t.id);
const demoLeagueId = 'league-5'; const demoLeagueId = 'league-5';
const races: Race[] = []; const races: Race[] = [];
for (let i = 1; i <= 25; i++) { for (let i = 1; i <= 50; i++) {
const leagueId = leagueIds[(i - 1) % leagueIds.length] ?? demoLeagueId; const leagueId = leagueIds[(i - 1) % leagueIds.length] ?? demoLeagueId;
const trackId = trackIds[(i - 1) % trackIds.length]!;
const track = tracks.find(t => t.id === trackId)!;
const scheduledAt = this.addDays(this.baseDate, i <= 10 ? -35 + i : 1 + (i - 10) * 2); const scheduledAt = this.addDays(this.baseDate, i <= 10 ? -35 + i : 1 + (i - 10) * 2);
const base = { const base = {
id: `race-${i}`, id: `race-${i}`,
leagueId, leagueId,
scheduledAt, scheduledAt,
track: tracks[(i - 1) % tracks.length]!, track: track.name.toString(),
trackId: track.id,
car: cars[(i - 1) % cars.length]!, car: cars[(i - 1) % cars.length]!,
}; };

View File

@@ -6,6 +6,7 @@ import { RaceRegistration } from '@core/racing/domain/entities/RaceRegistration'
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result'; import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
import { Standing } from '@core/racing/domain/entities/Standing'; import { Standing } from '@core/racing/domain/entities/Standing';
import { Team } from '@core/racing/domain/entities/Team'; import { Team } from '@core/racing/domain/entities/Team';
import { Track } from '@core/racing/domain/entities/Track';
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership'; import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
import type { FeedItem } from '@core/social/domain/types/FeedItem'; import type { FeedItem } from '@core/social/domain/types/FeedItem';
import { RacingDriverFactory } from './RacingDriverFactory'; import { RacingDriverFactory } from './RacingDriverFactory';
@@ -17,6 +18,7 @@ import { RacingRaceFactory } from './RacingRaceFactory';
import { RacingResultFactory } from './RacingResultFactory'; import { RacingResultFactory } from './RacingResultFactory';
import { RacingStandingFactory } from './RacingStandingFactory'; import { RacingStandingFactory } from './RacingStandingFactory';
import { RacingTeamFactory } from './RacingTeamFactory'; import { RacingTeamFactory } from './RacingTeamFactory';
import { RacingTrackFactory } from './RacingTrackFactory';
export type Friendship = { export type Friendship = {
driverId: string; driverId: string;
@@ -33,6 +35,7 @@ export type RacingSeed = {
raceRegistrations: RaceRegistration[]; raceRegistrations: RaceRegistration[];
teams: Team[]; teams: Team[];
teamMemberships: TeamMembership[]; teamMemberships: TeamMembership[];
tracks: Track[];
friendships: Friendship[]; friendships: Friendship[];
feedEvents: FeedItem[]; feedEvents: FeedItem[];
}; };
@@ -45,7 +48,7 @@ export type RacingSeedOptions = {
export const racingSeedDefaults: Readonly< export const racingSeedDefaults: Readonly<
Required<RacingSeedOptions> Required<RacingSeedOptions>
> = { > = {
driverCount: 32, driverCount: 100,
baseDate: new Date('2025-01-15T12:00:00.000Z'), baseDate: new Date('2025-01-15T12:00:00.000Z'),
}; };
@@ -60,23 +63,25 @@ class RacingSeedFactory {
create(): RacingSeed { create(): RacingSeed {
const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate); const driverFactory = new RacingDriverFactory(this.driverCount, this.baseDate);
const leagueFactory = new RacingLeagueFactory(this.baseDate); const trackFactory = new RacingTrackFactory();
const raceFactory = new RacingRaceFactory(this.baseDate); const raceFactory = new RacingRaceFactory(this.baseDate);
const resultFactory = new RacingResultFactory(); const resultFactory = new RacingResultFactory();
const standingFactory = new RacingStandingFactory(); const standingFactory = new RacingStandingFactory();
const membershipFactory = new RacingMembershipFactory(this.baseDate); const membershipFactory = new RacingMembershipFactory(this.baseDate);
const teamFactory = new RacingTeamFactory(this.baseDate);
const friendshipFactory = new RacingFriendshipFactory(); const friendshipFactory = new RacingFriendshipFactory();
const feedFactory = new RacingFeedFactory(this.baseDate); const feedFactory = new RacingFeedFactory(this.baseDate);
const drivers = driverFactory.create(); const drivers = driverFactory.create();
const tracks = trackFactory.create();
const leagueFactory = new RacingLeagueFactory(this.baseDate, drivers);
const leagues = leagueFactory.create(); const leagues = leagueFactory.create();
const races = raceFactory.create(leagues); const teamFactory = new RacingTeamFactory(this.baseDate, drivers, leagues);
const teams = teamFactory.createTeams();
const races = raceFactory.create(leagues, tracks);
const results = resultFactory.create(drivers, races); const results = resultFactory.create(drivers, races);
const standings = standingFactory.create(leagues, races, results); const standings = standingFactory.create(leagues, races, results);
const leagueMemberships = membershipFactory.createLeagueMemberships(drivers, leagues); const leagueMemberships = membershipFactory.createLeagueMemberships(drivers, leagues);
const raceRegistrations = membershipFactory.createRaceRegistrations(races); const raceRegistrations = membershipFactory.createRaceRegistrations(races);
const teams = teamFactory.createTeams();
const teamMemberships = teamFactory.createTeamMemberships(drivers, teams); const teamMemberships = teamFactory.createTeamMemberships(drivers, teams);
const friendships = friendshipFactory.create(drivers); const friendships = friendshipFactory.create(drivers);
const feedEvents = feedFactory.create(drivers, friendships, races, leagues); const feedEvents = feedFactory.create(drivers, friendships, races, leagues);
@@ -91,6 +96,7 @@ class RacingSeedFactory {
raceRegistrations, raceRegistrations,
teams, teams,
teamMemberships, teamMemberships,
tracks,
friendships, friendships,
feedEvents, feedEvents,
}; };

View File

@@ -1,92 +1,72 @@
import { Driver } from '@core/racing/domain/entities/Driver'; import { Driver } from '@core/racing/domain/entities/Driver';
import { League } from '@core/racing/domain/entities/League';
import { Team } from '@core/racing/domain/entities/Team'; import { Team } from '@core/racing/domain/entities/Team';
import type { TeamMembership } from '@core/racing/domain/types/TeamMembership'; import type { TeamMembership } from '@core/racing/domain/types/TeamMembership';
import { faker } from '@faker-js/faker';
export class RacingTeamFactory { export class RacingTeamFactory {
constructor(private readonly baseDate: Date) {} constructor(
private readonly baseDate: Date,
private readonly drivers: Driver[],
private readonly leagues: League[],
) {}
createTeams(): Team[] { createTeams(): Team[] {
return [ const teamCount = 15;
Team.create({
id: 'team-1', return Array.from({ length: teamCount }, (_, idx) => {
name: 'Apex Racing', const i = idx + 1;
tag: 'APEX', const owner = faker.helpers.arrayElement(this.drivers);
description: 'Demo team focused on clean racing.', const teamLeagues = faker.helpers.arrayElements(
ownerId: 'driver-1', this.leagues.map(l => l.id.toString()),
leagues: ['league-5'], { min: 0, max: 3 }
createdAt: this.addDays(this.baseDate, -100), );
}),
Team.create({ return Team.create({
id: 'team-2', id: `team-${i}`,
name: 'Night Owls', name: faker.company.name() + ' Racing',
tag: 'NITE', tag: faker.string.alpha({ length: 4, casing: 'upper' }),
description: 'Late-night grinders and endurance lovers.', description: faker.lorem.sentences(2),
ownerId: 'driver-2', ownerId: owner.id,
leagues: ['league-4'], leagues: teamLeagues,
createdAt: this.addDays(this.baseDate, -90), createdAt: faker.date.past({ years: 2, refDate: this.baseDate }),
}), });
Team.create({ });
id: 'team-3',
name: 'Club Legends',
tag: 'CLUB',
description: 'A casual team for ladder climbing.',
ownerId: 'driver-3',
leagues: ['league-3'],
createdAt: this.addDays(this.baseDate, -80),
}),
];
} }
createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] { createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] {
const memberships: TeamMembership[] = []; const memberships: TeamMembership[] = [];
const usedDrivers = new Set<string>();
const team1 = teams.find((t) => t.id === 'team-1'); teams.forEach((team) => {
const team2 = teams.find((t) => t.id === 'team-2'); const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString());
const team3 = teams.find((t) => t.id === 'team-3'); const memberCount = faker.number.int({ min: 1, max: 8 });
const members = faker.helpers.arrayElements(availableDrivers, memberCount);
if (team1) { // Add owner
const members = drivers.slice(0, 6); memberships.push({
members.forEach((d, idx) => { teamId: team.id.toString(),
memberships.push({ driverId: team.ownerId.toString(),
teamId: team1.id, role: 'owner',
driverId: d.id, status: 'active',
role: d.id === team1.ownerId.toString() ? 'owner' : idx === 1 ? 'manager' : 'driver', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
status: 'active',
joinedAt: this.addDays(this.baseDate, -50),
});
}); });
} usedDrivers.add(team.ownerId.toString());
if (team2) { // Add members
const members = drivers.slice(6, 12); members.forEach((driver) => {
members.forEach((d) => {
memberships.push({ memberships.push({
teamId: team2.id, teamId: team.id.toString(),
driverId: d.id, driverId: driver.id.toString(),
role: d.id === team2.ownerId.toString() ? 'owner' : 'driver', role: faker.helpers.arrayElement(['driver', 'manager']),
status: 'active', status: 'active',
joinedAt: this.addDays(this.baseDate, -45), joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }),
}); });
usedDrivers.add(driver.id.toString());
}); });
} });
if (team3) {
const members = drivers.slice(12, 18);
members.forEach((d) => {
memberships.push({
teamId: team3.id,
driverId: d.id,
role: d.id === team3.ownerId.toString() ? 'owner' : 'driver',
status: 'active',
joinedAt: this.addDays(this.baseDate, -40),
});
});
}
return memberships; return memberships;
} }
private addDays(date: Date, days: number): Date {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
}
} }

View File

@@ -0,0 +1,92 @@
import { Track } from '@core/racing/domain/entities/Track';
export class RacingTrackFactory {
create(): Track[] {
return [
Track.create({
id: 'track-spa',
name: 'Spa-Francorchamps',
shortName: 'SPA',
country: 'Belgium',
category: 'road',
difficulty: 'advanced',
lengthKm: 7.004,
turns: 19,
imageUrl: '/images/tracks/spa.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-monza',
name: 'Autodromo Nazionale Monza',
shortName: 'MON',
country: 'Italy',
category: 'road',
difficulty: 'intermediate',
lengthKm: 5.793,
turns: 11,
imageUrl: '/images/tracks/monza.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-nurburgring',
name: 'Nürburgring Grand Prix',
shortName: 'NUR',
country: 'Germany',
category: 'road',
difficulty: 'advanced',
lengthKm: 5.148,
turns: 15,
imageUrl: '/images/tracks/nurburgring.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-silverstone',
name: 'Silverstone Circuit',
shortName: 'SIL',
country: 'United Kingdom',
category: 'road',
difficulty: 'intermediate',
lengthKm: 5.891,
turns: 18,
imageUrl: '/images/tracks/silverstone.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-suzuka',
name: 'Suzuka International Racing Course',
shortName: 'SUZ',
country: 'Japan',
category: 'road',
difficulty: 'expert',
lengthKm: 5.807,
turns: 18,
imageUrl: '/images/tracks/suzuka.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-daytona',
name: 'Daytona International Speedway',
shortName: 'DAY',
country: 'United States',
category: 'oval',
difficulty: 'intermediate',
lengthKm: 4.023,
turns: 4,
imageUrl: '/images/tracks/daytona.jpg',
gameId: 'iracing',
}),
Track.create({
id: 'track-laguna',
name: 'WeatherTech Raceway Laguna Seca',
shortName: 'LAG',
country: 'United States',
category: 'road',
difficulty: 'advanced',
lengthKm: 3.602,
turns: 11,
imageUrl: '/images/tracks/laguna.jpg',
gameId: 'iracing',
}),
];
}
}

View File

@@ -35,6 +35,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
useCase = new CompleteDriverOnboardingUseCase( useCase = new CompleteDriverOnboardingUseCase(
driverRepository as unknown as IDriverRepository, driverRepository as unknown as IDriverRepository,
logger, logger,
output,
); );
}); });
@@ -65,7 +66,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
const result = await useCase.execute(command); const result = await useCase.execute(command);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ driver: createdDriver }); expect(output.present).toHaveBeenCalledWith({ driver: createdDriver });
expect(driverRepository.findById).toHaveBeenCalledWith('user-1'); expect(driverRepository.findById).toHaveBeenCalledWith('user-1');
expect(driverRepository.create).toHaveBeenCalledWith( expect(driverRepository.create).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
@@ -143,7 +144,7 @@ describe('CompleteDriverOnboardingUseCase', () => {
const result = await useCase.execute(command); const result = await useCase.execute(command);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ driver: createdDriver }); expect(output.present).toHaveBeenCalledWith({ driver: createdDriver });
expect(driverRepository.create).toHaveBeenCalledWith( expect(driverRepository.create).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
id: 'user-1', id: 'user-1',

View File

@@ -7,6 +7,8 @@ import type { IDriverRepository } from '../../domain/repositories/IDriverReposit
import type { IRankingService } from '../../domain/services/IRankingService'; import type { IRankingService } from '../../domain/services/IRankingService';
import type { IDriverStatsService } from '../../domain/services/IDriverStatsService'; import type { IDriverStatsService } from '../../domain/services/IDriverStatsService';
import type { Logger } from '@core/shared/application'; import type { Logger } from '@core/shared/application';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { GetDriversLeaderboardResult } from './GetDriversLeaderboardUseCase';
describe('GetDriversLeaderboardUseCase', () => { describe('GetDriversLeaderboardUseCase', () => {
const mockDriverFindAll = vi.fn(); const mockDriverFindAll = vi.fn();
@@ -39,6 +41,10 @@ describe('GetDriversLeaderboardUseCase', () => {
error: vi.fn(), error: vi.fn(),
}; };
const mockOutput: UseCaseOutputPort<GetDriversLeaderboardResult> = {
present: vi.fn(),
};
it('should return drivers leaderboard data', async () => { it('should return drivers leaderboard data', async () => {
const useCase = new GetDriversLeaderboardUseCase( const useCase = new GetDriversLeaderboardUseCase(
mockDriverRepo, mockDriverRepo,
@@ -46,6 +52,7 @@ describe('GetDriversLeaderboardUseCase', () => {
mockDriverStatsService, mockDriverStatsService,
mockGetDriverAvatar, mockGetDriverAvatar,
mockLogger, mockLogger,
mockOutput,
); );
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } }; const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
@@ -75,9 +82,8 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input); const result = await useCase.execute(input);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
const presented = result.unwrap();
expect(presented).toEqual({ expect(mockOutput.present).toHaveBeenCalledWith({
items: [ items: [
expect.objectContaining({ expect.objectContaining({
driver: driver1, driver: driver1,
@@ -115,6 +121,7 @@ describe('GetDriversLeaderboardUseCase', () => {
mockDriverStatsService, mockDriverStatsService,
mockGetDriverAvatar, mockGetDriverAvatar,
mockLogger, mockLogger,
mockOutput,
); );
mockDriverFindAll.mockResolvedValue([]); mockDriverFindAll.mockResolvedValue([]);
@@ -125,9 +132,8 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input); const result = await useCase.execute(input);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
const presented = result.unwrap();
expect(presented).toEqual({ expect(mockOutput.present).toHaveBeenCalledWith({
items: [], items: [],
totalRaces: 0, totalRaces: 0,
totalWins: 0, totalWins: 0,
@@ -142,6 +148,7 @@ describe('GetDriversLeaderboardUseCase', () => {
mockDriverStatsService, mockDriverStatsService,
mockGetDriverAvatar, mockGetDriverAvatar,
mockLogger, mockLogger,
mockOutput,
); );
const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } }; const driver1 = { id: 'driver1', name: { value: 'Driver One' }, country: { value: 'US' } };
@@ -157,9 +164,8 @@ describe('GetDriversLeaderboardUseCase', () => {
const result = await useCase.execute(input); const result = await useCase.execute(input);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
const presented = result.unwrap();
expect(presented).toEqual({ expect(mockOutput.present).toHaveBeenCalledWith({
items: [ items: [
expect.objectContaining({ expect.objectContaining({
driver: driver1, driver: driver1,
@@ -186,6 +192,7 @@ describe('GetDriversLeaderboardUseCase', () => {
mockDriverStatsService, mockDriverStatsService,
mockGetDriverAvatar, mockGetDriverAvatar,
mockLogger, mockLogger,
mockOutput,
); );
const error = new Error('Repository error'); const error = new Error('Repository error');

View File

@@ -3,21 +3,27 @@ import {
GetTotalDriversUseCase, GetTotalDriversUseCase,
GetTotalDriversInput, GetTotalDriversInput,
GetTotalDriversErrorCode, GetTotalDriversErrorCode,
GetTotalDriversResult,
} from './GetTotalDriversUseCase'; } from './GetTotalDriversUseCase';
import { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
describe('GetTotalDriversUseCase', () => { describe('GetTotalDriversUseCase', () => {
let useCase: GetTotalDriversUseCase; let useCase: GetTotalDriversUseCase;
let driverRepository: { let driverRepository: {
findAll: Mock; findAll: Mock;
}; };
let output: UseCaseOutputPort<GetTotalDriversResult>;
beforeEach(() => { beforeEach(() => {
driverRepository = { driverRepository = {
findAll: vi.fn(), findAll: vi.fn(),
}; };
output = {
present: vi.fn(),
};
useCase = new GetTotalDriversUseCase(driverRepository as unknown as IDriverRepository); useCase = new GetTotalDriversUseCase(driverRepository as unknown as IDriverRepository, output);
}); });
it('should return total number of drivers', async () => { it('should return total number of drivers', async () => {
@@ -30,7 +36,7 @@ describe('GetTotalDriversUseCase', () => {
const result = await useCase.execute(input); const result = await useCase.execute(input);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ totalDrivers: 2 }); expect(output.present).toHaveBeenCalledWith({ totalDrivers: 2 });
}); });
it('should return error on repository failure', async () => { it('should return error on repository failure', async () => {

View File

@@ -3,10 +3,12 @@ import {
IsDriverRegisteredForRaceUseCase, IsDriverRegisteredForRaceUseCase,
type IsDriverRegisteredForRaceInput, type IsDriverRegisteredForRaceInput,
type IsDriverRegisteredForRaceErrorCode, type IsDriverRegisteredForRaceErrorCode,
type IsDriverRegisteredForRaceResult,
} from './IsDriverRegisteredForRaceUseCase'; } from './IsDriverRegisteredForRaceUseCase';
import { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { Logger } from '@core/shared/application'; import type { Logger } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
describe('IsDriverRegisteredForRaceUseCase', () => { describe('IsDriverRegisteredForRaceUseCase', () => {
let useCase: IsDriverRegisteredForRaceUseCase; let useCase: IsDriverRegisteredForRaceUseCase;
@@ -19,6 +21,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
warn: Mock; warn: Mock;
error: Mock; error: Mock;
}; };
let output: UseCaseOutputPort<IsDriverRegisteredForRaceResult>;
beforeEach(() => { beforeEach(() => {
registrationRepository = { registrationRepository = {
isRegistered: vi.fn(), isRegistered: vi.fn(),
@@ -29,9 +32,13 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
warn: vi.fn(), warn: vi.fn(),
error: vi.fn(), error: vi.fn(),
}; };
output = {
present: vi.fn(),
};
useCase = new IsDriverRegisteredForRaceUseCase( useCase = new IsDriverRegisteredForRaceUseCase(
registrationRepository as unknown as IRaceRegistrationRepository, registrationRepository as unknown as IRaceRegistrationRepository,
logger as unknown as Logger, logger as unknown as Logger,
output,
); );
}); });
@@ -43,7 +50,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
const result = await useCase.execute(params); const result = await useCase.execute(params);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ expect(output.present).toHaveBeenCalledWith({
raceId: params.raceId, raceId: params.raceId,
driverId: params.driverId, driverId: params.driverId,
isRegistered: true, isRegistered: true,
@@ -58,7 +65,7 @@ describe('IsDriverRegisteredForRaceUseCase', () => {
const result = await useCase.execute(params); const result = await useCase.execute(params);
expect(result.isOk()).toBe(true); expect(result.isOk()).toBe(true);
expect(result.unwrap()).toEqual({ expect(output.present).toHaveBeenCalledWith({
raceId: params.raceId, raceId: params.raceId,
driverId: params.driverId, driverId: params.driverId,
isRegistered: false, isRegistered: false,

11
package-lock.json generated
View File

@@ -50,6 +50,7 @@
"eslint-import-resolver-typescript": "2.7.1", "eslint-import-resolver-typescript": "2.7.1",
"eslint-plugin-boundaries": "^5.3.1", "eslint-plugin-boundaries": "^5.3.1",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"faker": "^6.6.6",
"glob": "^13.0.0", "glob": "^13.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
@@ -87,7 +88,8 @@
}, },
"devDependencies": { "devDependencies": {
"@nestjs/testing": "^10.4.20", "@nestjs/testing": "^10.4.20",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0",
"tsconfig-paths": "^3.15.0"
} }
}, },
"apps/api/node_modules/reflect-metadata": { "apps/api/node_modules/reflect-metadata": {
@@ -8126,6 +8128,13 @@
"@types/yauzl": "^2.9.1" "@types/yauzl": "^2.9.1"
} }
}, },
"node_modules/faker": {
"version": "6.6.6",
"resolved": "https://registry.npmjs.org/faker/-/faker-6.6.6.tgz",
"integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",

View File

@@ -35,9 +35,10 @@
"commander": "^11.0.0", "commander": "^11.0.0",
"electron": "^39.2.7", "electron": "^39.2.7",
"eslint": "^8.0.0", "eslint": "^8.0.0",
"eslint-import-resolver-typescript": "2.7.1",
"eslint-plugin-boundaries": "^5.3.1", "eslint-plugin-boundaries": "^5.3.1",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-import-resolver-typescript": "2.7.1", "faker": "^6.6.6",
"glob": "^13.0.0", "glob": "^13.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",