This commit is contained in:
2025-12-17 00:33:13 +01:00
parent 8c67081953
commit f01e01e50c
186 changed files with 9242 additions and 1342 deletions

View File

@@ -0,0 +1,105 @@
import { describe, it, expect } from 'vitest';
import { ChampionshipStanding } from './ChampionshipStanding';
import type { ParticipantRef } from '../../types/ParticipantRef';
describe('ChampionshipStanding', () => {
const participant: ParticipantRef = { type: 'driver', id: 'driver1' };
it('should create a championship standing', () => {
const standing = ChampionshipStanding.create({
seasonId: 'season1',
championshipId: 'champ1',
participant,
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
});
expect(standing.id).toBe('season1-champ1-driver1');
expect(standing.seasonId).toBe('season1');
expect(standing.championshipId).toBe('champ1');
expect(standing.participant).toBe(participant);
expect(standing.totalPoints.toNumber()).toBe(100);
expect(standing.resultsCounted.toNumber()).toBe(5);
expect(standing.resultsDropped.toNumber()).toBe(1);
expect(standing.position.toNumber()).toBe(1);
});
it('should update position', () => {
const standing = ChampionshipStanding.create({
seasonId: 'season1',
championshipId: 'champ1',
participant,
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
});
const updated = standing.withPosition(2);
expect(updated.position.toNumber()).toBe(2);
expect(updated.id).toBe('season1-champ1-driver1');
expect(updated.totalPoints.toNumber()).toBe(100);
});
it('should throw on invalid seasonId', () => {
expect(() => ChampionshipStanding.create({
seasonId: '',
championshipId: 'champ1',
participant,
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
})).toThrow('Season ID is required');
});
it('should throw on invalid championshipId', () => {
expect(() => ChampionshipStanding.create({
seasonId: 'season1',
championshipId: '',
participant,
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
})).toThrow('Championship ID is required');
});
it('should throw on invalid participant', () => {
expect(() => ChampionshipStanding.create({
seasonId: 'season1',
championshipId: 'champ1',
participant: { type: 'driver', id: '' },
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
})).toThrow('Participant is required');
});
it('should throw on negative points', () => {
expect(() => ChampionshipStanding.create({
seasonId: 'season1',
championshipId: 'champ1',
participant,
totalPoints: -1,
resultsCounted: 5,
resultsDropped: 1,
position: 1,
})).toThrow('Points cannot be negative');
});
it('should throw on invalid position', () => {
expect(() => ChampionshipStanding.create({
seasonId: 'season1',
championshipId: 'champ1',
participant,
totalPoints: 100,
resultsCounted: 5,
resultsDropped: 1,
position: 0,
})).toThrow('Position must be a positive integer');
});
});

View File

@@ -0,0 +1,69 @@
import type { IEntity } from '@core/shared/domain';
import { RacingDomainValidationError } from '../../errors/RacingDomainError';
import type { ParticipantRef } from '../../types/ParticipantRef';
import { Points } from '../../value-objects/Points';
import { Position } from './Position';
import { ResultsCount } from './ResultsCount';
export class ChampionshipStanding implements IEntity<string> {
readonly id: string;
readonly seasonId: string;
readonly championshipId: string;
readonly participant: ParticipantRef;
readonly totalPoints: Points;
readonly resultsCounted: ResultsCount;
readonly resultsDropped: ResultsCount;
readonly position: Position;
private constructor(props: {
seasonId: string;
championshipId: string;
participant: ParticipantRef;
totalPoints: number;
resultsCounted: number;
resultsDropped: number;
position: number;
}) {
this.seasonId = props.seasonId;
this.championshipId = props.championshipId;
this.participant = props.participant;
this.totalPoints = Points.create(props.totalPoints);
this.resultsCounted = ResultsCount.create(props.resultsCounted);
this.resultsDropped = ResultsCount.create(props.resultsDropped);
this.position = Position.create(props.position);
this.id = `${this.seasonId}-${this.championshipId}-${this.participant.id}`;
}
static create(props: {
seasonId: string;
championshipId: string;
participant: ParticipantRef;
totalPoints: number;
resultsCounted: number;
resultsDropped: number;
position: number;
}): ChampionshipStanding {
if (!props.seasonId || props.seasonId.trim().length === 0) {
throw new RacingDomainValidationError('Season ID is required');
}
if (!props.championshipId || props.championshipId.trim().length === 0) {
throw new RacingDomainValidationError('Championship ID is required');
}
if (!props.participant || !props.participant.id || props.participant.id.trim().length === 0) {
throw new RacingDomainValidationError('Participant is required');
}
return new ChampionshipStanding(props);
}
withPosition(position: number): ChampionshipStanding {
return ChampionshipStanding.create({
seasonId: this.seasonId,
championshipId: this.championshipId,
participant: this.participant,
totalPoints: this.totalPoints.toNumber(),
resultsCounted: this.resultsCounted.toNumber(),
resultsDropped: this.resultsDropped.toNumber(),
position,
});
}
}

View File

@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { Position } from './Position';
describe('Position', () => {
it('should create position', () => {
const position = Position.create(1);
expect(position.toNumber()).toBe(1);
});
it('should not create zero position', () => {
expect(() => Position.create(0)).toThrow('Position must be a positive integer');
});
it('should not create negative position', () => {
expect(() => Position.create(-1)).toThrow('Position must be a positive integer');
});
it('should not create non-integer position', () => {
expect(() => Position.create(1.5)).toThrow('Position must be a positive integer');
});
it('should equal same position', () => {
const p1 = Position.create(2);
const p2 = Position.create(2);
expect(p1.equals(p2)).toBe(true);
});
it('should not equal different position', () => {
const p1 = Position.create(2);
const p2 = Position.create(3);
expect(p1.equals(p2)).toBe(false);
});
});

View File

@@ -0,0 +1,20 @@
import { RacingDomainValidationError } from '../../errors/RacingDomainError';
export class Position {
private constructor(private readonly value: number) {}
static create(value: number): Position {
if (!Number.isInteger(value) || value <= 0) {
throw new RacingDomainValidationError('Position must be a positive integer');
}
return new Position(value);
}
toNumber(): number {
return this.value;
}
equals(other: Position): boolean {
return this.value === other.value;
}
}

View File

@@ -0,0 +1,34 @@
import { describe, it, expect } from 'vitest';
import { ResultsCount } from './ResultsCount';
describe('ResultsCount', () => {
it('should create results count', () => {
const count = ResultsCount.create(5);
expect(count.toNumber()).toBe(5);
});
it('should create zero results count', () => {
const count = ResultsCount.create(0);
expect(count.toNumber()).toBe(0);
});
it('should not create negative results count', () => {
expect(() => ResultsCount.create(-1)).toThrow('Results count must be a non-negative integer');
});
it('should not create non-integer results count', () => {
expect(() => ResultsCount.create(1.5)).toThrow('Results count must be a non-negative integer');
});
it('should equal same count', () => {
const c1 = ResultsCount.create(3);
const c2 = ResultsCount.create(3);
expect(c1.equals(c2)).toBe(true);
});
it('should not equal different count', () => {
const c1 = ResultsCount.create(3);
const c2 = ResultsCount.create(4);
expect(c1.equals(c2)).toBe(false);
});
});

View File

@@ -0,0 +1,20 @@
import { RacingDomainValidationError } from '../../errors/RacingDomainError';
export class ResultsCount {
private constructor(private readonly value: number) {}
static create(value: number): ResultsCount {
if (!Number.isInteger(value) || value < 0) {
throw new RacingDomainValidationError('Results count must be a non-negative integer');
}
return new ResultsCount(value);
}
toNumber(): number {
return this.value;
}
equals(other: ResultsCount): boolean {
return this.value === other.value;
}
}