1161 lines
39 KiB
TypeScript
1161 lines
39 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
import {
|
|
DashboardOverviewUseCase,
|
|
type DashboardOverviewInput,
|
|
type DashboardOverviewResult,
|
|
} from '@core/racing/application/use-cases/DashboardOverviewUseCase';
|
|
import { Driver } from '@core/racing/domain/entities/Driver';
|
|
import { Race } from '@core/racing/domain/entities/Race';
|
|
import { League } from '@core/racing/domain/entities/League';
|
|
import { Standing } from '@core/racing/domain/entities/Standing';
|
|
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
|
import { Result as RaceResult } from '@core/racing/domain/entities/Result';
|
|
|
|
import type { FeedItem } from '@core/social/domain/types/FeedItem';
|
|
import { Result as UseCaseResult } from '@core/shared/application/Result';
|
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
|
|
|
describe('DashboardOverviewUseCase', () => {
|
|
it('partitions upcoming races into myUpcomingRaces and otherUpcomingRaces and selects nextRace from myUpcomingRaces', async () => {
|
|
const output: UseCaseOutputPort<DashboardOverviewResult> = {
|
|
present: vi.fn(),
|
|
};
|
|
|
|
const driverId = 'driver-1';
|
|
|
|
const driver = Driver.create({ id: driverId, iracingId: '12345', name: 'Alice Racer', country: 'US' });
|
|
|
|
const leagues = [
|
|
League.create({ id: 'league-1', name: 'Alpha League', description: 'First league', ownerId: 'owner-1' }),
|
|
League.create({ id: 'league-2', name: 'Beta League', description: 'Second league', ownerId: 'owner-2' }),
|
|
];
|
|
|
|
const now = Date.now();
|
|
|
|
const races = [
|
|
Race.create({
|
|
id: 'race-1',
|
|
leagueId: 'league-1',
|
|
track: 'Monza',
|
|
car: 'GT3',
|
|
scheduledAt: new Date(now + 60 * 60 * 1000),
|
|
status: 'scheduled',
|
|
}),
|
|
Race.create({
|
|
id: 'race-2',
|
|
leagueId: 'league-1',
|
|
track: 'Spa',
|
|
car: 'GT3',
|
|
scheduledAt: new Date(now + 2 * 60 * 60 * 1000),
|
|
status: 'scheduled',
|
|
}),
|
|
Race.create({
|
|
id: 'race-3',
|
|
leagueId: 'league-2',
|
|
track: 'Silverstone',
|
|
car: 'GT4',
|
|
scheduledAt: new Date(now + 3 * 60 * 60 * 1000),
|
|
status: 'scheduled',
|
|
}),
|
|
Race.create({
|
|
id: 'race-4',
|
|
leagueId: 'league-2',
|
|
track: 'Imola',
|
|
car: 'GT4',
|
|
scheduledAt: new Date(now + 4 * 60 * 60 * 1000),
|
|
status: 'scheduled',
|
|
}),
|
|
];
|
|
|
|
const results: RaceResult[] = [];
|
|
|
|
const memberships = [
|
|
LeagueMembership.create({
|
|
leagueId: 'league-1',
|
|
driverId,
|
|
role: 'member',
|
|
status: 'active',
|
|
}),
|
|
LeagueMembership.create({
|
|
leagueId: 'league-2',
|
|
driverId,
|
|
role: 'member',
|
|
status: 'active',
|
|
}),
|
|
];
|
|
|
|
const registeredRaceIds = new Set<string>(['race-1', 'race-3']);
|
|
|
|
const feedItems: FeedItem[] = [];
|
|
const friends: Driver[] = [];
|
|
|
|
const driverRepository = {
|
|
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
|
findByIRacingId: async (): Promise<Driver | null> => null,
|
|
findAll: async (): Promise<Driver[]> => [],
|
|
create: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByIRacingId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const raceRepository = {
|
|
findById: async (): Promise<Race | null> => null,
|
|
findAll: async (): Promise<Race[]> => races,
|
|
findByLeagueId: async (): Promise<Race[]> => [],
|
|
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
|
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
|
findByStatus: async (): Promise<Race[]> => [],
|
|
findByDateRange: async (): Promise<Race[]> => [],
|
|
create: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const resultRepository = {
|
|
findById: async (): Promise<RaceResult | null> => null,
|
|
findAll: async (): Promise<RaceResult[]> => results,
|
|
findByRaceId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<RaceResult[]> => [],
|
|
create: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
createMany: async (): Promise<RaceResult[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByRaceId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByRaceId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const leagueRepository = {
|
|
findById: async (): Promise<League | null> => null,
|
|
findAll: async (): Promise<League[]> => leagues,
|
|
findByOwnerId: async (): Promise<League[]> => [],
|
|
create: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
searchByName: async (): Promise<League[]> => [],
|
|
};
|
|
|
|
const standingRepository = {
|
|
findByLeagueId: async (): Promise<Standing[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
|
findAll: async (): Promise<Standing[]> => [],
|
|
save: async (): Promise<Standing> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveMany: async (): Promise<Standing[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByLeagueId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
recalculate: async (): Promise<Standing[]> => [],
|
|
};
|
|
|
|
const leagueMembershipRepository = {
|
|
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
|
return (
|
|
memberships.find(
|
|
m => m.leagueId === leagueId && m.driverId === driverIdParam,
|
|
) ?? null
|
|
);
|
|
},
|
|
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
|
getJoinRequests: async (): Promise<any[]> => [],
|
|
saveMembership: async (): Promise<LeagueMembership> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeMembership: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveJoinRequest: async (): Promise<any> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeJoinRequest: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const raceRegistrationRepository = {
|
|
isRegistered: async (raceId: string, driverIdParam: string): Promise<boolean> => {
|
|
if (driverIdParam !== driverId) return false;
|
|
return registeredRaceIds.has(raceId);
|
|
},
|
|
getRegisteredDrivers: async (): Promise<string[]> => [],
|
|
getRegistrationCount: async (): Promise<number> => 0,
|
|
register: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
withdraw: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
getDriverRegistrations: async (): Promise<string[]> => [],
|
|
clearRaceRegistrations: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const feedRepository = {
|
|
getFeedForDriver: async (): Promise<FeedItem[]> => feedItems,
|
|
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
|
};
|
|
|
|
const socialRepository = {
|
|
getFriends: async (): Promise<Driver[]> => friends,
|
|
getFriendIds: async (): Promise<string[]> => [],
|
|
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
|
};
|
|
|
|
const getDriverAvatar = async (id: string): Promise<string> => `avatar-${id}`;
|
|
|
|
const getDriverStats = (id: string) =>
|
|
id === driverId
|
|
? {
|
|
rating: 1600,
|
|
wins: 5,
|
|
podiums: 12,
|
|
totalRaces: 40,
|
|
overallRank: 42,
|
|
consistency: 88,
|
|
}
|
|
: null;
|
|
|
|
const useCase = new DashboardOverviewUseCase(
|
|
driverRepository,
|
|
raceRepository,
|
|
resultRepository,
|
|
leagueRepository,
|
|
standingRepository,
|
|
leagueMembershipRepository,
|
|
raceRegistrationRepository,
|
|
feedRepository,
|
|
socialRepository,
|
|
getDriverAvatar,
|
|
getDriverStats,
|
|
output,
|
|
);
|
|
|
|
const input: DashboardOverviewInput = { driverId };
|
|
|
|
const result: UseCaseResult<
|
|
void,
|
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
|
> = await useCase.execute(input);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBeUndefined();
|
|
|
|
expect(output.present).toHaveBeenCalledTimes(1);
|
|
const vm = (output.present as any).mock.calls[0][0] as DashboardOverviewResult;
|
|
|
|
expect(vm.myUpcomingRaces.map(r => r.race.id)).toEqual(['race-1', 'race-3']);
|
|
|
|
expect(vm.otherUpcomingRaces.map(r => r.race.id)).toEqual(['race-2', 'race-4']);
|
|
|
|
expect(vm.nextRace).not.toBeNull();
|
|
expect(vm.nextRace!.race.id).toBe('race-1');
|
|
});
|
|
|
|
it('builds recentResults sorted by date descending and leagueStandingsSummaries from standings', async () => {
|
|
const output: UseCaseOutputPort<DashboardOverviewResult> = {
|
|
present: vi.fn(),
|
|
};
|
|
|
|
const driverId = 'driver-2';
|
|
|
|
const driver = Driver.create({ id: driverId, iracingId: '67890', name: 'Result Driver', country: 'DE' });
|
|
|
|
const leagues = [
|
|
League.create({ id: 'league-A', name: 'Results League A', description: 'League A', ownerId: 'owner-A' }),
|
|
League.create({ id: 'league-B', name: 'Results League B', description: 'League B', ownerId: 'owner-B' }),
|
|
];
|
|
|
|
const raceOld = Race.create({
|
|
id: 'race-old',
|
|
leagueId: 'league-A',
|
|
track: 'Old Circuit',
|
|
car: 'GT3',
|
|
scheduledAt: new Date('2024-01-01T10:00:00Z'),
|
|
status: 'completed',
|
|
});
|
|
|
|
const raceNew = Race.create({
|
|
id: 'race-new',
|
|
leagueId: 'league-B',
|
|
track: 'New Circuit',
|
|
car: 'GT4',
|
|
scheduledAt: new Date('2024-02-01T10:00:00Z'),
|
|
status: 'completed',
|
|
});
|
|
|
|
const races = [raceOld, raceNew];
|
|
|
|
const results: RaceResult[] = [
|
|
RaceResult.create({
|
|
id: 'result-old',
|
|
raceId: raceOld.id,
|
|
driverId,
|
|
position: 5,
|
|
fastestLap: 120,
|
|
incidents: 3,
|
|
startPosition: 5,
|
|
}),
|
|
RaceResult.create({
|
|
id: 'result-new',
|
|
raceId: raceNew.id,
|
|
driverId,
|
|
position: 2,
|
|
fastestLap: 115,
|
|
incidents: 1,
|
|
startPosition: 2,
|
|
}),
|
|
];
|
|
|
|
const memberships = [
|
|
LeagueMembership.create({
|
|
leagueId: 'league-A',
|
|
driverId,
|
|
role: 'member',
|
|
status: 'active',
|
|
}),
|
|
LeagueMembership.create({
|
|
leagueId: 'league-B',
|
|
driverId,
|
|
role: 'member',
|
|
status: 'active',
|
|
}),
|
|
];
|
|
|
|
const standingsByLeague = new Map<string, Standing[]>();
|
|
standingsByLeague.set('league-A', [
|
|
Standing.create({ leagueId: 'league-A', driverId, position: 3, points: 50 }),
|
|
Standing.create({ leagueId: 'league-A', driverId: 'other-1', position: 1, points: 80 }),
|
|
]);
|
|
standingsByLeague.set('league-B', [
|
|
Standing.create({ leagueId: 'league-B', driverId, position: 1, points: 100 }),
|
|
Standing.create({ leagueId: 'league-B', driverId: 'other-2', position: 2, points: 90 }),
|
|
]);
|
|
|
|
const driverRepository = {
|
|
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
|
findByIRacingId: async (): Promise<Driver | null> => null,
|
|
findAll: async (): Promise<Driver[]> => [],
|
|
create: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByIRacingId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const raceRepository = {
|
|
findById: async (): Promise<Race | null> => null,
|
|
findAll: async (): Promise<Race[]> => races,
|
|
findByLeagueId: async (): Promise<Race[]> => [],
|
|
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
|
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
|
findByStatus: async (): Promise<Race[]> => [],
|
|
findByDateRange: async (): Promise<Race[]> => [],
|
|
create: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const resultRepository = {
|
|
findById: async (): Promise<RaceResult | null> => null,
|
|
findAll: async (): Promise<RaceResult[]> => results,
|
|
findByRaceId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<RaceResult[]> => [],
|
|
create: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
createMany: async (): Promise<RaceResult[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByRaceId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByRaceId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const leagueRepository = {
|
|
findById: async (): Promise<League | null> => null,
|
|
findAll: async (): Promise<League[]> => leagues,
|
|
findByOwnerId: async (): Promise<League[]> => [],
|
|
create: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
searchByName: async (): Promise<League[]> => [],
|
|
};
|
|
|
|
const standingRepository = {
|
|
findByLeagueId: async (leagueId: string): Promise<Standing[]> =>
|
|
standingsByLeague.get(leagueId) ?? [],
|
|
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
|
findAll: async (): Promise<Standing[]> => [],
|
|
save: async (): Promise<Standing> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveMany: async (): Promise<Standing[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByLeagueId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
recalculate: async (): Promise<Standing[]> => [],
|
|
};
|
|
|
|
const leagueMembershipRepository = {
|
|
getMembership: async (leagueId: string, driverIdParam: string): Promise<LeagueMembership | null> => {
|
|
return (
|
|
memberships.find(
|
|
m => m.leagueId === leagueId && m.driverId === driverIdParam,
|
|
) ?? null
|
|
);
|
|
},
|
|
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
|
getJoinRequests: async (): Promise<any[]> => [],
|
|
saveMembership: async (): Promise<LeagueMembership> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeMembership: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveJoinRequest: async (): Promise<any> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeJoinRequest: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const raceRegistrationRepository = {
|
|
isRegistered: async (): Promise<boolean> => false,
|
|
getRegisteredDrivers: async (): Promise<string[]> => [],
|
|
getRegistrationCount: async (): Promise<number> => 0,
|
|
register: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
withdraw: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
getDriverRegistrations: async (): Promise<string[]> => [],
|
|
clearRaceRegistrations: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const feedRepository = {
|
|
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
|
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
|
};
|
|
|
|
const socialRepository = {
|
|
getFriends: async (): Promise<Driver[]> => [],
|
|
getFriendIds: async (): Promise<string[]> => [],
|
|
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
|
};
|
|
|
|
const getDriverAvatar = async (id: string): Promise<string> => `avatar-${id}`;
|
|
|
|
const getDriverStats = (id: string) =>
|
|
id === driverId
|
|
? {
|
|
rating: 1800,
|
|
wins: 3,
|
|
podiums: 7,
|
|
totalRaces: 20,
|
|
overallRank: 10,
|
|
consistency: 92,
|
|
}
|
|
: null;
|
|
|
|
const useCase = new DashboardOverviewUseCase(
|
|
driverRepository,
|
|
raceRepository,
|
|
resultRepository,
|
|
leagueRepository,
|
|
standingRepository,
|
|
leagueMembershipRepository,
|
|
raceRegistrationRepository,
|
|
feedRepository,
|
|
socialRepository,
|
|
getDriverAvatar,
|
|
getDriverStats,
|
|
output,
|
|
);
|
|
|
|
const input: DashboardOverviewInput = { driverId };
|
|
|
|
const result: UseCaseResult<
|
|
void,
|
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
|
> = await useCase.execute(input);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBeUndefined();
|
|
|
|
expect(output.present).toHaveBeenCalledTimes(1);
|
|
const vm = (output.present as any).mock.calls[0][0] as DashboardOverviewResult;
|
|
|
|
expect(vm.recentResults.length).toBe(2);
|
|
expect(vm.recentResults[0]!.race.id).toBe('race-new');
|
|
expect(vm.recentResults[1]!.race.id).toBe('race-old');
|
|
|
|
const summariesByLeague = new Map(
|
|
vm.leagueStandingsSummaries.map(s => [s.league.id, s]),
|
|
);
|
|
|
|
const summaryA = summariesByLeague.get('league-A');
|
|
const summaryB = summariesByLeague.get('league-B');
|
|
|
|
expect(summaryA).toBeDefined();
|
|
expect(summaryA!.standing?.position).toBe(3);
|
|
expect(summaryA!.standing?.points).toBe(50);
|
|
expect(summaryA!.totalDrivers).toBe(2);
|
|
|
|
expect(summaryB).toBeDefined();
|
|
expect(summaryB!.standing?.position).toBe(1);
|
|
expect(summaryB!.standing?.points).toBe(100);
|
|
expect(summaryB!.totalDrivers).toBe(2);
|
|
});
|
|
|
|
it('returns empty collections and safe defaults when driver has no races or standings', async () => {
|
|
const output: UseCaseOutputPort<DashboardOverviewResult> = {
|
|
present: vi.fn(),
|
|
};
|
|
|
|
const driverId = 'driver-empty';
|
|
|
|
const driver = Driver.create({ id: driverId, iracingId: '11111', name: 'New Racer', country: 'FR' });
|
|
|
|
const driverRepository = {
|
|
findById: async (id: string): Promise<Driver | null> => (id === driver.id ? driver : null),
|
|
findByIRacingId: async (): Promise<Driver | null> => null,
|
|
findAll: async (): Promise<Driver[]> => [],
|
|
create: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByIRacingId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const raceRepository = {
|
|
findById: async (): Promise<Race | null> => null,
|
|
findAll: async (): Promise<Race[]> => [],
|
|
findByLeagueId: async (): Promise<Race[]> => [],
|
|
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
|
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
|
findByStatus: async (): Promise<Race[]> => [],
|
|
findByDateRange: async (): Promise<Race[]> => [],
|
|
create: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const resultRepository = {
|
|
findById: async (): Promise<RaceResult | null> => null,
|
|
findAll: async (): Promise<RaceResult[]> => [],
|
|
findByRaceId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<RaceResult[]> => [],
|
|
create: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
createMany: async (): Promise<RaceResult[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByRaceId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByRaceId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const leagueRepository = {
|
|
findById: async (): Promise<League | null> => null,
|
|
findAll: async (): Promise<League[]> => [],
|
|
findByOwnerId: async (): Promise<League[]> => [],
|
|
create: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
searchByName: async (): Promise<League[]> => [],
|
|
};
|
|
|
|
const standingRepository = {
|
|
findByLeagueId: async (): Promise<Standing[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
|
findAll: async (): Promise<Standing[]> => [],
|
|
save: async (): Promise<Standing> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveMany: async (): Promise<Standing[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByLeagueId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
recalculate: async (): Promise<Standing[]> => [],
|
|
};
|
|
|
|
const leagueMembershipRepository = {
|
|
getMembership: async (): Promise<LeagueMembership | null> => null,
|
|
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
|
getJoinRequests: async (): Promise<any[]> => [],
|
|
saveMembership: async (): Promise<LeagueMembership> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeMembership: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveJoinRequest: async (): Promise<any> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeJoinRequest: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const raceRegistrationRepository = {
|
|
isRegistered: async (): Promise<boolean> => false,
|
|
getRegisteredDrivers: async (): Promise<string[]> => [],
|
|
getRegistrationCount: async (): Promise<number> => 0,
|
|
register: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
withdraw: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
getDriverRegistrations: async (): Promise<string[]> => [],
|
|
clearRaceRegistrations: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const feedRepository = {
|
|
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
|
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
|
};
|
|
|
|
const socialRepository = {
|
|
getFriends: async (): Promise<Driver[]> => [],
|
|
getFriendIds: async (): Promise<string[]> => [],
|
|
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
|
};
|
|
|
|
const getDriverAvatar = async (id: string): Promise<string> => `avatar-${id}`;
|
|
|
|
const getDriverStats = () => null;
|
|
|
|
const useCase = new DashboardOverviewUseCase(
|
|
driverRepository,
|
|
raceRepository,
|
|
resultRepository,
|
|
leagueRepository,
|
|
standingRepository,
|
|
leagueMembershipRepository,
|
|
raceRegistrationRepository,
|
|
feedRepository,
|
|
socialRepository,
|
|
getDriverAvatar,
|
|
getDriverStats,
|
|
output,
|
|
);
|
|
|
|
const input: DashboardOverviewInput = { driverId };
|
|
|
|
const result: UseCaseResult<
|
|
void,
|
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
|
> = await useCase.execute(input);
|
|
|
|
expect(result.isOk()).toBe(true);
|
|
expect(result.unwrap()).toBeUndefined();
|
|
|
|
expect(output.present).toHaveBeenCalledTimes(1);
|
|
const vm = (output.present as any).mock.calls[0][0] as DashboardOverviewResult;
|
|
|
|
expect(vm.myUpcomingRaces).toEqual([]);
|
|
expect(vm.otherUpcomingRaces).toEqual([]);
|
|
expect(vm.nextRace).toBeNull();
|
|
expect(vm.recentResults).toEqual([]);
|
|
expect(vm.leagueStandingsSummaries).toEqual([]);
|
|
expect(vm.friends).toEqual([]);
|
|
expect(vm.feedSummary.notificationCount).toBe(0);
|
|
expect(vm.feedSummary.items).toEqual([]);
|
|
});
|
|
|
|
it('returns DRIVER_NOT_FOUND error and does not present when driver is missing', async () => {
|
|
const output: UseCaseOutputPort<DashboardOverviewResult> = {
|
|
present: vi.fn(),
|
|
};
|
|
|
|
const driverId = 'missing-driver';
|
|
|
|
const driverRepository = {
|
|
findById: async (): Promise<Driver | null> => null,
|
|
findByIRacingId: async (): Promise<Driver | null> => null,
|
|
findAll: async (): Promise<Driver[]> => [],
|
|
create: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByIRacingId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const raceRepository = {
|
|
findById: async (): Promise<Race | null> => null,
|
|
findAll: async (): Promise<Race[]> => [],
|
|
findByLeagueId: async (): Promise<Race[]> => [],
|
|
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
|
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
|
findByStatus: async (): Promise<Race[]> => [],
|
|
findByDateRange: async (): Promise<Race[]> => [],
|
|
create: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const resultRepository = {
|
|
findById: async (): Promise<RaceResult | null> => null,
|
|
findAll: async (): Promise<RaceResult[]> => [],
|
|
findByRaceId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<RaceResult[]> => [],
|
|
create: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
createMany: async (): Promise<RaceResult[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByRaceId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByRaceId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const leagueRepository = {
|
|
findById: async (): Promise<League | null> => null,
|
|
findAll: async (): Promise<League[]> => [],
|
|
findByOwnerId: async (): Promise<League[]> => [],
|
|
create: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
searchByName: async (): Promise<League[]> => [],
|
|
};
|
|
|
|
const standingRepository = {
|
|
findByLeagueId: async (): Promise<Standing[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
|
findAll: async (): Promise<Standing[]> => [],
|
|
save: async (): Promise<Standing> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveMany: async (): Promise<Standing[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByLeagueId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
recalculate: async (): Promise<Standing[]> => [],
|
|
};
|
|
|
|
const leagueMembershipRepository = {
|
|
getMembership: async (): Promise<LeagueMembership | null> => null,
|
|
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
|
getJoinRequests: async (): Promise<any[]> => [],
|
|
saveMembership: async (): Promise<LeagueMembership> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeMembership: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveJoinRequest: async (): Promise<any> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeJoinRequest: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const raceRegistrationRepository = {
|
|
isRegistered: async (): Promise<boolean> => false,
|
|
getRegisteredDrivers: async (): Promise<string[]> => [],
|
|
getRegistrationCount: async (): Promise<number> => 0,
|
|
register: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
withdraw: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
getDriverRegistrations: async (): Promise<string[]> => [],
|
|
clearRaceRegistrations: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const feedRepository = {
|
|
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
|
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
|
};
|
|
|
|
const socialRepository = {
|
|
getFriends: async (): Promise<Driver[]> => [],
|
|
getFriendIds: async (): Promise<string[]> => [],
|
|
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
|
};
|
|
|
|
const getDriverAvatar = async (id: string): Promise<string> => `avatar-${id}`;
|
|
|
|
const getDriverStats = () => null;
|
|
|
|
const useCase = new DashboardOverviewUseCase(
|
|
driverRepository,
|
|
raceRepository,
|
|
resultRepository,
|
|
leagueRepository,
|
|
standingRepository,
|
|
leagueMembershipRepository,
|
|
raceRegistrationRepository,
|
|
feedRepository,
|
|
socialRepository,
|
|
getDriverAvatar,
|
|
getDriverStats,
|
|
output,
|
|
);
|
|
|
|
const input: DashboardOverviewInput = { driverId };
|
|
|
|
const result: UseCaseResult<
|
|
void,
|
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
|
> = await useCase.execute(input);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
const err = result.unwrapErr();
|
|
expect(err.code).toBe('DRIVER_NOT_FOUND');
|
|
expect(err.details?.message).toBe('Driver not found');
|
|
|
|
expect(output.present).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns REPOSITORY_ERROR when an unexpected error occurs and does not present', async () => {
|
|
const output: UseCaseOutputPort<DashboardOverviewResult> = {
|
|
present: vi.fn(),
|
|
};
|
|
|
|
const driverId = 'driver-error';
|
|
|
|
const driver = Driver.create({ id: driverId, iracingId: '99999', name: 'Error Driver', country: 'GB' });
|
|
|
|
const driverRepository = {
|
|
findById: async (): Promise<Driver | null> => driver,
|
|
findByIRacingId: async (): Promise<Driver | null> => null,
|
|
findAll: async (): Promise<Driver[]> => [],
|
|
create: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Driver> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByIRacingId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const raceRepository = {
|
|
findById: async (): Promise<Race | null> => null,
|
|
findAll: async (): Promise<Race[]> => {
|
|
throw new Error('DB failure');
|
|
},
|
|
findByLeagueId: async (): Promise<Race[]> => [],
|
|
findUpcomingByLeagueId: async (): Promise<Race[]> => [],
|
|
findCompletedByLeagueId: async (): Promise<Race[]> => [],
|
|
findByStatus: async (): Promise<Race[]> => [],
|
|
findByDateRange: async (): Promise<Race[]> => [],
|
|
create: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<Race> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const resultRepository = {
|
|
findById: async (): Promise<RaceResult | null> => null,
|
|
findAll: async (): Promise<RaceResult[]> => [],
|
|
findByRaceId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverId: async (): Promise<RaceResult[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<RaceResult[]> => [],
|
|
create: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
createMany: async (): Promise<RaceResult[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<RaceResult> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByRaceId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
existsByRaceId: async (): Promise<boolean> => false,
|
|
};
|
|
|
|
const leagueRepository = {
|
|
findById: async (): Promise<League | null> => null,
|
|
findAll: async (): Promise<League[]> => [],
|
|
findByOwnerId: async (): Promise<League[]> => [],
|
|
create: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
update: async (): Promise<League> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
searchByName: async (): Promise<League[]> => [],
|
|
};
|
|
|
|
const standingRepository = {
|
|
findByLeagueId: async (): Promise<Standing[]> => [],
|
|
findByDriverIdAndLeagueId: async (): Promise<Standing | null> => null,
|
|
findAll: async (): Promise<Standing[]> => [],
|
|
save: async (): Promise<Standing> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveMany: async (): Promise<Standing[]> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
delete: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
deleteByLeagueId: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
exists: async (): Promise<boolean> => false,
|
|
recalculate: async (): Promise<Standing[]> => [],
|
|
};
|
|
|
|
const leagueMembershipRepository = {
|
|
getMembership: async (): Promise<LeagueMembership | null> => null,
|
|
getLeagueMembers: async (): Promise<LeagueMembership[]> => [],
|
|
getJoinRequests: async (): Promise<any[]> => [],
|
|
saveMembership: async (): Promise<LeagueMembership> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeMembership: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
saveJoinRequest: async (): Promise<any> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
removeJoinRequest: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const raceRegistrationRepository = {
|
|
isRegistered: async (): Promise<boolean> => false,
|
|
getRegisteredDrivers: async (): Promise<string[]> => [],
|
|
getRegistrationCount: async (): Promise<number> => 0,
|
|
register: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
withdraw: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
getDriverRegistrations: async (): Promise<string[]> => [],
|
|
clearRaceRegistrations: async (): Promise<void> => {
|
|
throw new Error('Not implemented');
|
|
},
|
|
};
|
|
|
|
const feedRepository = {
|
|
getFeedForDriver: async (): Promise<FeedItem[]> => [],
|
|
getGlobalFeed: async (): Promise<FeedItem[]> => [],
|
|
};
|
|
|
|
const socialRepository = {
|
|
getFriends: async (): Promise<Driver[]> => [],
|
|
getFriendIds: async (): Promise<string[]> => [],
|
|
getSuggestedFriends: async (): Promise<Driver[]> => [],
|
|
};
|
|
|
|
const getDriverAvatar = async (id: string): Promise<string> => `avatar-${id}`;
|
|
|
|
const getDriverStats = () => null;
|
|
|
|
const useCase = new DashboardOverviewUseCase(
|
|
driverRepository,
|
|
raceRepository,
|
|
resultRepository,
|
|
leagueRepository,
|
|
standingRepository,
|
|
leagueMembershipRepository,
|
|
raceRegistrationRepository,
|
|
feedRepository,
|
|
socialRepository,
|
|
getDriverAvatar,
|
|
getDriverStats,
|
|
output,
|
|
);
|
|
|
|
const input: DashboardOverviewInput = { driverId };
|
|
|
|
const result: UseCaseResult<
|
|
void,
|
|
ApplicationErrorCode<'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR', { message: string }>
|
|
> = await useCase.execute(input);
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
const err = result.unwrapErr();
|
|
expect(err.code).toBe('REPOSITORY_ERROR');
|
|
expect(err.details?.message).toBe('DB failure');
|
|
|
|
expect(output.present).not.toHaveBeenCalled();
|
|
});
|
|
});
|