Files
gridpilot.gg/apps/website/tests/view-data/leagues.test.ts
Marc Mintel c22e26d14c
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 5m48s
Contract Testing / contract-snapshot (pull_request) Has been skipped
view data tests
2026-01-22 17:27:08 +01:00

1886 lines
59 KiB
TypeScript

/**
* View Data Layer Tests - Leagues Functionality
*
* This test file covers the view data layer for leagues functionality.
*
* The view data layer is responsible for:
* - DTO → UI model mapping
* - Formatting, sorting, and grouping
* - Derived fields and defaults
* - UI-specific semantics
*
* This layer isolates the UI from API churn by providing a stable interface
* between the API layer and the presentation layer.
*
* Test coverage includes:
* - League list data transformation and sorting
* - Individual league profile view models
* - League roster data formatting and member management
* - League schedule and standings view models
* - League stewarding and protest handling data transformation
* - League wallet and sponsorship data formatting
* - League creation and migration data transformation
* - Derived league fields (member counts, status, permissions, etc.)
* - Default values and fallbacks for league views
* - League-specific formatting (dates, points, positions, race formats, etc.)
* - Data grouping and categorization for league components
* - League search and filtering view models
* - Real-time league data updates and state management
*/
import { LeaguesViewDataBuilder } from '@/lib/builders/view-data/LeaguesViewDataBuilder';
import { LeagueDetailViewDataBuilder } from '@/lib/builders/view-data/LeagueDetailViewDataBuilder';
import { LeagueRosterAdminViewDataBuilder } from '@/lib/builders/view-data/LeagueRosterAdminViewDataBuilder';
import { LeagueScheduleViewDataBuilder } from '@/lib/builders/view-data/LeagueScheduleViewDataBuilder';
import { LeagueStandingsViewDataBuilder } from '@/lib/builders/view-data/LeagueStandingsViewDataBuilder';
import type { AllLeaguesWithCapacityAndScoringDTO } from '@/lib/types/generated/AllLeaguesWithCapacityAndScoringDTO';
import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/LeagueWithCapacityAndScoringDTO';
import type { LeagueMembershipsDTO } from '@/lib/types/generated/LeagueMembershipsDTO';
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
import type { LeagueStandingDTO } from '@/lib/types/generated/LeagueStandingDTO';
import type { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
import type { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO';
import type { RaceDTO } from '@/lib/types/generated/RaceDTO';
describe('LeaguesViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform AllLeaguesWithCapacityAndScoringDTO to LeaguesViewData correctly', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Pro League',
description: 'A competitive league for experienced drivers',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Solo • 32 max',
},
usedSlots: 25,
category: 'competitive',
scoring: {
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-1',
scoringPresetName: 'Standard',
dropPolicySummary: 'Drop 2 worst races',
scoringPatternSummary: 'Points based on finish position',
},
timingSummary: 'Weekly races on Sundays',
logoUrl: 'https://example.com/logo.png',
pendingJoinRequestsCount: 3,
pendingProtestsCount: 1,
walletBalance: 1000,
},
{
id: 'league-2',
name: 'Rookie League',
description: null,
ownerId: 'owner-2',
createdAt: '2024-02-01T00:00:00.000Z',
settings: {
maxDrivers: 16,
qualifyingFormat: 'Solo • 16 max',
},
usedSlots: 10,
category: 'rookie',
scoring: {
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-2',
scoringPresetName: 'Rookie',
dropPolicySummary: 'No drops',
scoringPatternSummary: 'Points based on finish position',
},
timingSummary: 'Bi-weekly races',
logoUrl: null,
pendingJoinRequestsCount: 0,
pendingProtestsCount: 0,
walletBalance: 0,
},
],
totalCount: 2,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues).toHaveLength(2);
expect(result.leagues[0]).toEqual({
id: 'league-1',
name: 'Pro League',
description: 'A competitive league for experienced drivers',
logoUrl: 'https://example.com/logo.png',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
maxDrivers: 32,
usedDriverSlots: 25,
activeDriversCount: undefined,
nextRaceAt: undefined,
maxTeams: undefined,
usedTeamSlots: undefined,
structureSummary: 'Solo • 32 max',
timingSummary: 'Weekly races on Sundays',
category: 'competitive',
scoring: {
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-1',
scoringPresetName: 'Standard',
dropPolicySummary: 'Drop 2 worst races',
scoringPatternSummary: 'Points based on finish position',
},
});
expect(result.leagues[1]).toEqual({
id: 'league-2',
name: 'Rookie League',
description: null,
logoUrl: null,
ownerId: 'owner-2',
createdAt: '2024-02-01T00:00:00.000Z',
maxDrivers: 16,
usedDriverSlots: 10,
activeDriversCount: undefined,
nextRaceAt: undefined,
maxTeams: undefined,
usedTeamSlots: undefined,
structureSummary: 'Solo • 16 max',
timingSummary: 'Bi-weekly races',
category: 'rookie',
scoring: {
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-2',
scoringPresetName: 'Rookie',
dropPolicySummary: 'No drops',
scoringPatternSummary: 'Points based on finish position',
},
});
});
it('should handle empty leagues list', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [],
totalCount: 0,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues).toHaveLength(0);
});
it('should handle leagues with missing optional fields', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Minimal League',
description: '',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 20,
},
usedSlots: 5,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].description).toBe(null);
expect(result.leagues[0].logoUrl).toBe(null);
expect(result.leagues[0].category).toBe(null);
expect(result.leagues[0].scoring).toBeUndefined();
expect(result.leagues[0].timingSummary).toBe('');
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Solo • 32 max',
},
usedSlots: 20,
category: 'test',
scoring: {
gameId: 'game-1',
gameName: 'Test Game',
primaryChampionshipType: 'Test Type',
scoringPresetId: 'preset-1',
scoringPresetName: 'Test Preset',
dropPolicySummary: 'Test drop policy',
scoringPatternSummary: 'Test pattern',
},
timingSummary: 'Test timing',
logoUrl: 'https://example.com/test.png',
pendingJoinRequestsCount: 5,
pendingProtestsCount: 2,
walletBalance: 500,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].id).toBe(leaguesDTO.leagues[0].id);
expect(result.leagues[0].name).toBe(leaguesDTO.leagues[0].name);
expect(result.leagues[0].description).toBe(leaguesDTO.leagues[0].description);
expect(result.leagues[0].logoUrl).toBe(leaguesDTO.leagues[0].logoUrl);
expect(result.leagues[0].ownerId).toBe(leaguesDTO.leagues[0].ownerId);
expect(result.leagues[0].createdAt).toBe(leaguesDTO.leagues[0].createdAt);
expect(result.leagues[0].maxDrivers).toBe(leaguesDTO.leagues[0].settings.maxDrivers);
expect(result.leagues[0].usedDriverSlots).toBe(leaguesDTO.leagues[0].usedSlots);
expect(result.leagues[0].structureSummary).toBe(leaguesDTO.leagues[0].settings.qualifyingFormat);
expect(result.leagues[0].timingSummary).toBe(leaguesDTO.leagues[0].timingSummary);
expect(result.leagues[0].category).toBe(leaguesDTO.leagues[0].category);
expect(result.leagues[0].scoring).toEqual(leaguesDTO.leagues[0].scoring);
});
it('should not modify the input DTO', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Solo • 32 max',
},
usedSlots: 20,
category: 'test',
scoring: {
gameId: 'game-1',
gameName: 'Test Game',
primaryChampionshipType: 'Test Type',
scoringPresetId: 'preset-1',
scoringPresetName: 'Test Preset',
dropPolicySummary: 'Test drop policy',
scoringPatternSummary: 'Test pattern',
},
timingSummary: 'Test timing',
logoUrl: 'https://example.com/test.png',
pendingJoinRequestsCount: 5,
pendingProtestsCount: 2,
walletBalance: 500,
},
],
totalCount: 1,
};
const originalDTO = JSON.parse(JSON.stringify(leaguesDTO));
LeaguesViewDataBuilder.build(leaguesDTO);
expect(leaguesDTO).toEqual(originalDTO);
});
});
describe('edge cases', () => {
it('should handle leagues with very long descriptions', () => {
const longDescription = 'A'.repeat(1000);
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Test League',
description: longDescription,
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 20,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].description).toBe(longDescription);
});
it('should handle leagues with special characters in name', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'League & Co. (2024)',
description: 'Test league',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 20,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].name).toBe('League & Co. (2024)');
});
it('should handle leagues with zero used slots', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Empty League',
description: 'No members yet',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 0,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].usedDriverSlots).toBe(0);
});
it('should handle leagues with maximum capacity', () => {
const leaguesDTO: AllLeaguesWithCapacityAndScoringDTO = {
leagues: [
{
id: 'league-1',
name: 'Full League',
description: 'At maximum capacity',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 32,
},
],
totalCount: 1,
};
const result = LeaguesViewDataBuilder.build(leaguesDTO);
expect(result.leagues[0].usedDriverSlots).toBe(32);
expect(result.leagues[0].maxDrivers).toBe(32);
});
});
});
describe('LeagueDetailViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform league DTOs to LeagueDetailViewData correctly', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Pro League',
description: 'A competitive league for experienced drivers',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Solo • 32 max',
},
usedSlots: 25,
category: 'competitive',
scoring: {
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-1',
scoringPresetName: 'Standard',
dropPolicySummary: 'Drop 2 worst races',
scoringPatternSummary: 'Points based on finish position',
},
timingSummary: 'Weekly races on Sundays',
logoUrl: 'https://example.com/logo.png',
pendingJoinRequestsCount: 3,
pendingProtestsCount: 1,
walletBalance: 1000,
};
const owner: GetDriverOutputDTO = {
id: 'owner-1',
name: 'John Doe',
iracingId: '12345',
country: 'USA',
bio: 'Experienced driver',
joinedAt: '2023-01-01T00:00:00.000Z',
avatarUrl: 'https://example.com/avatar.jpg',
};
const scoringConfig: LeagueScoringConfigDTO = {
id: 'config-1',
leagueId: 'league-1',
gameId: 'game-1',
gameName: 'iRacing',
primaryChampionshipType: 'Single Championship',
scoringPresetId: 'preset-1',
scoringPresetName: 'Standard',
dropPolicySummary: 'Drop 2 worst races',
scoringPatternSummary: 'Points based on finish position',
dropRaces: 2,
pointsPerRace: 100,
pointsForWin: 25,
pointsForPodium: [20, 15, 10],
};
const memberships: LeagueMembershipsDTO = {
members: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
{
driverId: 'driver-2',
driver: {
id: 'driver-2',
name: 'Bob',
iracingId: '22222',
country: 'Germany',
joinedAt: '2023-07-01T00:00:00.000Z',
},
role: 'steward',
joinedAt: '2023-07-01T00:00:00.000Z',
},
{
driverId: 'driver-3',
driver: {
id: 'driver-3',
name: 'Charlie',
iracingId: '33333',
country: 'France',
joinedAt: '2023-08-01T00:00:00.000Z',
},
role: 'member',
joinedAt: '2023-08-01T00:00:00.000Z',
},
],
};
const races: RaceDTO[] = [
{
id: 'race-1',
name: 'Race 1',
date: '2024-01-15T14:00:00.000Z',
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
strengthOfField: 1500,
},
{
id: 'race-2',
name: 'Race 2',
date: '2024-01-22T14:00:00.000Z',
track: 'Monza',
car: 'Ferrari 488 GT3',
sessionType: 'race',
strengthOfField: 1600,
},
];
const sponsors: any[] = [
{
id: 'sponsor-1',
name: 'Sponsor A',
tier: 'main',
logoUrl: 'https://example.com/sponsor-a.png',
websiteUrl: 'https://sponsor-a.com',
tagline: 'Premium racing gear',
},
];
const result = LeagueDetailViewDataBuilder.build({
league,
owner,
scoringConfig,
memberships,
races,
sponsors,
});
expect(result.leagueId).toBe('league-1');
expect(result.name).toBe('Pro League');
expect(result.description).toBe('A competitive league for experienced drivers');
expect(result.logoUrl).toBe('https://example.com/logo.png');
expect(result.info.name).toBe('Pro League');
expect(result.info.description).toBe('A competitive league for experienced drivers');
expect(result.info.membersCount).toBe(3);
expect(result.info.racesCount).toBe(2);
expect(result.info.avgSOF).toBe(1550);
expect(result.info.structure).toBe('Solo • 32 max');
expect(result.info.scoring).toBe('preset-1');
expect(result.info.createdAt).toBe('2024-01-01T00:00:00.000Z');
expect(result.info.discordUrl).toBeUndefined();
expect(result.info.youtubeUrl).toBeUndefined();
expect(result.info.websiteUrl).toBeUndefined();
expect(result.ownerSummary).not.toBeNull();
expect(result.ownerSummary?.driverId).toBe('owner-1');
expect(result.ownerSummary?.driverName).toBe('John Doe');
expect(result.ownerSummary?.avatarUrl).toBe('https://example.com/avatar.jpg');
expect(result.ownerSummary?.roleBadgeText).toBe('Owner');
expect(result.adminSummaries).toHaveLength(1);
expect(result.adminSummaries[0].driverId).toBe('driver-1');
expect(result.adminSummaries[0].driverName).toBe('Alice');
expect(result.adminSummaries[0].roleBadgeText).toBe('Admin');
expect(result.stewardSummaries).toHaveLength(1);
expect(result.stewardSummaries[0].driverId).toBe('driver-2');
expect(result.stewardSummaries[0].driverName).toBe('Bob');
expect(result.stewardSummaries[0].roleBadgeText).toBe('Steward');
expect(result.memberSummaries).toHaveLength(1);
expect(result.memberSummaries[0].driverId).toBe('driver-3');
expect(result.memberSummaries[0].driverName).toBe('Charlie');
expect(result.memberSummaries[0].roleBadgeText).toBe('Member');
expect(result.sponsors).toHaveLength(1);
expect(result.sponsors[0].id).toBe('sponsor-1');
expect(result.sponsors[0].name).toBe('Sponsor A');
expect(result.sponsors[0].tier).toBe('main');
expect(result.walletBalance).toBe(1000);
expect(result.pendingProtestsCount).toBe(1);
expect(result.pendingJoinRequestsCount).toBe(3);
});
it('should handle league with no owner', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(result.ownerSummary).toBeNull();
});
it('should handle league with no scoring config', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(result.info.scoring).toBe('Standard');
});
it('should handle league with no races', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(result.info.racesCount).toBe(0);
expect(result.info.avgSOF).toBeNull();
expect(result.runningRaces).toEqual([]);
expect(result.nextRace).toBeUndefined();
expect(result.seasonProgress).toEqual({
completedRaces: 0,
totalRaces: 0,
percentage: 0,
});
expect(result.recentResults).toEqual([]);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Solo • 32 max',
},
usedSlots: 20,
category: 'test',
scoring: {
gameId: 'game-1',
gameName: 'Test Game',
primaryChampionshipType: 'Test Type',
scoringPresetId: 'preset-1',
scoringPresetName: 'Test Preset',
dropPolicySummary: 'Test drop policy',
scoringPatternSummary: 'Test pattern',
},
timingSummary: 'Test timing',
logoUrl: 'https://example.com/test.png',
pendingJoinRequestsCount: 5,
pendingProtestsCount: 2,
walletBalance: 500,
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(result.leagueId).toBe(league.id);
expect(result.name).toBe(league.name);
expect(result.description).toBe(league.description);
expect(result.logoUrl).toBe(league.logoUrl);
expect(result.walletBalance).toBe(league.walletBalance);
expect(result.pendingProtestsCount).toBe(league.pendingProtestsCount);
expect(result.pendingJoinRequestsCount).toBe(league.pendingJoinRequestsCount);
});
it('should not modify the input DTOs', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 20,
};
const originalLeague = JSON.parse(JSON.stringify(league));
LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(league).toEqual(originalLeague);
});
});
describe('edge cases', () => {
it('should handle league with missing optional fields', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Minimal League',
description: '',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races: [],
sponsors: [],
});
expect(result.description).toBe('');
expect(result.logoUrl).toBeUndefined();
expect(result.info.description).toBe('');
expect(result.info.discordUrl).toBeUndefined();
expect(result.info.youtubeUrl).toBeUndefined();
expect(result.info.websiteUrl).toBeUndefined();
});
it('should handle races with missing strengthOfField', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const races: RaceDTO[] = [
{
id: 'race-1',
name: 'Race 1',
date: '2024-01-15T14:00:00.000Z',
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
];
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races,
sponsors: [],
});
expect(result.info.avgSOF).toBeNull();
});
it('should handle races with zero strengthOfField', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const races: RaceDTO[] = [
{
id: 'race-1',
name: 'Race 1',
date: '2024-01-15T14:00:00.000Z',
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
strengthOfField: 0,
},
];
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races,
sponsors: [],
});
expect(result.info.avgSOF).toBeNull();
});
it('should handle races with different dates for next race calculation', () => {
const now = new Date();
const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day from now
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const races: RaceDTO[] = [
{
id: 'race-1',
name: 'Past Race',
date: pastDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
{
id: 'race-2',
name: 'Future Race',
date: futureDate.toISOString(),
track: 'Monza',
car: 'Ferrari 488 GT3',
sessionType: 'race',
},
];
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships: { members: [] },
races,
sponsors: [],
});
expect(result.nextRace).toBeDefined();
expect(result.nextRace?.id).toBe('race-2');
expect(result.nextRace?.name).toBe('Future Race');
expect(result.seasonProgress.completedRaces).toBe(1);
expect(result.seasonProgress.totalRaces).toBe(2);
expect(result.seasonProgress.percentage).toBe(50);
expect(result.recentResults).toHaveLength(1);
expect(result.recentResults[0].raceId).toBe('race-1');
});
it('should handle members with different roles', () => {
const league: LeagueWithCapacityAndScoringDTO = {
id: 'league-1',
name: 'Test League',
description: 'Test description',
ownerId: 'owner-1',
createdAt: '2024-01-01T00:00:00.000Z',
settings: {
maxDrivers: 32,
},
usedSlots: 10,
};
const memberships: LeagueMembershipsDTO = {
members: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Admin',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
{
driverId: 'driver-2',
driver: {
id: 'driver-2',
name: 'Steward',
iracingId: '22222',
country: 'Germany',
joinedAt: '2023-07-01T00:00:00.000Z',
},
role: 'steward',
joinedAt: '2023-07-01T00:00:00.000Z',
},
{
driverId: 'driver-3',
driver: {
id: 'driver-3',
name: 'Member',
iracingId: '33333',
country: 'France',
joinedAt: '2023-08-01T00:00:00.000Z',
},
role: 'member',
joinedAt: '2023-08-01T00:00:00.000Z',
},
],
};
const result = LeagueDetailViewDataBuilder.build({
league,
owner: null,
scoringConfig: null,
memberships,
races: [],
sponsors: [],
});
expect(result.adminSummaries).toHaveLength(1);
expect(result.stewardSummaries).toHaveLength(1);
expect(result.memberSummaries).toHaveLength(1);
expect(result.info.membersCount).toBe(3);
});
});
});
describe('LeagueRosterAdminViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform roster DTOs to LeagueRosterAdminViewData correctly', () => {
const members: LeagueRosterMemberDTO[] = [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
{
driverId: 'driver-2',
driver: {
id: 'driver-2',
name: 'Bob',
iracingId: '22222',
country: 'Germany',
joinedAt: '2023-07-01T00:00:00.000Z',
},
role: 'member',
joinedAt: '2023-07-01T00:00:00.000Z',
},
];
const joinRequests: LeagueRosterJoinRequestDTO[] = [
{
id: 'request-1',
leagueId: 'league-1',
driverId: 'driver-3',
requestedAt: '2024-01-15T10:00:00.000Z',
message: 'I would like to join this league',
driver: {},
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members,
joinRequests,
});
expect(result.leagueId).toBe('league-1');
expect(result.members).toHaveLength(2);
expect(result.members[0].driverId).toBe('driver-1');
expect(result.members[0].driver.id).toBe('driver-1');
expect(result.members[0].driver.name).toBe('Alice');
expect(result.members[0].role).toBe('admin');
expect(result.members[0].joinedAt).toBe('2023-06-01T00:00:00.000Z');
expect(result.members[0].formattedJoinedAt).toBeDefined();
expect(result.members[1].driverId).toBe('driver-2');
expect(result.members[1].driver.id).toBe('driver-2');
expect(result.members[1].driver.name).toBe('Bob');
expect(result.members[1].role).toBe('member');
expect(result.members[1].joinedAt).toBe('2023-07-01T00:00:00.000Z');
expect(result.members[1].formattedJoinedAt).toBeDefined();
expect(result.joinRequests).toHaveLength(1);
expect(result.joinRequests[0].id).toBe('request-1');
expect(result.joinRequests[0].driver.id).toBe('driver-3');
expect(result.joinRequests[0].driver.name).toBe('Unknown Driver');
expect(result.joinRequests[0].requestedAt).toBe('2024-01-15T10:00:00.000Z');
expect(result.joinRequests[0].formattedRequestedAt).toBeDefined();
expect(result.joinRequests[0].message).toBe('I would like to join this league');
});
it('should handle empty members and join requests', () => {
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members: [],
joinRequests: [],
});
expect(result.leagueId).toBe('league-1');
expect(result.members).toHaveLength(0);
expect(result.joinRequests).toHaveLength(0);
});
it('should handle members without driver details', () => {
const members: LeagueRosterMemberDTO[] = [
{
driverId: 'driver-1',
driver: undefined as any,
role: 'member',
joinedAt: '2023-06-01T00:00:00.000Z',
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members,
joinRequests: [],
});
expect(result.members[0].driver.name).toBe('Unknown Driver');
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const members: LeagueRosterMemberDTO[] = [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
];
const joinRequests: LeagueRosterJoinRequestDTO[] = [
{
id: 'request-1',
leagueId: 'league-1',
driverId: 'driver-3',
requestedAt: '2024-01-15T10:00:00.000Z',
message: 'I would like to join this league',
driver: {},
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members,
joinRequests,
});
expect(result.leagueId).toBe('league-1');
expect(result.members[0].driverId).toBe(members[0].driverId);
expect(result.members[0].driver.id).toBe(members[0].driver.id);
expect(result.members[0].driver.name).toBe(members[0].driver.name);
expect(result.members[0].role).toBe(members[0].role);
expect(result.members[0].joinedAt).toBe(members[0].joinedAt);
expect(result.joinRequests[0].id).toBe(joinRequests[0].id);
expect(result.joinRequests[0].requestedAt).toBe(joinRequests[0].requestedAt);
expect(result.joinRequests[0].message).toBe(joinRequests[0].message);
});
it('should not modify the input DTOs', () => {
const members: LeagueRosterMemberDTO[] = [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
];
const joinRequests: LeagueRosterJoinRequestDTO[] = [
{
id: 'request-1',
leagueId: 'league-1',
driverId: 'driver-3',
requestedAt: '2024-01-15T10:00:00.000Z',
message: 'I would like to join this league',
driver: {},
},
];
const originalMembers = JSON.parse(JSON.stringify(members));
const originalRequests = JSON.parse(JSON.stringify(joinRequests));
LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members,
joinRequests,
});
expect(members).toEqual(originalMembers);
expect(joinRequests).toEqual(originalRequests);
});
});
describe('edge cases', () => {
it('should handle members with missing driver field', () => {
const members: LeagueRosterMemberDTO[] = [
{
driverId: 'driver-1',
driver: undefined as any,
role: 'member',
joinedAt: '2023-06-01T00:00:00.000Z',
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members,
joinRequests: [],
});
expect(result.members[0].driver.name).toBe('Unknown Driver');
});
it('should handle join requests with missing driver field', () => {
const joinRequests: LeagueRosterJoinRequestDTO[] = [
{
id: 'request-1',
leagueId: 'league-1',
driverId: 'driver-3',
requestedAt: '2024-01-15T10:00:00.000Z',
message: 'I would like to join this league',
driver: undefined,
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members: [],
joinRequests,
});
expect(result.joinRequests[0].driver.name).toBe('Unknown Driver');
});
it('should handle join requests without message', () => {
const joinRequests: LeagueRosterJoinRequestDTO[] = [
{
id: 'request-1',
leagueId: 'league-1',
driverId: 'driver-3',
requestedAt: '2024-01-15T10:00:00.000Z',
driver: {},
},
];
const result = LeagueRosterAdminViewDataBuilder.build({
leagueId: 'league-1',
members: [],
joinRequests,
});
expect(result.joinRequests[0].message).toBeUndefined();
});
});
});
describe('LeagueScheduleViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform schedule DTO to LeagueScheduleViewData correctly', () => {
const now = new Date();
const pastDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 1 day from now
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Past Race',
date: pastDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
{
id: 'race-2',
name: 'Future Race',
date: futureDate.toISOString(),
track: 'Monza',
car: 'Ferrari 488 GT3',
sessionType: 'race',
},
],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto, 'driver-1', true);
expect(result.leagueId).toBe('league-1');
expect(result.races).toHaveLength(2);
expect(result.races[0].id).toBe('race-1');
expect(result.races[0].name).toBe('Past Race');
expect(result.races[0].scheduledAt).toBe(pastDate.toISOString());
expect(result.races[0].track).toBe('Spa');
expect(result.races[0].car).toBe('Porsche 911 GT3');
expect(result.races[0].sessionType).toBe('race');
expect(result.races[0].isPast).toBe(true);
expect(result.races[0].isUpcoming).toBe(false);
expect(result.races[0].status).toBe('completed');
expect(result.races[0].isUserRegistered).toBe(false);
expect(result.races[0].canRegister).toBe(false);
expect(result.races[0].canEdit).toBe(true);
expect(result.races[0].canReschedule).toBe(true);
expect(result.races[1].id).toBe('race-2');
expect(result.races[1].name).toBe('Future Race');
expect(result.races[1].scheduledAt).toBe(futureDate.toISOString());
expect(result.races[1].track).toBe('Monza');
expect(result.races[1].car).toBe('Ferrari 488 GT3');
expect(result.races[1].sessionType).toBe('race');
expect(result.races[1].isPast).toBe(false);
expect(result.races[1].isUpcoming).toBe(true);
expect(result.races[1].status).toBe('scheduled');
expect(result.races[1].isUserRegistered).toBe(false);
expect(result.races[1].canRegister).toBe(true);
expect(result.races[1].canEdit).toBe(true);
expect(result.races[1].canReschedule).toBe(true);
expect(result.currentDriverId).toBe('driver-1');
expect(result.isAdmin).toBe(true);
});
it('should handle empty races list', () => {
const apiDto = {
leagueId: 'league-1',
races: [],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto);
expect(result.leagueId).toBe('league-1');
expect(result.races).toHaveLength(0);
});
it('should handle non-admin user', () => {
const now = new Date();
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Future Race',
date: futureDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto, 'driver-1', false);
expect(result.races[0].canEdit).toBe(false);
expect(result.races[0].canReschedule).toBe(false);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const now = new Date();
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Test Race',
date: futureDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto);
expect(result.leagueId).toBe(apiDto.leagueId);
expect(result.races[0].id).toBe(apiDto.races[0].id);
expect(result.races[0].name).toBe(apiDto.races[0].name);
expect(result.races[0].scheduledAt).toBe(apiDto.races[0].date);
expect(result.races[0].track).toBe(apiDto.races[0].track);
expect(result.races[0].car).toBe(apiDto.races[0].car);
expect(result.races[0].sessionType).toBe(apiDto.races[0].sessionType);
});
it('should not modify the input DTO', () => {
const now = new Date();
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Test Race',
date: futureDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
],
};
const originalDto = JSON.parse(JSON.stringify(apiDto));
LeagueScheduleViewDataBuilder.build(apiDto);
expect(apiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle races with missing optional fields', () => {
const now = new Date();
const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000);
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Test Race',
date: futureDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto);
expect(result.races[0].track).toBe('Spa');
expect(result.races[0].car).toBe('Porsche 911 GT3');
expect(result.races[0].sessionType).toBe('race');
});
it('should handle races at exactly the current time', () => {
const now = new Date();
const currentRaceDate = new Date(now.getTime());
const apiDto = {
leagueId: 'league-1',
races: [
{
id: 'race-1',
name: 'Current Race',
date: currentRaceDate.toISOString(),
track: 'Spa',
car: 'Porsche 911 GT3',
sessionType: 'race',
},
],
};
const result = LeagueScheduleViewDataBuilder.build(apiDto);
// Race at current time should be considered past
expect(result.races[0].isPast).toBe(true);
expect(result.races[0].isUpcoming).toBe(false);
expect(result.races[0].status).toBe('completed');
});
});
});
describe('LeagueStandingsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform standings DTOs to LeagueStandingsViewData correctly', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: ['race-1', 'race-2'],
},
{
driverId: 'driver-2',
driver: {
id: 'driver-2',
name: 'Bob',
iracingId: '22222',
country: 'Germany',
},
points: 1100,
position: 2,
wins: 3,
podiums: 8,
races: 15,
positionChange: -1,
lastRacePoints: 15,
droppedRaceIds: [],
},
],
};
const membershipsDto = {
members: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'member',
joinedAt: '2023-06-01T00:00:00.000Z',
},
{
driverId: 'driver-2',
driver: {
id: 'driver-2',
name: 'Bob',
iracingId: '22222',
country: 'Germany',
joinedAt: '2023-07-01T00:00:00.000Z',
},
role: 'member',
joinedAt: '2023-07-01T00:00:00.000Z',
},
],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.leagueId).toBe('league-1');
expect(result.isTeamChampionship).toBe(false);
expect(result.currentDriverId).toBeNull();
expect(result.isAdmin).toBe(false);
expect(result.standings).toHaveLength(2);
expect(result.standings[0].driverId).toBe('driver-1');
expect(result.standings[0].position).toBe(1);
expect(result.standings[0].totalPoints).toBe(1250);
expect(result.standings[0].racesFinished).toBe(15);
expect(result.standings[0].racesStarted).toBe(15);
expect(result.standings[0].avgFinish).toBeNull();
expect(result.standings[0].penaltyPoints).toBe(0);
expect(result.standings[0].bonusPoints).toBe(0);
expect(result.standings[0].positionChange).toBe(2);
expect(result.standings[0].lastRacePoints).toBe(25);
expect(result.standings[0].droppedRaceIds).toEqual(['race-1', 'race-2']);
expect(result.standings[0].wins).toBe(5);
expect(result.standings[0].podiums).toBe(10);
expect(result.standings[1].driverId).toBe('driver-2');
expect(result.standings[1].position).toBe(2);
expect(result.standings[1].totalPoints).toBe(1100);
expect(result.standings[1].racesFinished).toBe(15);
expect(result.standings[1].racesStarted).toBe(15);
expect(result.standings[1].avgFinish).toBeNull();
expect(result.standings[1].penaltyPoints).toBe(0);
expect(result.standings[1].bonusPoints).toBe(0);
expect(result.standings[1].positionChange).toBe(-1);
expect(result.standings[1].lastRacePoints).toBe(15);
expect(result.standings[1].droppedRaceIds).toEqual([]);
expect(result.standings[1].wins).toBe(3);
expect(result.standings[1].podiums).toBe(8);
expect(result.drivers).toHaveLength(2);
expect(result.drivers[0].id).toBe('driver-1');
expect(result.drivers[0].name).toBe('Alice');
expect(result.drivers[0].iracingId).toBe('11111');
expect(result.drivers[0].country).toBe('UK');
expect(result.drivers[0].avatarUrl).toBeNull();
expect(result.drivers[1].id).toBe('driver-2');
expect(result.drivers[1].name).toBe('Bob');
expect(result.drivers[1].iracingId).toBe('22222');
expect(result.drivers[1].country).toBe('Germany');
expect(result.drivers[1].avatarUrl).toBeNull();
expect(result.memberships).toHaveLength(2);
expect(result.memberships[0].driverId).toBe('driver-1');
expect(result.memberships[0].leagueId).toBe('league-1');
expect(result.memberships[0].role).toBe('member');
expect(result.memberships[0].joinedAt).toBe('2023-06-01T00:00:00.000Z');
expect(result.memberships[0].status).toBe('active');
expect(result.memberships[1].driverId).toBe('driver-2');
expect(result.memberships[1].leagueId).toBe('league-1');
expect(result.memberships[1].role).toBe('member');
expect(result.memberships[1].joinedAt).toBe('2023-07-01T00:00:00.000Z');
expect(result.memberships[1].status).toBe('active');
});
it('should handle empty standings and memberships', () => {
const standingsDto = {
standings: [],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.standings).toHaveLength(0);
expect(result.drivers).toHaveLength(0);
expect(result.memberships).toHaveLength(0);
});
it('should handle team championship mode', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: [],
},
],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
true
);
expect(result.isTeamChampionship).toBe(true);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: ['race-1'],
},
],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.standings[0].driverId).toBe(standingsDto.standings[0].driverId);
expect(result.standings[0].position).toBe(standingsDto.standings[0].position);
expect(result.standings[0].totalPoints).toBe(standingsDto.standings[0].points);
expect(result.standings[0].racesFinished).toBe(standingsDto.standings[0].races);
expect(result.standings[0].racesStarted).toBe(standingsDto.standings[0].races);
expect(result.standings[0].positionChange).toBe(standingsDto.standings[0].positionChange);
expect(result.standings[0].lastRacePoints).toBe(standingsDto.standings[0].lastRacePoints);
expect(result.standings[0].droppedRaceIds).toEqual(standingsDto.standings[0].droppedRaceIds);
expect(result.standings[0].wins).toBe(standingsDto.standings[0].wins);
expect(result.standings[0].podiums).toBe(standingsDto.standings[0].podiums);
expect(result.drivers[0].id).toBe(standingsDto.standings[0].driver.id);
expect(result.drivers[0].name).toBe(standingsDto.standings[0].driver.name);
expect(result.drivers[0].iracingId).toBe(standingsDto.standings[0].driver.iracingId);
expect(result.drivers[0].country).toBe(standingsDto.standings[0].driver.country);
});
it('should not modify the input DTOs', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: ['race-1'],
},
],
};
const membershipsDto = {
members: [],
};
const originalStandings = JSON.parse(JSON.stringify(standingsDto));
const originalMemberships = JSON.parse(JSON.stringify(membershipsDto));
LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(standingsDto).toEqual(originalStandings);
expect(membershipsDto).toEqual(originalMemberships);
});
});
describe('edge cases', () => {
it('should handle standings with missing optional fields', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
},
],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.standings[0].positionChange).toBe(0);
expect(result.standings[0].lastRacePoints).toBe(0);
expect(result.standings[0].droppedRaceIds).toEqual([]);
});
it('should handle standings with missing driver field', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: undefined as any,
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: [],
},
],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.drivers).toHaveLength(0);
});
it('should handle duplicate drivers in standings', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: [],
},
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1100,
position: 2,
wins: 3,
podiums: 8,
races: 15,
positionChange: -1,
lastRacePoints: 15,
droppedRaceIds: [],
},
],
};
const membershipsDto = {
members: [],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
// Should only have one driver entry
expect(result.drivers).toHaveLength(1);
expect(result.drivers[0].id).toBe('driver-1');
});
it('should handle members with different roles', () => {
const standingsDto = {
standings: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
},
points: 1250,
position: 1,
wins: 5,
podiums: 10,
races: 15,
positionChange: 2,
lastRacePoints: 25,
droppedRaceIds: [],
},
],
};
const membershipsDto = {
members: [
{
driverId: 'driver-1',
driver: {
id: 'driver-1',
name: 'Alice',
iracingId: '11111',
country: 'UK',
joinedAt: '2023-06-01T00:00:00.000Z',
},
role: 'admin',
joinedAt: '2023-06-01T00:00:00.000Z',
},
],
};
const result = LeagueStandingsViewDataBuilder.build(
standingsDto,
membershipsDto,
'league-1',
false
);
expect(result.memberships[0].role).toBe('admin');
});
});
});