view data tests
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 5m55s
Contract Testing / contract-snapshot (pull_request) Has been skipped

This commit is contained in:
2026-01-22 18:22:08 +01:00
parent 1f4f837282
commit 108cfbcd65
18 changed files with 8030 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
import { describe, it, expect } from 'vitest';
import { GenerateAvatarsViewDataBuilder } from './GenerateAvatarsViewDataBuilder';
import type { RequestAvatarGenerationOutputDTO } from '@/lib/types/generated/RequestAvatarGenerationOutputDTO';
describe('GenerateAvatarsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform RequestAvatarGenerationOutputDTO to GenerateAvatarsViewData correctly', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1', 'avatar-url-2', 'avatar-url-3'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result).toEqual({
success: true,
avatarUrls: ['avatar-url-1', 'avatar-url-2', 'avatar-url-3'],
errorMessage: null,
});
});
it('should handle empty avatar URLs', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: [],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toHaveLength(0);
});
it('should handle single avatar URL', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toHaveLength(1);
expect(result.avatarUrls[0]).toBe('avatar-url-1');
});
it('should handle multiple avatar URLs', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1', 'avatar-url-2', 'avatar-url-3', 'avatar-url-4', 'avatar-url-5'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toHaveLength(5);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1', 'avatar-url-2'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.success).toBe(requestAvatarGenerationOutputDto.success);
expect(result.avatarUrls).toEqual(requestAvatarGenerationOutputDto.avatarUrls);
expect(result.errorMessage).toBe(requestAvatarGenerationOutputDto.errorMessage);
});
it('should not modify the input DTO', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1'],
errorMessage: null,
};
const originalDto = { ...requestAvatarGenerationOutputDto };
GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(requestAvatarGenerationOutputDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle success false', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: false,
avatarUrls: [],
errorMessage: 'Generation failed',
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.success).toBe(false);
});
it('should handle error message', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: false,
avatarUrls: [],
errorMessage: 'Invalid input data',
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.errorMessage).toBe('Invalid input data');
});
it('should handle null error message', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.errorMessage).toBeNull();
});
it('should handle undefined avatarUrls', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: undefined,
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toEqual([]);
});
it('should handle empty string avatar URLs', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['', 'avatar-url-1', ''],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toEqual(['', 'avatar-url-1', '']);
});
it('should handle special characters in avatar URLs', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: ['avatar-url-1?param=value', 'avatar-url-2#anchor', 'avatar-url-3?query=1&test=2'],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toEqual([
'avatar-url-1?param=value',
'avatar-url-2#anchor',
'avatar-url-3?query=1&test=2',
]);
});
it('should handle very long avatar URLs', () => {
const longUrl = 'https://example.com/avatars/' + 'a'.repeat(1000) + '.png';
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: [longUrl],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls[0]).toBe(longUrl);
});
it('should handle avatar URLs with special characters', () => {
const requestAvatarGenerationOutputDto: RequestAvatarGenerationOutputDTO = {
success: true,
avatarUrls: [
'avatar-url-1?name=John%20Doe',
'avatar-url-2?email=test@example.com',
'avatar-url-3?query=hello%20world',
],
errorMessage: null,
};
const result = GenerateAvatarsViewDataBuilder.build(requestAvatarGenerationOutputDto);
expect(result.avatarUrls).toEqual([
'avatar-url-1?name=John%20Doe',
'avatar-url-2?email=test@example.com',
'avatar-url-3?query=hello%20world',
]);
});
});
});

View File

@@ -0,0 +1,167 @@
import { describe, it, expect } from 'vitest';
import { HomeViewDataBuilder } from './HomeViewDataBuilder';
import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO';
describe('HomeViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform HomeDataDTO to HomeViewData correctly', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: true,
upcomingRaces: [
{
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
track: 'Test Track',
},
],
topLeagues: [
{
id: 'league-1',
name: 'Test League',
description: 'Test Description',
},
],
teams: [
{
id: 'team-1',
name: 'Test Team',
tag: 'TT',
},
],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result).toEqual({
isAlpha: true,
upcomingRaces: [
{
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
track: 'Test Track',
},
],
topLeagues: [
{
id: 'league-1',
name: 'Test League',
description: 'Test Description',
},
],
teams: [
{
id: 'team-1',
name: 'Test Team',
tag: 'TT',
},
],
});
});
it('should handle empty arrays correctly', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: false,
upcomingRaces: [],
topLeagues: [],
teams: [],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result).toEqual({
isAlpha: false,
upcomingRaces: [],
topLeagues: [],
teams: [],
});
});
it('should handle multiple items in arrays', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: true,
upcomingRaces: [
{ id: 'race-1', name: 'Race 1', scheduledAt: '2024-01-01T10:00:00Z', track: 'Track 1' },
{ id: 'race-2', name: 'Race 2', scheduledAt: '2024-01-02T10:00:00Z', track: 'Track 2' },
],
topLeagues: [
{ id: 'league-1', name: 'League 1', description: 'Description 1' },
{ id: 'league-2', name: 'League 2', description: 'Description 2' },
],
teams: [
{ id: 'team-1', name: 'Team 1', tag: 'T1' },
{ id: 'team-2', name: 'Team 2', tag: 'T2' },
],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result.upcomingRaces).toHaveLength(2);
expect(result.topLeagues).toHaveLength(2);
expect(result.teams).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: true,
upcomingRaces: [{ id: 'race-1', name: 'Race', scheduledAt: '2024-01-01T10:00:00Z', track: 'Track' }],
topLeagues: [{ id: 'league-1', name: 'League', description: 'Description' }],
teams: [{ id: 'team-1', name: 'Team', tag: 'T' }],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result.isAlpha).toBe(homeDataDto.isAlpha);
expect(result.upcomingRaces).toEqual(homeDataDto.upcomingRaces);
expect(result.topLeagues).toEqual(homeDataDto.topLeagues);
expect(result.teams).toEqual(homeDataDto.teams);
});
it('should not modify the input DTO', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: true,
upcomingRaces: [{ id: 'race-1', name: 'Race', scheduledAt: '2024-01-01T10:00:00Z', track: 'Track' }],
topLeagues: [{ id: 'league-1', name: 'League', description: 'Description' }],
teams: [{ id: 'team-1', name: 'Team', tag: 'T' }],
};
const originalDto = { ...homeDataDto };
HomeViewDataBuilder.build(homeDataDto);
expect(homeDataDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle false isAlpha value', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: false,
upcomingRaces: [],
topLeagues: [],
teams: [],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result.isAlpha).toBe(false);
});
it('should handle null/undefined values in arrays', () => {
const homeDataDto: HomeDataDTO = {
isAlpha: true,
upcomingRaces: [{ id: 'race-1', name: 'Race', scheduledAt: '2024-01-01T10:00:00Z', track: 'Track' }],
topLeagues: [{ id: 'league-1', name: 'League', description: 'Description' }],
teams: [{ id: 'team-1', name: 'Team', tag: 'T' }],
};
const result = HomeViewDataBuilder.build(homeDataDto);
expect(result.upcomingRaces[0].id).toBe('race-1');
expect(result.topLeagues[0].id).toBe('league-1');
expect(result.teams[0].id).toBe('team-1');
});
});
});

View File

@@ -0,0 +1,148 @@
import { describe, it, expect } from 'vitest';
import { LeagueSettingsViewDataBuilder } from './LeagueSettingsViewDataBuilder';
import type { LeagueSettingsApiDto } from '@/lib/types/tbd/LeagueSettingsApiDto';
describe('LeagueSettingsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform LeagueSettingsApiDto to LeagueSettingsViewData correctly', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-123',
league: {
id: 'league-123',
name: 'Test League',
description: 'Test Description',
},
config: {
maxDrivers: 32,
qualifyingFormat: 'Open',
raceLength: 30,
},
};
const result = LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(result).toEqual({
leagueId: 'league-123',
league: {
id: 'league-123',
name: 'Test League',
description: 'Test Description',
},
config: {
maxDrivers: 32,
qualifyingFormat: 'Open',
raceLength: 30,
},
});
});
it('should handle minimal configuration', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-456',
league: {
id: 'league-456',
name: 'Minimal League',
description: '',
},
config: {
maxDrivers: 16,
qualifyingFormat: 'Open',
raceLength: 20,
},
};
const result = LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(result.leagueId).toBe('league-456');
expect(result.league.name).toBe('Minimal League');
expect(result.config.maxDrivers).toBe(16);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-789',
league: {
id: 'league-789',
name: 'Full League',
description: 'Full Description',
},
config: {
maxDrivers: 24,
qualifyingFormat: 'Open',
raceLength: 45,
},
};
const result = LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(result.leagueId).toBe(leagueSettingsApiDto.leagueId);
expect(result.league).toEqual(leagueSettingsApiDto.league);
expect(result.config).toEqual(leagueSettingsApiDto.config);
});
it('should not modify the input DTO', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-101',
league: {
id: 'league-101',
name: 'Test League',
description: 'Test',
},
config: {
maxDrivers: 20,
qualifyingFormat: 'Open',
raceLength: 25,
},
};
const originalDto = { ...leagueSettingsApiDto };
LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(leagueSettingsApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle different qualifying formats', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-102',
league: {
id: 'league-102',
name: 'Test League',
description: 'Test',
},
config: {
maxDrivers: 20,
qualifyingFormat: 'Closed',
raceLength: 30,
},
};
const result = LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(result.config.qualifyingFormat).toBe('Closed');
});
it('should handle large driver counts', () => {
const leagueSettingsApiDto: LeagueSettingsApiDto = {
leagueId: 'league-103',
league: {
id: 'league-103',
name: 'Test League',
description: 'Test',
},
config: {
maxDrivers: 100,
qualifyingFormat: 'Open',
raceLength: 60,
},
};
const result = LeagueSettingsViewDataBuilder.build(leagueSettingsApiDto);
expect(result.config.maxDrivers).toBe(100);
});
});
});

View File

@@ -0,0 +1,235 @@
import { describe, it, expect } from 'vitest';
import { LeagueSponsorshipsViewDataBuilder } from './LeagueSponsorshipsViewDataBuilder';
import type { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
describe('LeagueSponsorshipsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform LeagueSponsorshipsApiDto to LeagueSponsorshipsViewData correctly', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-123',
league: {
id: 'league-123',
name: 'Test League',
},
sponsorshipSlots: [
{
id: 'slot-1',
name: 'Primary Sponsor',
price: 1000,
status: 'available',
},
],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
},
],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result).toEqual({
leagueId: 'league-123',
activeTab: 'overview',
onTabChange: expect.any(Function),
league: {
id: 'league-123',
name: 'Test League',
},
sponsorshipSlots: [
{
id: 'slot-1',
name: 'Primary Sponsor',
price: 1000,
status: 'available',
},
],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
formattedRequestedAt: expect.any(String),
statusLabel: expect.any(String),
},
],
});
});
it('should handle empty sponsorship requests', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-456',
league: {
id: 'league-456',
name: 'Test League',
},
sponsorshipSlots: [
{
id: 'slot-1',
name: 'Primary Sponsor',
price: 1000,
status: 'available',
},
],
sponsorshipRequests: [],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result.sponsorshipRequests).toHaveLength(0);
});
it('should handle multiple sponsorship requests', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-789',
league: {
id: 'league-789',
name: 'Test League',
},
sponsorshipSlots: [],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Sponsor 1',
sponsorLogo: 'logo-1',
message: 'Message 1',
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
},
{
id: 'request-2',
sponsorId: 'sponsor-2',
sponsorName: 'Sponsor 2',
sponsorLogo: 'logo-2',
message: 'Message 2',
requestedAt: '2024-01-02T10:00:00Z',
status: 'approved',
},
],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result.sponsorshipRequests).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-101',
league: {
id: 'league-101',
name: 'Test League',
},
sponsorshipSlots: [
{
id: 'slot-1',
name: 'Primary Sponsor',
price: 1000,
status: 'available',
},
],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
},
],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result.leagueId).toBe(leagueSponsorshipsApiDto.leagueId);
expect(result.league).toEqual(leagueSponsorshipsApiDto.league);
expect(result.sponsorshipSlots).toEqual(leagueSponsorshipsApiDto.sponsorshipSlots);
});
it('should not modify the input DTO', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-102',
league: {
id: 'league-102',
name: 'Test League',
},
sponsorshipSlots: [],
sponsorshipRequests: [],
};
const originalDto = { ...leagueSponsorshipsApiDto };
LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(leagueSponsorshipsApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle requests without sponsor logo', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-103',
league: {
id: 'league-103',
name: 'Test League',
},
sponsorshipSlots: [],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: null,
message: 'Test message',
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
},
],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result.sponsorshipRequests[0].sponsorLogoUrl).toBeNull();
});
it('should handle requests without message', () => {
const leagueSponsorshipsApiDto: LeagueSponsorshipsApiDto = {
leagueId: 'league-104',
league: {
id: 'league-104',
name: 'Test League',
},
sponsorshipSlots: [],
sponsorshipRequests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: null,
requestedAt: '2024-01-01T10:00:00Z',
status: 'pending',
},
],
};
const result = LeagueSponsorshipsViewDataBuilder.build(leagueSponsorshipsApiDto);
expect(result.sponsorshipRequests[0].message).toBeNull();
});
});
});

View File

@@ -0,0 +1,213 @@
import { describe, it, expect } from 'vitest';
import { LeagueWalletViewDataBuilder } from './LeagueWalletViewDataBuilder';
import type { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
describe('LeagueWalletViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform LeagueWalletApiDto to LeagueWalletViewData correctly', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-123',
balance: 5000,
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: 1000,
status: 'completed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Sponsorship payment',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result).toEqual({
leagueId: 'league-123',
balance: 5000,
formattedBalance: expect.any(String),
totalRevenue: 5000,
formattedTotalRevenue: expect.any(String),
totalFees: 0,
formattedTotalFees: expect.any(String),
pendingPayouts: 0,
formattedPendingPayouts: expect.any(String),
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: 1000,
status: 'completed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Sponsorship payment',
formattedAmount: expect.any(String),
amountColor: 'green',
formattedDate: expect.any(String),
statusColor: 'green',
typeColor: 'blue',
},
],
});
});
it('should handle empty transactions', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-456',
balance: 0,
currency: 'USD',
transactions: [],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.transactions).toHaveLength(0);
expect(result.balance).toBe(0);
});
it('should handle multiple transactions', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-789',
balance: 10000,
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: 5000,
status: 'completed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Sponsorship payment',
},
{
id: 'txn-2',
amount: -1000,
status: 'completed',
createdAt: '2024-01-02T10:00:00Z',
description: 'Payout',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.transactions).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-101',
balance: 7500,
currency: 'EUR',
transactions: [
{
id: 'txn-1',
amount: 2500,
status: 'completed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Test transaction',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.leagueId).toBe(leagueWalletApiDto.leagueId);
expect(result.balance).toBe(leagueWalletApiDto.balance);
expect(result.currency).toBe(leagueWalletApiDto.currency);
});
it('should not modify the input DTO', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-102',
balance: 5000,
currency: 'USD',
transactions: [],
};
const originalDto = { ...leagueWalletApiDto };
LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(leagueWalletApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle negative balance', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-103',
balance: -500,
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: -500,
status: 'completed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Overdraft',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.balance).toBe(-500);
expect(result.transactions[0].amountColor).toBe('red');
});
it('should handle pending transactions', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-104',
balance: 1000,
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: 500,
status: 'pending',
createdAt: '2024-01-01T10:00:00Z',
description: 'Pending payment',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.transactions[0].statusColor).toBe('yellow');
});
it('should handle failed transactions', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-105',
balance: 1000,
currency: 'USD',
transactions: [
{
id: 'txn-1',
amount: 500,
status: 'failed',
createdAt: '2024-01-01T10:00:00Z',
description: 'Failed payment',
},
],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.transactions[0].statusColor).toBe('red');
});
it('should handle different currencies', () => {
const leagueWalletApiDto: LeagueWalletApiDto = {
leagueId: 'league-106',
balance: 1000,
currency: 'EUR',
transactions: [],
};
const result = LeagueWalletViewDataBuilder.build(leagueWalletApiDto);
expect(result.currency).toBe('EUR');
});
});
});

View File

@@ -0,0 +1,243 @@
import { describe, it, expect } from 'vitest';
import { ProfileLeaguesViewDataBuilder } from './ProfileLeaguesViewDataBuilder';
describe('ProfileLeaguesViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform ProfileLeaguesPageDto to ProfileLeaguesViewData correctly', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Owned League',
description: 'Test Description',
membershipRole: 'owner' as const,
},
],
memberLeagues: [
{
leagueId: 'league-2',
name: 'Member League',
description: 'Test Description',
membershipRole: 'member' as const,
},
],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result).toEqual({
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Owned League',
description: 'Test Description',
membershipRole: 'owner',
},
],
memberLeagues: [
{
leagueId: 'league-2',
name: 'Member League',
description: 'Test Description',
membershipRole: 'member',
},
],
});
});
it('should handle empty owned leagues', () => {
const profileLeaguesPageDto = {
ownedLeagues: [],
memberLeagues: [
{
leagueId: 'league-1',
name: 'Member League',
description: 'Test Description',
membershipRole: 'member' as const,
},
],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues).toHaveLength(0);
expect(result.memberLeagues).toHaveLength(1);
});
it('should handle empty member leagues', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Owned League',
description: 'Test Description',
membershipRole: 'owner' as const,
},
],
memberLeagues: [],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues).toHaveLength(1);
expect(result.memberLeagues).toHaveLength(0);
});
it('should handle multiple leagues in both arrays', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Owned League 1',
description: 'Description 1',
membershipRole: 'owner' as const,
},
{
leagueId: 'league-2',
name: 'Owned League 2',
description: 'Description 2',
membershipRole: 'admin' as const,
},
],
memberLeagues: [
{
leagueId: 'league-3',
name: 'Member League 1',
description: 'Description 3',
membershipRole: 'member' as const,
},
{
leagueId: 'league-4',
name: 'Member League 2',
description: 'Description 4',
membershipRole: 'steward' as const,
},
],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues).toHaveLength(2);
expect(result.memberLeagues).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Test League',
description: 'Test Description',
membershipRole: 'owner' as const,
},
],
memberLeagues: [
{
leagueId: 'league-2',
name: 'Test League 2',
description: 'Test Description 2',
membershipRole: 'member' as const,
},
],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues[0].leagueId).toBe(profileLeaguesPageDto.ownedLeagues[0].leagueId);
expect(result.ownedLeagues[0].name).toBe(profileLeaguesPageDto.ownedLeagues[0].name);
expect(result.ownedLeagues[0].description).toBe(profileLeaguesPageDto.ownedLeagues[0].description);
expect(result.ownedLeagues[0].membershipRole).toBe(profileLeaguesPageDto.ownedLeagues[0].membershipRole);
expect(result.memberLeagues[0].leagueId).toBe(profileLeaguesPageDto.memberLeagues[0].leagueId);
expect(result.memberLeagues[0].name).toBe(profileLeaguesPageDto.memberLeagues[0].name);
expect(result.memberLeagues[0].description).toBe(profileLeaguesPageDto.memberLeagues[0].description);
expect(result.memberLeagues[0].membershipRole).toBe(profileLeaguesPageDto.memberLeagues[0].membershipRole);
});
it('should not modify the input DTO', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Test League',
description: 'Test Description',
membershipRole: 'owner' as const,
},
],
memberLeagues: [
{
leagueId: 'league-2',
name: 'Test League 2',
description: 'Test Description 2',
membershipRole: 'member' as const,
},
],
};
const originalDto = { ...profileLeaguesPageDto };
ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(profileLeaguesPageDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle different membership roles', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Test League',
description: 'Test Description',
membershipRole: 'owner' as const,
},
{
leagueId: 'league-2',
name: 'Test League 2',
description: 'Test Description 2',
membershipRole: 'admin' as const,
},
{
leagueId: 'league-3',
name: 'Test League 3',
description: 'Test Description 3',
membershipRole: 'steward' as const,
},
{
leagueId: 'league-4',
name: 'Test League 4',
description: 'Test Description 4',
membershipRole: 'member' as const,
},
],
memberLeagues: [],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues[0].membershipRole).toBe('owner');
expect(result.ownedLeagues[1].membershipRole).toBe('admin');
expect(result.ownedLeagues[2].membershipRole).toBe('steward');
expect(result.ownedLeagues[3].membershipRole).toBe('member');
});
it('should handle empty description', () => {
const profileLeaguesPageDto = {
ownedLeagues: [
{
leagueId: 'league-1',
name: 'Test League',
description: '',
membershipRole: 'owner' as const,
},
],
memberLeagues: [],
};
const result = ProfileLeaguesViewDataBuilder.build(profileLeaguesPageDto);
expect(result.ownedLeagues[0].description).toBe('');
});
});
});

View File

@@ -0,0 +1,499 @@
import { describe, it, expect } from 'vitest';
import { ProfileViewDataBuilder } from './ProfileViewDataBuilder';
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
describe('ProfileViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform GetDriverProfileOutputDTO to ProfileViewData correctly', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: 'Test bio',
iracingId: 12345,
joinedAt: '2024-01-01',
globalRank: 100,
},
stats: {
totalRaces: 50,
wins: 10,
podiums: 20,
dnfs: 5,
avgFinish: 5.5,
bestFinish: 1,
worstFinish: 20,
finishRate: 90,
winRate: 20,
podiumRate: 40,
percentile: 95,
rating: 1500,
consistency: 85,
overallRank: 100,
},
finishDistribution: {
totalRaces: 50,
wins: 10,
podiums: 20,
topTen: 30,
dnfs: 5,
other: 15,
},
teamMemberships: [
{
teamId: 'team-1',
teamName: 'Test Team',
teamTag: 'TT',
role: 'driver',
joinedAt: '2024-01-01',
isCurrent: true,
},
],
socialSummary: {
friendsCount: 10,
friends: [
{
id: 'friend-1',
name: 'Friend 1',
country: 'US',
avatarUrl: 'avatar-url',
},
],
},
extendedProfile: {
socialHandles: [
{
platform: 'twitter',
handle: '@test',
url: 'https://twitter.com/test',
},
],
achievements: [
{
id: 'ach-1',
title: 'Achievement',
description: 'Test achievement',
icon: 'trophy',
rarity: 'rare',
earnedAt: '2024-01-01',
},
],
racingStyle: 'Aggressive',
favoriteTrack: 'Test Track',
favoriteCar: 'Test Car',
timezone: 'UTC',
availableHours: 10,
lookingForTeam: true,
openToRequests: true,
},
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.id).toBe('driver-123');
expect(result.driver.name).toBe('Test Driver');
expect(result.driver.countryCode).toBe('US');
expect(result.driver.bio).toBe('Test bio');
expect(result.driver.iracingId).toBe('12345');
expect(result.stats).not.toBeNull();
expect(result.stats?.ratingLabel).toBe('1500');
expect(result.teamMemberships).toHaveLength(1);
expect(result.extendedProfile).not.toBeNull();
expect(result.extendedProfile?.socialHandles).toHaveLength(1);
expect(result.extendedProfile?.achievements).toHaveLength(1);
});
it('should handle null driver (no profile)', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: null,
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.id).toBe('');
expect(result.driver.name).toBe('');
expect(result.driver.countryCode).toBe('');
expect(result.driver.bio).toBeNull();
expect(result.driver.iracingId).toBeNull();
expect(result.stats).toBeNull();
expect(result.teamMemberships).toHaveLength(0);
expect(result.extendedProfile).toBeNull();
});
it('should handle null stats', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.stats).toBeNull();
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: 'Test bio',
iracingId: 12345,
joinedAt: '2024-01-01',
globalRank: 100,
},
stats: {
totalRaces: 50,
wins: 10,
podiums: 20,
dnfs: 5,
avgFinish: 5.5,
bestFinish: 1,
worstFinish: 20,
finishRate: 90,
winRate: 20,
podiumRate: 40,
percentile: 95,
rating: 1500,
consistency: 85,
overallRank: 100,
},
finishDistribution: {
totalRaces: 50,
wins: 10,
podiums: 20,
topTen: 30,
dnfs: 5,
other: 15,
},
teamMemberships: [
{
teamId: 'team-1',
teamName: 'Test Team',
teamTag: 'TT',
role: 'driver',
joinedAt: '2024-01-01',
isCurrent: true,
},
],
socialSummary: {
friendsCount: 10,
friends: [
{
id: 'friend-1',
name: 'Friend 1',
country: 'US',
avatarUrl: 'avatar-url',
},
],
},
extendedProfile: {
socialHandles: [
{
platform: 'twitter',
handle: '@test',
url: 'https://twitter.com/test',
},
],
achievements: [
{
id: 'ach-1',
title: 'Achievement',
description: 'Test achievement',
icon: 'trophy',
rarity: 'rare',
earnedAt: '2024-01-01',
},
],
racingStyle: 'Aggressive',
favoriteTrack: 'Test Track',
favoriteCar: 'Test Car',
timezone: 'UTC',
availableHours: 10,
lookingForTeam: true,
openToRequests: true,
},
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.id).toBe(profileDto.currentDriver?.id);
expect(result.driver.name).toBe(profileDto.currentDriver?.name);
expect(result.driver.countryCode).toBe(profileDto.currentDriver?.country);
expect(result.driver.bio).toBe(profileDto.currentDriver?.bio);
expect(result.driver.iracingId).toBe(String(profileDto.currentDriver?.iracingId));
expect(result.stats?.totalRacesLabel).toBe('50');
expect(result.stats?.winsLabel).toBe('10');
expect(result.teamMemberships).toHaveLength(1);
expect(result.extendedProfile?.socialHandles).toHaveLength(1);
expect(result.extendedProfile?.achievements).toHaveLength(1);
});
it('should not modify the input DTO', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: 'Test bio',
iracingId: 12345,
joinedAt: '2024-01-01',
globalRank: 100,
},
stats: {
totalRaces: 50,
wins: 10,
podiums: 20,
dnfs: 5,
avgFinish: 5.5,
bestFinish: 1,
worstFinish: 20,
finishRate: 90,
winRate: 20,
podiumRate: 40,
percentile: 95,
rating: 1500,
consistency: 85,
overallRank: 100,
},
finishDistribution: {
totalRaces: 50,
wins: 10,
podiums: 20,
topTen: 30,
dnfs: 5,
other: 15,
},
teamMemberships: [
{
teamId: 'team-1',
teamName: 'Test Team',
teamTag: 'TT',
role: 'driver',
joinedAt: '2024-01-01',
isCurrent: true,
},
],
socialSummary: {
friendsCount: 10,
friends: [
{
id: 'friend-1',
name: 'Friend 1',
country: 'US',
avatarUrl: 'avatar-url',
},
],
},
extendedProfile: {
socialHandles: [
{
platform: 'twitter',
handle: '@test',
url: 'https://twitter.com/test',
},
],
achievements: [
{
id: 'ach-1',
title: 'Achievement',
description: 'Test achievement',
icon: 'trophy',
rarity: 'rare',
earnedAt: '2024-01-01',
},
],
racingStyle: 'Aggressive',
favoriteTrack: 'Test Track',
favoriteCar: 'Test Car',
timezone: 'UTC',
availableHours: 10,
lookingForTeam: true,
openToRequests: true,
},
};
const originalDto = { ...profileDto };
ProfileViewDataBuilder.build(profileDto);
expect(profileDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle driver without avatar', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: null,
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.avatarUrl).toContain('default');
});
it('should handle driver without iracingId', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.iracingId).toBeNull();
});
it('should handle driver without global rank', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.driver.globalRankLabel).toBe('—');
});
it('should handle empty team memberships', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: null,
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.teamMemberships).toHaveLength(0);
});
it('should handle empty friends list', () => {
const profileDto: GetDriverProfileOutputDTO = {
currentDriver: {
id: 'driver-123',
name: 'Test Driver',
country: 'US',
avatarUrl: 'avatar-url',
bio: null,
iracingId: null,
joinedAt: '2024-01-01',
globalRank: null,
},
stats: null,
finishDistribution: null,
teamMemberships: [],
socialSummary: {
friendsCount: 0,
friends: [],
},
extendedProfile: {
socialHandles: [],
achievements: [],
racingStyle: null,
favoriteTrack: null,
favoriteCar: null,
timezone: null,
availableHours: null,
lookingForTeam: false,
openToRequests: false,
},
};
const result = ProfileViewDataBuilder.build(profileDto);
expect(result.extendedProfile?.friends).toHaveLength(0);
expect(result.extendedProfile?.friendsCountLabel).toBe('0');
});
});
});

View File

@@ -0,0 +1,319 @@
import { describe, it, expect } from 'vitest';
import { ProtestDetailViewDataBuilder } from './ProtestDetailViewDataBuilder';
describe('ProtestDetailViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform ProtestDetailApiDto to ProtestDetailViewData correctly', () => {
const protestDetailApiDto = {
id: 'protest-123',
leagueId: 'league-456',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [
{
type: 'time_penalty',
label: 'Time Penalty',
description: 'Add time to race result',
},
],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result).toEqual({
protestId: 'protest-123',
leagueId: 'league-456',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [
{
type: 'time_penalty',
label: 'Time Penalty',
description: 'Add time to race result',
},
],
});
});
it('should handle resolved status', () => {
const protestDetailApiDto = {
id: 'protest-456',
leagueId: 'league-789',
status: 'resolved',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
protestingDriver: {
id: 'driver-3',
name: 'Driver 3',
},
accusedDriver: {
id: 'driver-4',
name: 'Driver 4',
},
race: {
id: 'race-2',
name: 'Test Race 2',
scheduledAt: '2024-01-02T10:00:00Z',
},
penaltyTypes: [],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.status).toBe('resolved');
});
it('should handle multiple penalty types', () => {
const protestDetailApiDto = {
id: 'protest-789',
leagueId: 'league-101',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 15,
description: 'Contact at turn 7',
},
protestingDriver: {
id: 'driver-5',
name: 'Driver 5',
},
accusedDriver: {
id: 'driver-6',
name: 'Driver 6',
},
race: {
id: 'race-3',
name: 'Test Race 3',
scheduledAt: '2024-01-03T10:00:00Z',
},
penaltyTypes: [
{
type: 'time_penalty',
label: 'Time Penalty',
description: 'Add time to race result',
},
{
type: 'grid_penalty',
label: 'Grid Penalty',
description: 'Drop grid positions',
},
],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.penaltyTypes).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const protestDetailApiDto = {
id: 'protest-101',
leagueId: 'league-102',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [
{
type: 'time_penalty',
label: 'Time Penalty',
description: 'Add time to race result',
},
],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.protestId).toBe(protestDetailApiDto.id);
expect(result.leagueId).toBe(protestDetailApiDto.leagueId);
expect(result.status).toBe(protestDetailApiDto.status);
expect(result.submittedAt).toBe(protestDetailApiDto.submittedAt);
expect(result.incident).toEqual(protestDetailApiDto.incident);
expect(result.protestingDriver).toEqual(protestDetailApiDto.protestingDriver);
expect(result.accusedDriver).toEqual(protestDetailApiDto.accusedDriver);
expect(result.race).toEqual(protestDetailApiDto.race);
expect(result.penaltyTypes).toEqual(protestDetailApiDto.penaltyTypes);
});
it('should not modify the input DTO', () => {
const protestDetailApiDto = {
id: 'protest-102',
leagueId: 'league-103',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [],
};
const originalDto = { ...protestDetailApiDto };
ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(protestDetailApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle different status values', () => {
const protestDetailApiDto = {
id: 'protest-103',
leagueId: 'league-104',
status: 'rejected',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.status).toBe('rejected');
});
it('should handle lap 0', () => {
const protestDetailApiDto = {
id: 'protest-104',
leagueId: 'league-105',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 0,
description: 'Contact at start',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.incident.lap).toBe(0);
});
it('should handle empty description', () => {
const protestDetailApiDto = {
id: 'protest-105',
leagueId: 'league-106',
status: 'pending',
submittedAt: '2024-01-01T10:00:00Z',
incident: {
lap: 5,
description: '',
},
protestingDriver: {
id: 'driver-1',
name: 'Driver 1',
},
accusedDriver: {
id: 'driver-2',
name: 'Driver 2',
},
race: {
id: 'race-1',
name: 'Test Race',
scheduledAt: '2024-01-01T10:00:00Z',
},
penaltyTypes: [],
};
const result = ProtestDetailViewDataBuilder.build(protestDetailApiDto);
expect(result.incident.description).toBe('');
});
});
});

View File

@@ -0,0 +1,393 @@
import { describe, it, expect } from 'vitest';
import { RaceDetailViewDataBuilder } from './RaceDetailViewDataBuilder';
describe('RaceDetailViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform API DTO to RaceDetailViewData correctly', () => {
const apiDto = {
race: {
id: 'race-123',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
league: {
id: 'league-456',
name: 'Test League',
description: 'Test Description',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Open',
},
},
entryList: [
{
id: 'driver-1',
name: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
rating: 1500,
isCurrentUser: false,
},
],
registration: {
isUserRegistered: false,
canRegister: true,
},
userResult: {
position: 5,
startPosition: 10,
positionChange: 5,
incidents: 2,
isClean: false,
isPodium: false,
ratingChange: 10,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result).toEqual({
race: {
id: 'race-123',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
league: {
id: 'league-456',
name: 'Test League',
description: 'Test Description',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Open',
},
},
entryList: [
{
id: 'driver-1',
name: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
rating: 1500,
isCurrentUser: false,
},
],
registration: {
isUserRegistered: false,
canRegister: true,
},
userResult: {
position: 5,
startPosition: 10,
positionChange: 5,
incidents: 2,
isClean: false,
isPodium: false,
ratingChange: 10,
},
canReopenRace: false,
});
});
it('should handle race without league', () => {
const apiDto = {
race: {
id: 'race-456',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.league).toBeUndefined();
});
it('should handle race without user result', () => {
const apiDto = {
race: {
id: 'race-789',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.userResult).toBeUndefined();
});
it('should handle multiple entries in entry list', () => {
const apiDto = {
race: {
id: 'race-101',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
entryList: [
{
id: 'driver-1',
name: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
rating: 1500,
isCurrentUser: false,
},
{
id: 'driver-2',
name: 'Driver 2',
avatarUrl: 'avatar-url',
country: 'UK',
rating: 1600,
isCurrentUser: true,
},
],
registration: {
isUserRegistered: true,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.entryList).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const apiDto = {
race: {
id: 'race-102',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
league: {
id: 'league-103',
name: 'Test League',
description: 'Test Description',
settings: {
maxDrivers: 32,
qualifyingFormat: 'Open',
},
},
entryList: [
{
id: 'driver-1',
name: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
rating: 1500,
isCurrentUser: false,
},
],
registration: {
isUserRegistered: false,
canRegister: true,
},
userResult: {
position: 5,
startPosition: 10,
positionChange: 5,
incidents: 2,
isClean: false,
isPodium: false,
ratingChange: 10,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.race.id).toBe(apiDto.race.id);
expect(result.race.track).toBe(apiDto.race.track);
expect(result.race.car).toBe(apiDto.race.car);
expect(result.race.scheduledAt).toBe(apiDto.race.scheduledAt);
expect(result.race.status).toBe(apiDto.race.status);
expect(result.race.sessionType).toBe(apiDto.race.sessionType);
expect(result.league?.id).toBe(apiDto.league.id);
expect(result.league?.name).toBe(apiDto.league.name);
expect(result.registration.isUserRegistered).toBe(apiDto.registration.isUserRegistered);
expect(result.registration.canRegister).toBe(apiDto.registration.canRegister);
expect(result.canReopenRace).toBe(apiDto.canReopenRace);
});
it('should not modify the input DTO', () => {
const apiDto = {
race: {
id: 'race-104',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const originalDto = { ...apiDto };
RaceDetailViewDataBuilder.build(apiDto);
expect(apiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle null API DTO', () => {
const result = RaceDetailViewDataBuilder.build(null);
expect(result.race.id).toBe('');
expect(result.race.track).toBe('');
expect(result.race.car).toBe('');
expect(result.race.scheduledAt).toBe('');
expect(result.race.status).toBe('scheduled');
expect(result.race.sessionType).toBe('race');
expect(result.entryList).toHaveLength(0);
expect(result.registration.isUserRegistered).toBe(false);
expect(result.registration.canRegister).toBe(false);
expect(result.canReopenRace).toBe(false);
});
it('should handle undefined API DTO', () => {
const result = RaceDetailViewDataBuilder.build(undefined);
expect(result.race.id).toBe('');
expect(result.race.track).toBe('');
expect(result.race.car).toBe('');
expect(result.race.scheduledAt).toBe('');
expect(result.race.status).toBe('scheduled');
expect(result.race.sessionType).toBe('race');
expect(result.entryList).toHaveLength(0);
expect(result.registration.isUserRegistered).toBe(false);
expect(result.registration.canRegister).toBe(false);
expect(result.canReopenRace).toBe(false);
});
it('should handle race without entry list', () => {
const apiDto = {
race: {
id: 'race-105',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'race',
},
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.entryList).toHaveLength(0);
});
it('should handle different race statuses', () => {
const apiDto = {
race: {
id: 'race-106',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'running',
sessionType: 'race',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.race.status).toBe('running');
});
it('should handle different session types', () => {
const apiDto = {
race: {
id: 'race-107',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'scheduled',
sessionType: 'qualifying',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: false,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.race.sessionType).toBe('qualifying');
});
it('should handle canReopenRace true', () => {
const apiDto = {
race: {
id: 'race-108',
track: 'Test Track',
car: 'Test Car',
scheduledAt: '2024-01-01T10:00:00Z',
status: 'completed',
sessionType: 'race',
},
entryList: [],
registration: {
isUserRegistered: false,
canRegister: false,
},
canReopenRace: true,
};
const result = RaceDetailViewDataBuilder.build(apiDto);
expect(result.canReopenRace).toBe(true);
});
});
});

View File

@@ -0,0 +1,775 @@
import { describe, it, expect } from 'vitest';
import { RaceResultsViewDataBuilder } from './RaceResultsViewDataBuilder';
describe('RaceResultsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform API DTO to RaceResultsViewData correctly', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [
{
driverId: 'driver-2',
driverName: 'Driver 2',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
pointsSystem: {
1: 25,
2: 18,
3: 15,
},
fastestLapTime: 120000,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result).toEqual({
raceTrack: 'Test Track',
raceScheduledAt: '2024-01-01T10:00:00Z',
totalDrivers: 20,
leagueName: 'Test League',
raceSOF: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
driverAvatar: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [
{
driverId: 'driver-2',
driverName: 'Driver 2',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
pointsSystem: {
1: 25,
2: 18,
3: 15,
},
fastestLapTime: 120000,
});
});
it('should handle empty results and penalties', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 0,
},
league: {
name: 'Test League',
},
strengthOfField: null,
results: [],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.raceSOF).toBeNull();
});
it('should handle multiple results and penalties', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
{
position: 2,
driverId: 'driver-2',
driverName: 'Driver 2',
avatarUrl: 'avatar-url',
country: 'UK',
car: 'Test Car',
laps: 30,
time: '1:24.000',
fastestLap: '1:21.000',
points: 18,
incidents: 1,
isCurrentUser: true,
},
],
penalties: [
{
driverId: 'driver-3',
driverName: 'Driver 3',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
{
driverId: 'driver-4',
driverName: 'Driver 4',
type: 'grid_penalty',
value: 3,
reason: 'Qualifying infringement',
notes: null,
},
],
pointsSystem: {
1: 25,
2: 18,
3: 15,
},
fastestLapTime: 120000,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results).toHaveLength(2);
expect(result.penalties).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {
1: 25,
2: 18,
3: 15,
},
fastestLapTime: 120000,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.raceTrack).toBe(apiDto.race.track);
expect(result.raceScheduledAt).toBe(apiDto.race.scheduledAt);
expect(result.totalDrivers).toBe(apiDto.stats.totalDrivers);
expect(result.leagueName).toBe(apiDto.league.name);
expect(result.raceSOF).toBe(apiDto.strengthOfField);
expect(result.pointsSystem).toEqual(apiDto.pointsSystem);
expect(result.fastestLapTime).toBe(apiDto.fastestLapTime);
});
it('should not modify the input DTO', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const originalDto = { ...apiDto };
RaceResultsViewDataBuilder.build(apiDto);
expect(apiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle null API DTO', () => {
const result = RaceResultsViewDataBuilder.build(null);
expect(result.raceSOF).toBeNull();
expect(result.results).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.pointsSystem).toEqual({});
expect(result.fastestLapTime).toBe(0);
});
it('should handle undefined API DTO', () => {
const result = RaceResultsViewDataBuilder.build(undefined);
expect(result.raceSOF).toBeNull();
expect(result.results).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.pointsSystem).toEqual({});
expect(result.fastestLapTime).toBe(0);
});
it('should handle results without country', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: null,
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].country).toBe('US');
});
it('should handle results without car', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: null,
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].car).toBe('Unknown');
});
it('should handle results without laps', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: null,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].laps).toBe(0);
});
it('should handle results without time', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: null,
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].time).toBe('0:00.00');
});
it('should handle results without fastest lap', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: null,
points: 25,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].fastestLap).toBe('0.00');
});
it('should handle results without points', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: null,
incidents: 0,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].points).toBe(0);
});
it('should handle results without incidents', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: null,
isCurrentUser: false,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].incidents).toBe(0);
});
it('should handle results without isCurrentUser', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [
{
position: 1,
driverId: 'driver-1',
driverName: 'Driver 1',
avatarUrl: 'avatar-url',
country: 'US',
car: 'Test Car',
laps: 30,
time: '1:23.456',
fastestLap: '1:20.000',
points: 25,
incidents: 0,
isCurrentUser: null,
},
],
penalties: [],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.results[0].isCurrentUser).toBe(false);
});
it('should handle penalties without driver name', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [],
penalties: [
{
driverId: 'driver-1',
driverName: null,
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.penalties[0].driverName).toBe('Unknown');
});
it('should handle penalties without value', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [],
penalties: [
{
driverId: 'driver-1',
driverName: 'Driver 1',
type: 'time_penalty',
value: null,
reason: 'Track limits',
notes: 'Warning issued',
},
],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.penalties[0].value).toBe(0);
});
it('should handle penalties without reason', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [],
penalties: [
{
driverId: 'driver-1',
driverName: 'Driver 1',
type: 'time_penalty',
value: 5,
reason: null,
notes: 'Warning issued',
},
],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.penalties[0].reason).toBe('Penalty applied');
});
it('should handle different penalty types', () => {
const apiDto = {
race: {
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
stats: {
totalDrivers: 20,
},
league: {
name: 'Test League',
},
strengthOfField: 1500,
results: [],
penalties: [
{
driverId: 'driver-1',
driverName: 'Driver 1',
type: 'grid_penalty',
value: 3,
reason: 'Qualifying infringement',
notes: null,
},
{
driverId: 'driver-2',
driverName: 'Driver 2',
type: 'points_deduction',
value: 10,
reason: 'Dangerous driving',
notes: null,
},
{
driverId: 'driver-3',
driverName: 'Driver 3',
type: 'disqualification',
value: 0,
reason: 'Technical infringement',
notes: null,
},
{
driverId: 'driver-4',
driverName: 'Driver 4',
type: 'warning',
value: 0,
reason: 'Minor infraction',
notes: null,
},
{
driverId: 'driver-5',
driverName: 'Driver 5',
type: 'license_points',
value: 2,
reason: 'Multiple incidents',
notes: null,
},
],
pointsSystem: {},
fastestLapTime: 0,
};
const result = RaceResultsViewDataBuilder.build(apiDto);
expect(result.penalties[0].type).toBe('grid_penalty');
expect(result.penalties[1].type).toBe('points_deduction');
expect(result.penalties[2].type).toBe('disqualification');
expect(result.penalties[3].type).toBe('warning');
expect(result.penalties[4].type).toBe('license_points');
});
});
});

View File

@@ -0,0 +1,841 @@
import { describe, it, expect } from 'vitest';
import { RaceStewardingViewDataBuilder } from './RaceStewardingViewDataBuilder';
describe('RaceStewardingViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform API DTO to RaceStewardingViewData correctly', () => {
const apiDto = {
race: {
id: 'race-123',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-456',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-4',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: 'Penalty applied',
},
],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-5',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
},
pendingCount: 1,
resolvedCount: 1,
penaltiesCount: 1,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result).toEqual({
race: {
id: 'race-123',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-456',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-4',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: 'Penalty applied',
},
],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-5',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
},
pendingCount: 1,
resolvedCount: 1,
penaltiesCount: 1,
});
});
it('should handle empty protests and penalties', () => {
const apiDto = {
race: {
id: 'race-456',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-789',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [],
driverMap: {},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingProtests).toHaveLength(0);
expect(result.resolvedProtests).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.pendingCount).toBe(0);
expect(result.resolvedCount).toBe(0);
expect(result.penaltiesCount).toBe(0);
});
it('should handle multiple protests and penalties', () => {
const apiDto = {
race: {
id: 'race-789',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-101',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-4',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [
{
id: 'protest-3',
protestingDriverId: 'driver-5',
accusedDriverId: 'driver-6',
incident: {
lap: 15,
description: 'Contact at turn 7',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: 'Penalty applied',
},
],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-7',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
{
id: 'penalty-2',
driverId: 'driver-8',
type: 'grid_penalty',
value: 3,
reason: 'Qualifying infringement',
notes: null,
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
'driver-6': { id: 'driver-6', name: 'Driver 6' },
'driver-7': { id: 'driver-7', name: 'Driver 7' },
'driver-8': { id: 'driver-8', name: 'Driver 8' },
},
pendingCount: 2,
resolvedCount: 1,
penaltiesCount: 2,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingProtests).toHaveLength(2);
expect(result.resolvedProtests).toHaveLength(1);
expect(result.penalties).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const apiDto = {
race: {
id: 'race-102',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-103',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [],
penalties: [],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
},
pendingCount: 1,
resolvedCount: 0,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.race?.id).toBe(apiDto.race.id);
expect(result.race?.track).toBe(apiDto.race.track);
expect(result.race?.scheduledAt).toBe(apiDto.race.scheduledAt);
expect(result.league?.id).toBe(apiDto.league.id);
expect(result.pendingCount).toBe(apiDto.pendingCount);
expect(result.resolvedCount).toBe(apiDto.resolvedCount);
expect(result.penaltiesCount).toBe(apiDto.penaltiesCount);
});
it('should not modify the input DTO', () => {
const apiDto = {
race: {
id: 'race-104',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-105',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [],
driverMap: {},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
const originalDto = { ...apiDto };
RaceStewardingViewDataBuilder.build(apiDto);
expect(apiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle null API DTO', () => {
const result = RaceStewardingViewDataBuilder.build(null);
expect(result.race).toBeNull();
expect(result.league).toBeNull();
expect(result.pendingProtests).toHaveLength(0);
expect(result.resolvedProtests).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.driverMap).toEqual({});
expect(result.pendingCount).toBe(0);
expect(result.resolvedCount).toBe(0);
expect(result.penaltiesCount).toBe(0);
});
it('should handle undefined API DTO', () => {
const result = RaceStewardingViewDataBuilder.build(undefined);
expect(result.race).toBeNull();
expect(result.league).toBeNull();
expect(result.pendingProtests).toHaveLength(0);
expect(result.resolvedProtests).toHaveLength(0);
expect(result.penalties).toHaveLength(0);
expect(result.driverMap).toEqual({});
expect(result.pendingCount).toBe(0);
expect(result.resolvedCount).toBe(0);
expect(result.penaltiesCount).toBe(0);
});
it('should handle race without league', () => {
const apiDto = {
race: {
id: 'race-106',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [],
driverMap: {},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.league).toBeNull();
});
it('should handle protests without proof video', () => {
const apiDto = {
race: {
id: 'race-107',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-108',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: null,
decisionNotes: null,
},
],
resolvedProtests: [],
penalties: [],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
},
pendingCount: 1,
resolvedCount: 0,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingProtests[0].proofVideoUrl).toBeNull();
});
it('should handle protests without decision notes', () => {
const apiDto = {
race: {
id: 'race-109',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-110',
},
pendingProtests: [],
resolvedProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
penalties: [],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
},
pendingCount: 0,
resolvedCount: 1,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.resolvedProtests[0].decisionNotes).toBeNull();
});
it('should handle penalties without notes', () => {
const apiDto = {
race: {
id: 'race-111',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-112',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: null,
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 1,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.penalties[0].notes).toBeNull();
});
it('should handle penalties without value', () => {
const apiDto = {
race: {
id: 'race-113',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-114',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'disqualification',
value: null,
reason: 'Technical infringement',
notes: null,
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 1,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.penalties[0].value).toBe(0);
});
it('should handle penalties without reason', () => {
const apiDto = {
race: {
id: 'race-115',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-116',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'warning',
value: 0,
reason: null,
notes: null,
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 1,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.penalties[0].reason).toBe('');
});
it('should handle different protest statuses', () => {
const apiDto = {
race: {
id: 'race-117',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-118',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-4',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: 'Penalty applied',
},
{
id: 'protest-3',
protestingDriverId: 'driver-5',
accusedDriverId: 'driver-6',
incident: {
lap: 15,
description: 'Contact at turn 7',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'rejected',
proofVideoUrl: 'video-url',
decisionNotes: 'Insufficient evidence',
},
],
penalties: [],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
'driver-6': { id: 'driver-6', name: 'Driver 6' },
},
pendingCount: 1,
resolvedCount: 2,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingProtests[0].status).toBe('pending');
expect(result.resolvedProtests[0].status).toBe('resolved');
expect(result.resolvedProtests[1].status).toBe('rejected');
});
it('should handle different penalty types', () => {
const apiDto = {
race: {
id: 'race-119',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-120',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-1',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
{
id: 'penalty-2',
driverId: 'driver-2',
type: 'grid_penalty',
value: 3,
reason: 'Qualifying infringement',
notes: null,
},
{
id: 'penalty-3',
driverId: 'driver-3',
type: 'points_deduction',
value: 10,
reason: 'Dangerous driving',
notes: null,
},
{
id: 'penalty-4',
driverId: 'driver-4',
type: 'disqualification',
value: 0,
reason: 'Technical infringement',
notes: null,
},
{
id: 'penalty-5',
driverId: 'driver-5',
type: 'warning',
value: 0,
reason: 'Minor infraction',
notes: null,
},
{
id: 'penalty-6',
driverId: 'driver-6',
type: 'license_points',
value: 2,
reason: 'Multiple incidents',
notes: null,
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
'driver-6': { id: 'driver-6', name: 'Driver 6' },
},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 6,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.penalties[0].type).toBe('time_penalty');
expect(result.penalties[1].type).toBe('grid_penalty');
expect(result.penalties[2].type).toBe('points_deduction');
expect(result.penalties[3].type).toBe('disqualification');
expect(result.penalties[4].type).toBe('warning');
expect(result.penalties[5].type).toBe('license_points');
});
it('should handle empty driver map', () => {
const apiDto = {
race: {
id: 'race-121',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-122',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [],
driverMap: {},
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.driverMap).toEqual({});
});
it('should handle count values from DTO', () => {
const apiDto = {
race: {
id: 'race-123',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-124',
},
pendingProtests: [],
resolvedProtests: [],
penalties: [],
driverMap: {},
pendingCount: 5,
resolvedCount: 10,
penaltiesCount: 3,
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingCount).toBe(5);
expect(result.resolvedCount).toBe(10);
expect(result.penaltiesCount).toBe(3);
});
it('should calculate counts from arrays when not provided', () => {
const apiDto = {
race: {
id: 'race-125',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
},
league: {
id: 'league-126',
},
pendingProtests: [
{
id: 'protest-1',
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: 5,
description: 'Contact at turn 3',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'pending',
proofVideoUrl: 'video-url',
decisionNotes: null,
},
],
resolvedProtests: [
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-4',
incident: {
lap: 10,
description: 'Contact at turn 5',
},
filedAt: '2024-01-01T10:00:00Z',
status: 'resolved',
proofVideoUrl: 'video-url',
decisionNotes: 'Penalty applied',
},
],
penalties: [
{
id: 'penalty-1',
driverId: 'driver-5',
type: 'time_penalty',
value: 5,
reason: 'Track limits',
notes: 'Warning issued',
},
],
driverMap: {
'driver-1': { id: 'driver-1', name: 'Driver 1' },
'driver-2': { id: 'driver-2', name: 'Driver 2' },
'driver-3': { id: 'driver-3', name: 'Driver 3' },
'driver-4': { id: 'driver-4', name: 'Driver 4' },
'driver-5': { id: 'driver-5', name: 'Driver 5' },
},
};
const result = RaceStewardingViewDataBuilder.build(apiDto);
expect(result.pendingCount).toBe(1);
expect(result.resolvedCount).toBe(1);
expect(result.penaltiesCount).toBe(1);
});
});
});

View File

@@ -0,0 +1,407 @@
import { describe, it, expect } from 'vitest';
import { RulebookViewDataBuilder } from './RulebookViewDataBuilder';
import type { RulebookApiDto } from '@/lib/types/tbd/RulebookApiDto';
describe('RulebookViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform RulebookApiDto to RulebookViewData correctly', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-123',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
{ sessionType: 'race', position: 2, points: 18 },
{ sessionType: 'race', position: 3, points: 15 },
],
bonusSummary: [
{ type: 'fastest_lap', points: 5, description: 'Fastest lap' },
],
},
],
dropPolicySummary: 'Drop 2 worst results',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result).toEqual({
leagueId: 'league-123',
gameName: 'iRacing',
scoringPresetName: 'Standard',
championshipsCount: 1,
sessionTypes: 'race',
dropPolicySummary: 'Drop 2 worst results',
hasActiveDropPolicy: true,
positionPoints: [
{ position: 1, points: 25 },
{ position: 2, points: 18 },
{ position: 3, points: 15 },
],
bonusPoints: [
{ type: 'fastest_lap', points: 5, description: 'Fastest lap' },
],
hasBonusPoints: true,
});
});
it('should handle championship without driver type', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-456',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'team',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.positionPoints).toEqual([{ position: 1, points: 25 }]);
});
it('should handle multiple championships', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-789',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
{
type: 'team',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.championshipsCount).toBe(2);
});
it('should handle empty bonus points', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-101',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.bonusPoints).toEqual([]);
expect(result.hasBonusPoints).toBe(false);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-102',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [
{ type: 'fastest_lap', points: 5, description: 'Fastest lap' },
],
},
],
dropPolicySummary: 'Drop 2 worst results',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.leagueId).toBe(rulebookApiDto.leagueId);
expect(result.gameName).toBe(rulebookApiDto.scoringConfig.gameName);
expect(result.scoringPresetName).toBe(rulebookApiDto.scoringConfig.scoringPresetName);
expect(result.dropPolicySummary).toBe(rulebookApiDto.scoringConfig.dropPolicySummary);
});
it('should not modify the input DTO', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-103',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const originalDto = { ...rulebookApiDto };
RulebookViewDataBuilder.build(rulebookApiDto);
expect(rulebookApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle empty drop policy', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-104',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: '',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.hasActiveDropPolicy).toBe(false);
});
it('should handle drop policy with "All" keyword', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-105',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'Drop all results',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.hasActiveDropPolicy).toBe(false);
});
it('should handle multiple session types', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-106',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race', 'qualifying', 'practice'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.sessionTypes).toBe('race, qualifying, practice');
});
it('should handle single session type', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-107',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.sessionTypes).toBe('race');
});
it('should handle empty points preview', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-108',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.positionPoints).toEqual([]);
});
it('should handle points preview with different session types', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-109',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
{ sessionType: 'qualifying', position: 1, points: 10 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.positionPoints).toEqual([{ position: 1, points: 25 }]);
});
it('should handle points preview with non-sequential positions', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-110',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
{ sessionType: 'race', position: 3, points: 15 },
{ sessionType: 'race', position: 2, points: 18 },
],
bonusSummary: [],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.positionPoints).toEqual([
{ position: 1, points: 25 },
{ position: 2, points: 18 },
{ position: 3, points: 15 },
]);
});
it('should handle multiple bonus points', () => {
const rulebookApiDto: RulebookApiDto = {
leagueId: 'league-111',
scoringConfig: {
gameName: 'iRacing',
scoringPresetName: 'Standard',
championships: [
{
type: 'driver',
sessionTypes: ['race'],
pointsPreview: [
{ sessionType: 'race', position: 1, points: 25 },
],
bonusSummary: [
{ type: 'fastest_lap', points: 5, description: 'Fastest lap' },
{ type: 'pole_position', points: 3, description: 'Pole position' },
{ type: 'clean_race', points: 2, description: 'Clean race' },
],
},
],
dropPolicySummary: 'No drops',
},
};
const result = RulebookViewDataBuilder.build(rulebookApiDto);
expect(result.bonusPoints).toHaveLength(3);
expect(result.hasBonusPoints).toBe(true);
});
});
});

View File

@@ -0,0 +1,223 @@
import { describe, it, expect } from 'vitest';
import { SponsorshipRequestsPageViewDataBuilder } from './SponsorshipRequestsPageViewDataBuilder';
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
describe('SponsorshipRequestsPageViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform GetPendingSponsorshipRequestsOutputDTO to SponsorshipRequestsViewData correctly', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-123',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result).toEqual({
sections: [
{
entityType: 'driver',
entityId: 'driver-123',
entityName: 'driver',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogoUrl: 'logo-url',
message: 'Test message',
createdAtIso: '2024-01-01T10:00:00Z',
},
],
},
],
});
});
it('should handle empty requests', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-456',
requests: [],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections).toHaveLength(1);
expect(result.sections[0].requests).toHaveLength(0);
});
it('should handle multiple requests', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'season',
entityId: 'season-789',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Sponsor 1',
sponsorLogo: 'logo-1',
message: 'Message 1',
createdAt: '2024-01-01T10:00:00Z',
},
{
id: 'request-2',
sponsorId: 'sponsor-2',
sponsorName: 'Sponsor 2',
sponsorLogo: 'logo-2',
message: 'Message 2',
createdAt: '2024-01-02T10:00:00Z',
},
],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].requests).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-101',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].entityType).toBe(sponsorshipRequestsPageDto.entityType);
expect(result.sections[0].entityId).toBe(sponsorshipRequestsPageDto.entityId);
expect(result.sections[0].requests[0].id).toBe(sponsorshipRequestsPageDto.requests[0].id);
expect(result.sections[0].requests[0].sponsorId).toBe(sponsorshipRequestsPageDto.requests[0].sponsorId);
expect(result.sections[0].requests[0].sponsorName).toBe(sponsorshipRequestsPageDto.requests[0].sponsorName);
expect(result.sections[0].requests[0].sponsorLogoUrl).toBe(sponsorshipRequestsPageDto.requests[0].sponsorLogo);
expect(result.sections[0].requests[0].message).toBe(sponsorshipRequestsPageDto.requests[0].message);
expect(result.sections[0].requests[0].createdAtIso).toBe(sponsorshipRequestsPageDto.requests[0].createdAt);
});
it('should not modify the input DTO', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-102',
requests: [],
};
const originalDto = { ...sponsorshipRequestsPageDto };
SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(sponsorshipRequestsPageDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle requests without sponsor logo', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-103',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: null,
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].requests[0].sponsorLogoUrl).toBeNull();
});
it('should handle requests without message', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-104',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: null,
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].requests[0].message).toBeNull();
});
it('should handle different entity types', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-105',
requests: [],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].entityType).toBe('team');
});
it('should handle entity name for driver type', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-106',
requests: [],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].entityName).toBe('driver');
});
it('should handle entity name for team type', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-107',
requests: [],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].entityName).toBe('team');
});
it('should handle entity name for season type', () => {
const sponsorshipRequestsPageDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'season',
entityId: 'season-108',
requests: [],
};
const result = SponsorshipRequestsPageViewDataBuilder.build(sponsorshipRequestsPageDto);
expect(result.sections[0].entityName).toBe('season');
});
});
});

View File

@@ -0,0 +1,223 @@
import { describe, it, expect } from 'vitest';
import { SponsorshipRequestsViewDataBuilder } from './SponsorshipRequestsViewDataBuilder';
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
describe('SponsorshipRequestsViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform GetPendingSponsorshipRequestsOutputDTO to SponsorshipRequestsViewData correctly', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-123',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result).toEqual({
sections: [
{
entityType: 'driver',
entityId: 'driver-123',
entityName: 'Driver',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogoUrl: 'logo-url',
message: 'Test message',
createdAtIso: '2024-01-01T10:00:00Z',
},
],
},
],
});
});
it('should handle empty requests', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-456',
requests: [],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections).toHaveLength(1);
expect(result.sections[0].requests).toHaveLength(0);
});
it('should handle multiple requests', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'season',
entityId: 'season-789',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Sponsor 1',
sponsorLogo: 'logo-1',
message: 'Message 1',
createdAt: '2024-01-01T10:00:00Z',
},
{
id: 'request-2',
sponsorId: 'sponsor-2',
sponsorName: 'Sponsor 2',
sponsorLogo: 'logo-2',
message: 'Message 2',
createdAt: '2024-01-02T10:00:00Z',
},
],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].requests).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-101',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].entityType).toBe(sponsorshipRequestsDto.entityType);
expect(result.sections[0].entityId).toBe(sponsorshipRequestsDto.entityId);
expect(result.sections[0].requests[0].id).toBe(sponsorshipRequestsDto.requests[0].id);
expect(result.sections[0].requests[0].sponsorId).toBe(sponsorshipRequestsDto.requests[0].sponsorId);
expect(result.sections[0].requests[0].sponsorName).toBe(sponsorshipRequestsDto.requests[0].sponsorName);
expect(result.sections[0].requests[0].sponsorLogoUrl).toBe(sponsorshipRequestsDto.requests[0].sponsorLogo);
expect(result.sections[0].requests[0].message).toBe(sponsorshipRequestsDto.requests[0].message);
expect(result.sections[0].requests[0].createdAtIso).toBe(sponsorshipRequestsDto.requests[0].createdAt);
});
it('should not modify the input DTO', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-102',
requests: [],
};
const originalDto = { ...sponsorshipRequestsDto };
SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(sponsorshipRequestsDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle requests without sponsor logo', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-103',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: null,
message: 'Test message',
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].requests[0].sponsorLogoUrl).toBeNull();
});
it('should handle requests without message', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-104',
requests: [
{
id: 'request-1',
sponsorId: 'sponsor-1',
sponsorName: 'Test Sponsor',
sponsorLogo: 'logo-url',
message: null,
createdAt: '2024-01-01T10:00:00Z',
},
],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].requests[0].message).toBeNull();
});
it('should handle different entity types', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-105',
requests: [],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].entityType).toBe('team');
});
it('should handle entity name for driver type', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'driver',
entityId: 'driver-106',
requests: [],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].entityName).toBe('Driver');
});
it('should handle entity name for team type', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'team',
entityId: 'team-107',
requests: [],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].entityName).toBe('team');
});
it('should handle entity name for season type', () => {
const sponsorshipRequestsDto: GetPendingSponsorshipRequestsOutputDTO = {
entityType: 'season',
entityId: 'season-108',
requests: [],
};
const result = SponsorshipRequestsViewDataBuilder.build(sponsorshipRequestsDto);
expect(result.sections[0].entityName).toBe('season');
});
});
});

View File

@@ -0,0 +1,349 @@
import { describe, it, expect } from 'vitest';
import { StewardingViewDataBuilder } from './StewardingViewDataBuilder';
import type { StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
describe('StewardingViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform StewardingApiDto to StewardingViewData correctly', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-123',
totalPending: 5,
totalResolved: 10,
totalPenalties: 3,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1', 'protest-2'],
resolvedProtests: ['protest-3'],
penalties: ['penalty-1'],
},
],
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
},
],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result).toEqual({
leagueId: 'league-123',
totalPending: 5,
totalResolved: 10,
totalPenalties: 3,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1', 'protest-2'],
resolvedProtests: ['protest-3'],
penalties: ['penalty-1'],
},
],
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
},
],
});
});
it('should handle empty races and drivers', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-456',
totalPending: 0,
totalResolved: 0,
totalPenalties: 0,
races: [],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races).toHaveLength(0);
expect(result.drivers).toHaveLength(0);
});
it('should handle multiple races and drivers', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-789',
totalPending: 10,
totalResolved: 20,
totalPenalties: 5,
races: [
{
id: 'race-1',
track: 'Test Track 1',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1'],
resolvedProtests: ['protest-2'],
penalties: ['penalty-1'],
},
{
id: 'race-2',
track: 'Test Track 2',
scheduledAt: '2024-01-02T10:00:00Z',
pendingProtests: ['protest-3'],
resolvedProtests: ['protest-4'],
penalties: ['penalty-2'],
},
],
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
},
{
id: 'driver-2',
name: 'Driver 2',
},
],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races).toHaveLength(2);
expect(result.drivers).toHaveLength(2);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-101',
totalPending: 5,
totalResolved: 10,
totalPenalties: 3,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1'],
resolvedProtests: ['protest-2'],
penalties: ['penalty-1'],
},
],
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
},
],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.leagueId).toBe(stewardingApiDto.leagueId);
expect(result.totalPending).toBe(stewardingApiDto.totalPending);
expect(result.totalResolved).toBe(stewardingApiDto.totalResolved);
expect(result.totalPenalties).toBe(stewardingApiDto.totalPenalties);
expect(result.races).toEqual(stewardingApiDto.races);
expect(result.drivers).toEqual(stewardingApiDto.drivers);
});
it('should not modify the input DTO', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-102',
totalPending: 0,
totalResolved: 0,
totalPenalties: 0,
races: [],
drivers: [],
};
const originalDto = { ...stewardingApiDto };
StewardingViewDataBuilder.build(stewardingApiDto);
expect(stewardingApiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle null API DTO', () => {
const result = StewardingViewDataBuilder.build(null);
expect(result.leagueId).toBeUndefined();
expect(result.totalPending).toBe(0);
expect(result.totalResolved).toBe(0);
expect(result.totalPenalties).toBe(0);
expect(result.races).toHaveLength(0);
expect(result.drivers).toHaveLength(0);
});
it('should handle undefined API DTO', () => {
const result = StewardingViewDataBuilder.build(undefined);
expect(result.leagueId).toBeUndefined();
expect(result.totalPending).toBe(0);
expect(result.totalResolved).toBe(0);
expect(result.totalPenalties).toBe(0);
expect(result.races).toHaveLength(0);
expect(result.drivers).toHaveLength(0);
});
it('should handle races without pending protests', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-103',
totalPending: 0,
totalResolved: 5,
totalPenalties: 2,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: [],
resolvedProtests: ['protest-1'],
penalties: ['penalty-1'],
},
],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races[0].pendingProtests).toHaveLength(0);
});
it('should handle races without resolved protests', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-104',
totalPending: 5,
totalResolved: 0,
totalPenalties: 2,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1'],
resolvedProtests: [],
penalties: ['penalty-1'],
},
],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races[0].resolvedProtests).toHaveLength(0);
});
it('should handle races without penalties', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-105',
totalPending: 5,
totalResolved: 10,
totalPenalties: 0,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1'],
resolvedProtests: ['protest-2'],
penalties: [],
},
],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races[0].penalties).toHaveLength(0);
});
it('should handle races with empty arrays', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-106',
totalPending: 0,
totalResolved: 0,
totalPenalties: 0,
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: [],
resolvedProtests: [],
penalties: [],
},
],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.races[0].pendingProtests).toHaveLength(0);
expect(result.races[0].resolvedProtests).toHaveLength(0);
expect(result.races[0].penalties).toHaveLength(0);
});
it('should handle drivers without name', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-107',
totalPending: 0,
totalResolved: 0,
totalPenalties: 0,
races: [],
drivers: [
{
id: 'driver-1',
name: null,
},
],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.drivers[0].name).toBeNull();
});
it('should handle count values from DTO', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-108',
totalPending: 15,
totalResolved: 25,
totalPenalties: 8,
races: [],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.totalPending).toBe(15);
expect(result.totalResolved).toBe(25);
expect(result.totalPenalties).toBe(8);
});
it('should calculate counts from arrays when not provided', () => {
const stewardingApiDto: StewardingApiDto = {
leagueId: 'league-109',
races: [
{
id: 'race-1',
track: 'Test Track',
scheduledAt: '2024-01-01T10:00:00Z',
pendingProtests: ['protest-1', 'protest-2'],
resolvedProtests: ['protest-3', 'protest-4', 'protest-5'],
penalties: ['penalty-1', 'penalty-2'],
},
],
drivers: [],
};
const result = StewardingViewDataBuilder.build(stewardingApiDto);
expect(result.totalPending).toBe(2);
expect(result.totalResolved).toBe(3);
expect(result.totalPenalties).toBe(2);
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,449 @@
import { describe, it, expect } from 'vitest';
import { DriversViewModelBuilder } from './DriversViewModelBuilder';
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
describe('DriversViewModelBuilder', () => {
describe('happy paths', () => {
it('should transform DriversLeaderboardDTO to DriverLeaderboardViewModel correctly', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers).toHaveLength(2);
expect(result.drivers[0].id).toBe('driver-1');
expect(result.drivers[0].name).toBe('Driver 1');
expect(result.drivers[0].country).toBe('US');
expect(result.drivers[0].avatarUrl).toBe('avatar-url');
expect(result.drivers[0].rating).toBe(1500);
expect(result.drivers[0].globalRank).toBe(1);
expect(result.drivers[0].consistency).toBe(95);
expect(result.drivers[1].id).toBe('driver-2');
expect(result.drivers[1].name).toBe('Driver 2');
expect(result.drivers[1].country).toBe('UK');
expect(result.drivers[1].avatarUrl).toBe('avatar-url');
expect(result.drivers[1].rating).toBe(1450);
expect(result.drivers[1].globalRank).toBe(2);
expect(result.drivers[1].consistency).toBe(90);
});
it('should handle empty drivers array', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers).toHaveLength(0);
});
it('should handle single driver', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers).toHaveLength(1);
});
it('should handle multiple drivers', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
{
id: 'driver-3',
name: 'Driver 3',
country: 'DE',
avatarUrl: 'avatar-url',
rating: 1400,
globalRank: 3,
consistency: 85,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers).toHaveLength(3);
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].id).toBe(driversLeaderboardDto.drivers[0].id);
expect(result.drivers[0].name).toBe(driversLeaderboardDto.drivers[0].name);
expect(result.drivers[0].country).toBe(driversLeaderboardDto.drivers[0].country);
expect(result.drivers[0].avatarUrl).toBe(driversLeaderboardDto.drivers[0].avatarUrl);
expect(result.drivers[0].rating).toBe(driversLeaderboardDto.drivers[0].rating);
expect(result.drivers[0].globalRank).toBe(driversLeaderboardDto.drivers[0].globalRank);
expect(result.drivers[0].consistency).toBe(driversLeaderboardDto.drivers[0].consistency);
});
it('should not modify the input DTO', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
],
};
const originalDto = { ...driversLeaderboardDto };
DriversViewModelBuilder.build(driversLeaderboardDto);
expect(driversLeaderboardDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle driver without avatar', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: null,
rating: 1500,
globalRank: 1,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].avatarUrl).toBeNull();
});
it('should handle driver without country', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: null,
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].country).toBeNull();
});
it('should handle driver without rating', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: null,
globalRank: 1,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].rating).toBeNull();
});
it('should handle driver without global rank', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: null,
consistency: 95,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].globalRank).toBeNull();
});
it('should handle driver without consistency', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: null,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].consistency).toBeNull();
});
it('should handle different countries', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
{
id: 'driver-3',
name: 'Driver 3',
country: 'DE',
avatarUrl: 'avatar-url',
rating: 1400,
globalRank: 3,
consistency: 85,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].country).toBe('US');
expect(result.drivers[1].country).toBe('UK');
expect(result.drivers[2].country).toBe('DE');
});
it('should handle different ratings', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
{
id: 'driver-3',
name: 'Driver 3',
country: 'DE',
avatarUrl: 'avatar-url',
rating: 1400,
globalRank: 3,
consistency: 85,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].rating).toBe(1500);
expect(result.drivers[1].rating).toBe(1450);
expect(result.drivers[2].rating).toBe(1400);
});
it('should handle different global ranks', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
{
id: 'driver-3',
name: 'Driver 3',
country: 'DE',
avatarUrl: 'avatar-url',
rating: 1400,
globalRank: 3,
consistency: 85,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].globalRank).toBe(1);
expect(result.drivers[1].globalRank).toBe(2);
expect(result.drivers[2].globalRank).toBe(3);
});
it('should handle different consistency values', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: [
{
id: 'driver-1',
name: 'Driver 1',
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500,
globalRank: 1,
consistency: 95,
},
{
id: 'driver-2',
name: 'Driver 2',
country: 'UK',
avatarUrl: 'avatar-url',
rating: 1450,
globalRank: 2,
consistency: 90,
},
{
id: 'driver-3',
name: 'Driver 3',
country: 'DE',
avatarUrl: 'avatar-url',
rating: 1400,
globalRank: 3,
consistency: 85,
},
],
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers[0].consistency).toBe(95);
expect(result.drivers[1].consistency).toBe(90);
expect(result.drivers[2].consistency).toBe(85);
});
it('should handle large number of drivers', () => {
const driversLeaderboardDto: DriversLeaderboardDTO = {
drivers: Array.from({ length: 100 }, (_, i) => ({
id: `driver-${i + 1}`,
name: `Driver ${i + 1}`,
country: 'US',
avatarUrl: 'avatar-url',
rating: 1500 - i,
globalRank: i + 1,
consistency: 95 - i * 0.1,
})),
};
const result = DriversViewModelBuilder.build(driversLeaderboardDto);
expect(result.drivers).toHaveLength(100);
expect(result.drivers[0].id).toBe('driver-1');
expect(result.drivers[99].id).toBe('driver-100');
});
});
});