view data tests
This commit is contained in:
@@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
167
apps/website/lib/builders/view-data/HomeViewDataBuilder.test.ts
Normal file
167
apps/website/lib/builders/view-data/HomeViewDataBuilder.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user