website refactor

This commit is contained in:
2026-01-16 18:21:06 +01:00
parent 2f53727702
commit 095885544b
146 changed files with 970 additions and 524 deletions

View File

@@ -5,8 +5,7 @@
* Uses EligibilityEvaluator to provide explainable results.
*/
import { EvaluationResultDto } from '../dtos/EvaluationResultDto';
import { EligibilityFilterDto } from '../dtos/EligibilityFilterDto';
import { EvaluationResultDto, EligibilityFilterDto } from '../../domain/types/Eligibility';
import { EligibilityEvaluator, RatingData } from '../../domain/services/EligibilityEvaluator';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';

View File

@@ -2,7 +2,7 @@
* Tests for GetUserRatingsSummaryQuery
*/
import { describe, expect, it, beforeEach, vi } from 'vitest';
import { describe, expect, it, beforeEach, vi, type Mock } from 'vitest';
import { GetUserRatingsSummaryQuery, GetUserRatingsSummaryQueryHandler } from './GetUserRatingsSummaryQuery';
import { UserRating } from '../../domain/value-objects/UserRating';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
@@ -13,11 +13,16 @@ import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserId } from '../../domain/value-objects/UserId';
describe('GetUserRatingsSummaryQuery', () => {
let mockUserRatingRepo: any;
let mockExternalRatingRepo: any;
let mockRatingEventRepo: any;
let mockUserRatingRepo: { findByUserId: Mock };
let mockExternalRatingRepo: { findByUserId: Mock };
let mockRatingEventRepo: { getAllByUserId: Mock };
let handler: GetUserRatingsSummaryQueryHandler;
beforeEach(() => {
@@ -32,9 +37,9 @@ describe('GetUserRatingsSummaryQuery', () => {
};
handler = new GetUserRatingsSummaryQueryHandler(
mockUserRatingRepo,
mockExternalRatingRepo,
mockRatingEventRepo
mockUserRatingRepo as unknown as UserRatingRepository,
mockExternalRatingRepo as unknown as ExternalGameRatingRepository,
mockRatingEventRepo as unknown as RatingEventRepository
);
});
@@ -49,7 +54,7 @@ describe('GetUserRatingsSummaryQuery', () => {
// Mock external ratings
const gameKey = GameKey.create('iracing');
const profile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey,
ratings: new Map([
['iRating', ExternalRating.create(gameKey, 'iRating', 2200)],
@@ -109,7 +114,7 @@ describe('GetUserRatingsSummaryQuery', () => {
// Multiple game profiles
const iracingProfile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey: GameKey.create('iracing'),
ratings: new Map([
['iRating', ExternalRating.create(GameKey.create('iracing'), 'iRating', 2200)],
@@ -118,7 +123,7 @@ describe('GetUserRatingsSummaryQuery', () => {
});
const assettoProfile = ExternalGameRatingProfile.create({
userId: { toString: () => userId } as any,
userId: UserId.fromString(userId),
gameKey: GameKey.create('assetto'),
ratings: new Map([
['rating', ExternalRating.create(GameKey.create('assetto'), 'rating', 85)],

View File

@@ -134,14 +134,16 @@ class MockUserRatingRepository {
}
}
import { CreateRatingEventDto } from '../dtos/CreateRatingEventDto';
// Mock AppendRatingEventsUseCase
class MockAppendRatingEventsUseCase {
constructor(
private ratingEventRepository: any,
private userRatingRepository: any
private ratingEventRepository: MockRatingEventRepository,
private userRatingRepository: MockUserRatingRepository
) {}
async execute(input: any): Promise<any> {
async execute(input: { userId: string; events: CreateRatingEventDto[] }): Promise<{ events: string[]; snapshotUpdated: boolean }> {
const events: RatingEvent[] = [];
// Create events from input
@@ -195,7 +197,6 @@ describe('Admin Vote Session Use Cases', () => {
// Use dates relative to current time so close() works
const now = new Date(Date.now() - 86400000); // Yesterday
const tomorrow = new Date(Date.now() + 86400000); // Tomorrow
const dayAfter = new Date(Date.now() + 86400000 * 2); // Day after tomorrow
beforeEach(() => {
mockSessionRepo = new MockAdminVoteSessionRepository();

View File

@@ -3,9 +3,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest';
import { AppendRatingEventsUseCase, AppendRatingEventsInput } from './AppendRatingEventsUseCase';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
describe('AppendRatingEventsUseCase', () => {
let mockEventRepo: Partial<RatingEventRepository>;

View File

@@ -2,7 +2,7 @@ import { RatingEventRepository } from '../../domain/repositories/RatingEventRepo
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEvent, type RatingEventProps } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';
@@ -97,7 +97,7 @@ export class AppendRatingEventsUseCase {
}
private createEventFromDto(dto: CreateRatingEventDto) {
const props: any = {
const props: RatingEventProps = {
id: RatingEventId.generate(),
userId: dto.userId,
dimension: RatingDimensionKey.create(dto.dimension),

View File

@@ -1,10 +1,10 @@
import { AdminVoteSessionRepository } from '../../domain/repositories/AdminVoteSessionRepository';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { AdminTrustRatingCalculator } from '../../domain/services/AdminTrustRatingCalculator';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { CloseAdminVoteSessionInput, CloseAdminVoteSessionOutput } from '../dtos/AdminVoteSessionDto';
import { AdminVoteSession, AdminVoteOutcome } from '../../domain/entities/AdminVoteSession';
/**
* Use Case: CloseAdminVoteSessionUseCase
@@ -26,7 +26,6 @@ export class CloseAdminVoteSessionUseCase {
private readonly adminVoteSessionRepository: AdminVoteSessionRepository,
private readonly ratingEventRepository: RatingEventRepository,
private readonly userRatingRepository: UserRatingRepository,
private readonly appendRatingEventsUseCase: any, // Will be typed properly in integration
) {}
async execute(input: CloseAdminVoteSessionInput): Promise<CloseAdminVoteSessionOutput> {
@@ -117,7 +116,7 @@ export class CloseAdminVoteSessionUseCase {
* Events are created for the admin being voted on
* Per plans: no events are created for tie outcomes
*/
private async createRatingEvents(session: any, outcome: any): Promise<number> {
private async createRatingEvents(session: AdminVoteSession, outcome: AdminVoteOutcome): Promise<number> {
let eventsCreated = 0;
// Don't create events for tie outcomes

View File

@@ -1,7 +1,8 @@
import { Result } from '@/shared/domain/Result';
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { User } from '../../domain/entities/User';
import { UserRepository } from '../../domain/repositories/UserRepository';
export type GetCurrentSessionInput = {
userId: string;

View File

@@ -4,11 +4,12 @@
* Authenticates a user with email and password.
*/
import { PasswordHash } from '@/identity/domain/value-objects/PasswordHash';
import { PasswordHash } from '../../domain/value-objects/PasswordHash';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { UserRepository } from '../../domain/repositories/UserRepository';
export type LoginWithEmailInput = {
email: string;

View File

@@ -6,8 +6,6 @@ import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';
// In-memory implementations for integration testing
class InMemoryRaceResultsProvider implements RaceResultsProvider {

View File

@@ -6,8 +6,7 @@ import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';
import { describe, it, expect, beforeEach } from 'vitest';
// Mock implementations
class MockRaceResultsProvider implements RaceResultsProvider {
@@ -17,11 +16,11 @@ class MockRaceResultsProvider implements RaceResultsProvider {
this.results = results;
}
async getRaceResults(raceId: string): Promise<RaceResultsData | null> {
async getRaceResults(_raceId: string): Promise<RaceResultsData | null> {
return this.results;
}
async hasRaceResults(raceId: string): Promise<boolean> {
async hasRaceResults(_raceId: string): Promise<boolean> {
return this.results !== null;
}
}

View File

@@ -2,8 +2,6 @@ import { RaceResultsProvider } from '../ports/RaceResultsProvider';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { DrivingRatingCalculator } from '../../domain/services/DrivingRatingCalculator';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { RecordRaceRatingEventsInput, RecordRaceRatingEventsOutput } from '../dtos/RecordRaceRatingEventsDto';

View File

@@ -1,6 +1,7 @@
import { Result } from '@core/shared/domain/Result';
import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { StoredUser } from '../../domain/repositories/UserRepository';
import type { StoredUser, UserRepository } from '../../domain/repositories/UserRepository';
import type { AuthenticatedUser } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';

View File

@@ -1,27 +1,30 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { UpsertExternalGameRatingUseCase } from './UpsertExternalGameRatingUseCase';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
import { ExternalGameRatingRepository } from '../../domain/repositories/ExternalGameRatingRepository';
// Mock repository for integration test
class MockExternalGameRatingRepository {
private profiles = new Map<string, any>();
private profiles = new Map<string, ExternalGameRatingProfile>();
private getKey(userId: string, gameKey: string): string {
return `${userId}|${gameKey}`;
}
async findByUserIdAndGameKey(userId: string, gameKey: string): Promise<any | null> {
async findByUserIdAndGameKey(userId: string, gameKey: string): Promise<ExternalGameRatingProfile | null> {
return this.profiles.get(this.getKey(userId, gameKey)) || null;
}
async findByUserId(userId: string): Promise<any[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.userId.toString() === userId);
async findByUserId(userId: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.userId.toString() === userId);
}
async findByGameKey(gameKey: string): Promise<any[]> {
return Array.from(this.profiles.values()).filter((p: any) => p.gameKey.toString() === gameKey);
async findByGameKey(gameKey: string): Promise<ExternalGameRatingProfile[]> {
return Array.from(this.profiles.values()).filter((p: ExternalGameRatingProfile) => p.gameKey.toString() === gameKey);
}
async save(profile: any): Promise<any> {
async save(profile: ExternalGameRatingProfile): Promise<ExternalGameRatingProfile> {
const key = this.getKey(profile.userId.toString(), profile.gameKey.toString());
this.profiles.set(key, profile);
return profile;
@@ -50,7 +53,7 @@ describe('UpsertExternalGameRatingUseCase - Integration', () => {
beforeEach(() => {
repository = new MockExternalGameRatingRepository();
useCase = new UpsertExternalGameRatingUseCase(repository as any);
useCase = new UpsertExternalGameRatingUseCase(repository as unknown as ExternalGameRatingRepository);
});
describe('Full upsert flow', () => {

View File

@@ -6,11 +6,19 @@ import { GameKey } from '../../domain/value-objects/GameKey';
import { ExternalRating } from '../../domain/value-objects/ExternalRating';
import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance';
import { UpsertExternalGameRatingInput } from '../dtos/UpsertExternalGameRatingDto';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { vi, describe, it, expect, beforeEach, type Mock } from 'vitest';
describe('UpsertExternalGameRatingUseCase', () => {
let useCase: UpsertExternalGameRatingUseCase;
let mockRepository: ExternalGameRatingRepository;
let mockRepository: {
findByUserIdAndGameKey: Mock;
findByUserId: Mock;
findByGameKey: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
exists: Mock;
};
beforeEach(() => {
mockRepository = {
@@ -21,9 +29,17 @@ describe('UpsertExternalGameRatingUseCase', () => {
saveMany: vi.fn(),
delete: vi.fn(),
exists: vi.fn(),
} as any;
} as unknown as {
findByUserIdAndGameKey: Mock;
findByUserId: Mock;
findByGameKey: Mock;
save: Mock;
saveMany: Mock;
delete: Mock;
exists: Mock;
};
useCase = new UpsertExternalGameRatingUseCase(mockRepository);
useCase = new UpsertExternalGameRatingUseCase(mockRepository as unknown as ExternalGameRatingRepository);
});
describe('execute', () => {
@@ -42,8 +58,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);
@@ -61,8 +77,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
it('should update existing profile', async () => {
const existingProfile = createTestProfile('user-123', 'iracing');
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(existingProfile);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(existingProfile);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const input: UpsertExternalGameRatingInput = {
userId: 'user-123',
@@ -211,7 +227,7 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockRejectedValue(new Error('Database connection failed'));
mockRepository.findByUserIdAndGameKey.mockRejectedValue(new Error('Database connection failed'));
const result = await useCase.execute(input);
@@ -232,8 +248,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);
@@ -254,8 +270,8 @@ describe('UpsertExternalGameRatingUseCase', () => {
},
};
(mockRepository.findByUserIdAndGameKey as any).mockResolvedValue(null);
(mockRepository.save as any).mockImplementation(async (profile: any) => profile);
mockRepository.findByUserIdAndGameKey.mockResolvedValue(null);
mockRepository.save.mockImplementation(async (profile: ExternalGameRatingProfile) => profile);
const result = await useCase.execute(input);

View File

@@ -1,5 +1,4 @@
import type { DomainError } from '@core/shared/errors/DomainError';
import type { CommonDomainErrorKind } from '@core/shared/errors/DomainError';
import type { DomainError, CommonDomainErrorKind } from '@core/shared/errors/DomainError';
export abstract class IdentityDomainError extends Error implements DomainError<CommonDomainErrorKind> {
readonly type = 'domain' as const;

View File

@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { ExternalGameRatingRepository } from './ExternalGameRatingRepository';
import { ExternalGameRatingProfile } from '../entities/ExternalGameRatingProfile';
import { UserId } from '../value-objects/UserId';
@@ -182,11 +183,6 @@ describe('ExternalGameRatingRepository', () => {
await repository.save(profile);
// Update the profile
const updatedProvenance = ExternalRatingProvenance.create({
source: 'iracing',
lastSyncedAt: new Date('2024-01-02'),
verified: true,
});
profile.updateLastSyncedAt(new Date('2024-01-02'));
profile.markVerified();
@@ -290,7 +286,7 @@ describe('ExternalGameRatingRepository', () => {
lastSyncedAt: new Date('2024-01-01'),
verified: false,
});
profile2.updateRatings(profile2.ratings, profile2Provenance);
profile2.updateRatings(new Map(profile2.ratings), profile2Provenance);
await repository.saveMany([profile1, profile2]);

View File

@@ -3,7 +3,6 @@ import { RatingEvent } from '../entities/RatingEvent';
import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingDelta } from '../value-objects/RatingDelta';
import { AdminVoteOutcome } from '../entities/AdminVoteSession';
describe('AdminTrustRatingCalculator', () => {
describe('calculate', () => {
@@ -316,13 +315,12 @@ describe('AdminTrustRatingCalculator', () => {
it('should default to zero for unknown action type', () => {
const input: SystemSignalInput = {
actionType: 'sla_response' as any,
actionType: 'unknown_type' as unknown as SystemSignalInput['actionType'],
details: {},
};
// Override for test
const delta = AdminTrustRatingCalculator.calculateFromSystemSignal(input);
expect(delta.value).toBe(5); // Known type
expect(delta.value).toBe(0);
});
});

View File

@@ -1,6 +1,4 @@
import { RatingEvent } from '../entities/RatingEvent';
import { DrivingReasonCode } from '../value-objects/DrivingReasonCode';
import { RatingDelta } from '../value-objects/RatingDelta';
/**
* Input DTO for driving rating calculation from race facts
@@ -71,7 +69,7 @@ export class DrivingRatingCalculator {
const fieldStrength = facts.results.length > 0
? (facts.results
.filter(r => r.status === 'finished')
.reduce((sum, r) => sum + (r.sof || this.estimateDriverRating(r.userId)), 0) /
.reduce((sum, r) => sum + (r.sof || this.estimateDriverRating()), 0) /
Math.max(1, facts.results.filter(r => r.status === 'finished').length))
: 0;
@@ -297,7 +295,7 @@ export class DrivingRatingCalculator {
* Estimate driver rating for SoF calculation
* This is a placeholder - in real implementation, would query user rating snapshot
*/
private static estimateDriverRating(userId: string): number {
private static estimateDriverRating(): number {
// Default rating for new drivers
return 50;
}

View File

@@ -3,7 +3,7 @@
*/
import { EligibilityEvaluator, RatingData } from './EligibilityEvaluator';
import { EligibilityFilterDto } from '../../application/dtos/EligibilityFilterDto';
import { EligibilityFilterDto } from '../types/Eligibility';
describe('EligibilityEvaluator', () => {
let evaluator: EligibilityEvaluator;

View File

@@ -6,8 +6,13 @@
* Provides explainable results with detailed reasons.
*/
import { EvaluationResultDto, EvaluationReason } from '../../application/dtos/EvaluationResultDto';
import { EligibilityFilterDto, ParsedEligibilityFilter, EligibilityCondition } from '../../application/dtos/EligibilityFilterDto';
import {
EvaluationResultDto,
EvaluationReason,
EligibilityFilterDto,
ParsedEligibilityFilter,
EligibilityCondition
} from '../types/Eligibility';
export interface RatingData {
platform: {

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { RatingUpdateService } from './RatingUpdateService';
import type { UserRatingRepository } from '../repositories/UserRatingRepository';
import type { RatingEventRepository } from '../repositories/RatingEventRepository';
@@ -10,8 +10,8 @@ import { RatingDelta } from '../value-objects/RatingDelta';
describe('RatingUpdateService - Slice 7 Evolution', () => {
let service: RatingUpdateService;
let userRatingRepository: any;
let ratingEventRepository: any;
let userRatingRepository: { findByUserId: Mock; save: Mock };
let ratingEventRepository: { save: Mock; getAllByUserId: Mock };
beforeEach(() => {
userRatingRepository = {
@@ -24,7 +24,10 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
getAllByUserId: vi.fn(),
};
service = new RatingUpdateService(userRatingRepository, ratingEventRepository);
service = new RatingUpdateService(
userRatingRepository as unknown as UserRatingRepository,
ratingEventRepository as unknown as RatingEventRepository
);
});
describe('recordRaceRatingEvents - Ledger-based approach', () => {
@@ -150,7 +153,7 @@ describe('RatingUpdateService - Slice 7 Evolution', () => {
// Verify DNF penalty event was created
const savedEvents = ratingEventRepository.save.mock.calls.map(call => call[0]);
const hasDnfPenalty = savedEvents.some((event: any) =>
const hasDnfPenalty = savedEvents.some((event: RatingEvent) =>
event.reason.code === 'DRIVING_DNF_PENALTY'
);
expect(hasDnfPenalty).toBe(true);

View File

@@ -0,0 +1,55 @@
/**
* Domain Types: Eligibility
*
* DSL-based eligibility filter and evaluation results.
*/
export interface EligibilityFilterDto {
/**
* DSL expression for eligibility rules
*/
dsl: string;
/**
* Optional context for evaluation
*/
context?: {
userId?: string;
leagueId?: string;
[key: string]: unknown;
};
}
export interface EligibilityCondition {
target: 'platform' | 'external';
dimension?: string;
game?: string;
operator: string;
expected: number | [number, number];
}
export interface ParsedEligibilityFilter {
conditions: EligibilityCondition[];
logicalOperator: 'AND' | 'OR';
}
export interface EvaluationReason {
target: string;
operator: string;
expected: number | [number, number];
actual: number;
failed: boolean;
message?: string;
}
export interface EvaluationResultDto {
eligible: boolean;
reasons: EvaluationReason[];
summary: string;
evaluatedAt: string;
metadata?: {
userId?: string;
filter?: string;
[key: string]: unknown;
};
}

View File

@@ -1,5 +1,6 @@
import { AdminTrustReasonCode } from './AdminTrustReasonCode';
import { AdminTrustReasonCode, type AdminTrustReasonCodeValue } from './AdminTrustReasonCode';
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('AdminTrustReasonCode', () => {
describe('create', () => {
@@ -71,7 +72,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE',
];
voteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(true);
});
});
@@ -82,7 +83,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_REVERSAL_PENALTY',
];
nonVoteCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isVoteOutcome()).toBe(false);
});
});
@@ -97,7 +98,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
systemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(true);
});
});
@@ -108,7 +109,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_VOTE_OUTCOME_NEGATIVE',
];
nonSystemCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isSystemSignal()).toBe(false);
});
});
@@ -122,7 +123,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS',
];
positiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(true);
});
});
@@ -134,7 +135,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
nonPositiveCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isPositive()).toBe(false);
});
});
@@ -148,7 +149,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
];
negativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(true);
});
});
@@ -160,7 +161,7 @@ describe('AdminTrustReasonCode', () => {
'ADMIN_ACTION_RULE_CLARITY_BONUS',
];
nonNegativeCodes.forEach(codeStr => {
const code = AdminTrustReasonCode.fromValue(codeStr as any);
const code = AdminTrustReasonCode.fromValue(codeStr as AdminTrustReasonCodeValue);
expect(code.isNegative()).toBe(false);
});
});

View File

@@ -1,5 +1,6 @@
import { DrivingReasonCode } from './DrivingReasonCode';
import { DrivingReasonCode, type DrivingReasonCodeValue } from './DrivingReasonCode';
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { describe, it, expect } from 'vitest';
describe('DrivingReasonCode', () => {
describe('create', () => {
@@ -77,7 +78,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN',
];
performanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(true);
});
});
@@ -89,7 +90,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonPerformanceCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPerformance()).toBe(false);
});
});
@@ -103,7 +104,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PENALTY_INVOLVEMENT_PENALTY',
];
cleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(true);
});
});
@@ -115,7 +116,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonCleanDrivingCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isCleanDriving()).toBe(false);
});
});
@@ -131,7 +132,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
reliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(true);
});
});
@@ -142,7 +143,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_INCIDENTS_PENALTY',
];
nonReliabilityCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isReliability()).toBe(false);
});
});
@@ -160,7 +161,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_AFK_PENALTY',
];
penaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(true);
});
});
@@ -172,7 +173,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_SEASON_ATTENDANCE_BONUS',
];
nonPenaltyCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isPenalty()).toBe(false);
});
});
@@ -187,7 +188,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_PACE_RELATIVE_GAIN',
];
bonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(true);
});
});
@@ -198,7 +199,7 @@ describe('DrivingReasonCode', () => {
'DRIVING_DNS_PENALTY',
];
nonBonusCodes.forEach(codeStr => {
const code = DrivingReasonCode.fromValue(codeStr as any);
const code = DrivingReasonCode.fromValue(codeStr as DrivingReasonCodeValue);
expect(code.isBonus()).toBe(false);
});
});