This commit is contained in:
2025-12-29 22:27:33 +01:00
parent 3f610c1cb6
commit 7a853d4e43
96 changed files with 14790 additions and 111 deletions

View File

@@ -0,0 +1,198 @@
import 'reflect-metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { DataSource } from 'typeorm';
import { RatingEventOrmEntity } from '@adapters/identity/persistence/typeorm/entities/RatingEventOrmEntity';
import { UserRatingOrmEntity } from '@adapters/identity/persistence/typeorm/entities/UserRatingOrmEntity';
import { TypeOrmRatingEventRepository } from '@adapters/identity/persistence/typeorm/repositories/TypeOrmRatingEventRepository';
import { TypeOrmUserRatingRepository } from '@adapters/identity/persistence/typeorm/repositories/TypeOrmUserRatingRepository';
import { AppendRatingEventsUseCase } from '@core/identity/application/use-cases/AppendRatingEventsUseCase';
import { RecomputeUserRatingSnapshotUseCase } from '@core/identity/application/use-cases/RecomputeUserRatingSnapshotUseCase';
const databaseUrl = process.env.DATABASE_URL;
const describeIfDatabase = databaseUrl ? describe : describe.skip;
describeIfDatabase('TypeORM Identity Rating repositories (postgres slice)', () => {
let dataSource: DataSource;
let eventRepo: TypeOrmRatingEventRepository;
let ratingRepo: TypeOrmUserRatingRepository;
let appendUseCase: AppendRatingEventsUseCase;
let recomputeUseCase: RecomputeUserRatingSnapshotUseCase;
beforeAll(async () => {
if (!databaseUrl) {
throw new Error('DATABASE_URL is required to run postgres integration tests');
}
dataSource = new DataSource({
type: 'postgres',
url: databaseUrl,
entities: [RatingEventOrmEntity, UserRatingOrmEntity],
synchronize: true,
});
await dataSource.initialize();
// Initialize repositories
eventRepo = new TypeOrmRatingEventRepository(dataSource);
ratingRepo = new TypeOrmUserRatingRepository(dataSource);
// Initialize use cases
appendUseCase = new AppendRatingEventsUseCase(eventRepo, ratingRepo);
recomputeUseCase = new RecomputeUserRatingSnapshotUseCase(eventRepo, ratingRepo);
});
afterAll(async () => {
if (dataSource?.isInitialized) {
// Clean up test data
await dataSource.getRepository(RatingEventOrmEntity).clear();
await dataSource.getRepository(UserRatingOrmEntity).clear();
await dataSource.destroy();
}
});
it('should complete full flow: append events -> persist -> recompute snapshot', async () => {
const userId = `test-user-${Date.now()}`;
// Step 1: Append rating events from race results
const appendResult = await appendUseCase.execute({
userId,
raceId: 'race-integration-test',
raceResults: [
{
position: 3,
totalDrivers: 10,
startPosition: 5,
incidents: 1,
fieldStrength: 1500,
status: 'finished',
},
{
position: 1,
totalDrivers: 10,
startPosition: 2,
incidents: 0,
fieldStrength: 1500,
status: 'finished',
},
],
});
// Verify events were saved
expect(appendResult.events.length).toBeGreaterThan(0);
expect(appendResult.snapshotUpdated).toBe(true);
// Step 2: Verify events are in database
const eventsInDb = await eventRepo.getAllByUserId(userId);
expect(eventsInDb.length).toBeGreaterThan(0);
expect(eventsInDb.length).toBe(appendResult.events.length);
// Step 3: Verify snapshot was created
const snapshotFromDb = await ratingRepo.findByUserId(userId);
expect(snapshotFromDb).not.toBeNull();
expect(snapshotFromDb!.userId).toBe(userId);
expect(snapshotFromDb!.driver.value).toBeGreaterThan(50); // Should have increased
// Step 4: Recompute snapshot manually
const recomputeResult = await recomputeUseCase.execute({ userId });
// Verify recomputed snapshot
expect(recomputeResult.snapshot.userId).toBe(userId);
expect(recomputeResult.snapshot.driver.value).toBeGreaterThan(50);
expect(recomputeResult.snapshot.driver.sampleSize).toBeGreaterThan(0);
// Step 5: Verify recomputed snapshot matches what's in DB
const finalSnapshot = await ratingRepo.findByUserId(userId);
expect(finalSnapshot!.driver.value).toBe(recomputeResult.snapshot.driver.value);
});
it('should handle direct event creation and recompute', async () => {
const userId = `test-user-direct-${Date.now()}`;
// Append direct events
const appendResult = await appendUseCase.execute({
userId,
events: [
{
userId,
dimension: 'driving',
delta: 8,
sourceType: 'race',
sourceId: 'race-direct',
reasonCode: 'DRIVING_FINISH_STRENGTH_GAIN',
reasonSummary: 'Excellent finish',
},
{
userId,
dimension: 'driving',
delta: -2,
weight: 0.5,
sourceType: 'penalty',
sourceId: 'penalty-1',
reasonCode: 'DRIVING_INCIDENTS_PENALTY',
reasonSummary: 'Minor incident',
},
],
});
expect(appendResult.events).toHaveLength(2);
expect(appendResult.snapshotUpdated).toBe(true);
// Verify events
const events = await eventRepo.getAllByUserId(userId);
expect(events).toHaveLength(2);
// Verify snapshot
const snapshot = await ratingRepo.findByUserId(userId);
expect(snapshot).not.toBeNull();
expect(snapshot!.driver.value).toBeGreaterThan(50);
expect(snapshot!.driver.sampleSize).toBe(2);
});
it('should handle empty race results gracefully', async () => {
const userId = `test-user-empty-${Date.now()}`;
const result = await appendUseCase.execute({
userId,
raceId: 'race-empty',
raceResults: [],
});
expect(result.events).toHaveLength(0);
expect(result.snapshotUpdated).toBe(false);
// No snapshot should exist
const snapshot = await ratingRepo.findByUserId(userId);
expect(snapshot).toBeNull();
});
it('should handle DNF/DNS/DSQ/AFK statuses', async () => {
const userId = `test-user-status-${Date.now()}`;
const result = await appendUseCase.execute({
userId,
raceId: 'race-status-test',
raceResults: [
{ position: 5, totalDrivers: 10, startPosition: 3, incidents: 2, fieldStrength: 1500, status: 'dnf' },
{ position: 5, totalDrivers: 10, startPosition: 3, incidents: 0, fieldStrength: 1500, status: 'dns' },
{ position: 5, totalDrivers: 10, startPosition: 3, incidents: 5, fieldStrength: 1500, status: 'dsq' },
{ position: 5, totalDrivers: 10, startPosition: 3, incidents: 1, fieldStrength: 1500, status: 'afk' },
],
});
expect(result.events.length).toBeGreaterThan(0);
expect(result.snapshotUpdated).toBe(true);
// Verify events were created for each status
const events = await eventRepo.getAllByUserId(userId);
expect(events.length).toBeGreaterThan(0);
// Verify snapshot exists and has penalties
const snapshot = await ratingRepo.findByUserId(userId);
expect(snapshot).not.toBeNull();
expect(snapshot!.driver.value).toBeLessThan(50); // Should have decreased due to penalties
});
});