team rating
This commit is contained in:
376
core/racing/application/queries/GetTeamRatingLedgerQuery.test.ts
Normal file
376
core/racing/application/queries/GetTeamRatingLedgerQuery.test.ts
Normal file
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* Tests for GetTeamRatingLedgerQuery
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
|
||||
import { GetTeamRatingLedgerQuery, GetTeamRatingLedgerQueryHandler } from './GetTeamRatingLedgerQuery';
|
||||
import { TeamRatingEvent } from '../../domain/entities/TeamRatingEvent';
|
||||
import { TeamRatingEventId } from '../../domain/value-objects/TeamRatingEventId';
|
||||
import { TeamRatingDimensionKey } from '../../domain/value-objects/TeamRatingDimensionKey';
|
||||
import { TeamRatingDelta } from '../../domain/value-objects/TeamRatingDelta';
|
||||
|
||||
describe('GetTeamRatingLedgerQuery', () => {
|
||||
let mockRatingEventRepo: any;
|
||||
let handler: GetTeamRatingLedgerQueryHandler;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRatingEventRepo = {
|
||||
findEventsPaginated: vi.fn(),
|
||||
};
|
||||
|
||||
handler = new GetTeamRatingLedgerQueryHandler(mockRatingEventRepo);
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
it('should return paginated ledger entries', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
// Mock paginated result
|
||||
const event1 = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('driving'),
|
||||
delta: TeamRatingDelta.create(10),
|
||||
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-01T10:00:00Z'),
|
||||
source: { type: 'race', id: 'race-123' },
|
||||
reason: { code: 'RACE_FINISH' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
const event2 = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('adminTrust'),
|
||||
delta: TeamRatingDelta.create(-5),
|
||||
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-02T10:00:00Z'),
|
||||
source: { type: 'penalty', id: 'penalty-456' },
|
||||
reason: { code: 'LATE_JOIN' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
weight: 2,
|
||||
});
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [event1, event2],
|
||||
total: 2,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
nextOffset: undefined,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result.entries.length).toBe(2);
|
||||
|
||||
const entry1 = result.entries[0];
|
||||
expect(entry1).toBeDefined();
|
||||
if (entry1) {
|
||||
expect(entry1.teamId).toBe(teamId);
|
||||
expect(entry1.dimension).toBe('driving');
|
||||
expect(entry1.delta).toBe(10);
|
||||
expect(entry1.source.type).toBe('race');
|
||||
expect(entry1.source.id).toBe('race-123');
|
||||
expect(entry1.reason.code).toBe('RACE_FINISH');
|
||||
expect(entry1.visibility.public).toBe(true);
|
||||
}
|
||||
|
||||
const entry2 = result.entries[1];
|
||||
expect(entry2).toBeDefined();
|
||||
if (entry2) {
|
||||
expect(entry2.dimension).toBe('adminTrust');
|
||||
expect(entry2.delta).toBe(-5);
|
||||
expect(entry2.weight).toBe(2);
|
||||
expect(entry2.source.type).toBe('penalty');
|
||||
expect(entry2.source.id).toBe('penalty-456');
|
||||
}
|
||||
|
||||
expect(result.pagination.total).toBe(2);
|
||||
expect(result.pagination.limit).toBe(20);
|
||||
expect(result.pagination.hasMore).toBe(false);
|
||||
});
|
||||
|
||||
it('should apply default pagination values', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
await handler.execute(query);
|
||||
|
||||
expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
|
||||
teamId,
|
||||
expect.objectContaining({
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply custom pagination values', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
limit: 10,
|
||||
offset: 20,
|
||||
hasMore: true,
|
||||
nextOffset: 30,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId, limit: 10, offset: 20 };
|
||||
await handler.execute(query);
|
||||
|
||||
expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
|
||||
teamId,
|
||||
expect.objectContaining({
|
||||
limit: 10,
|
||||
offset: 20,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply filters when provided', async () => {
|
||||
const teamId = 'team-123';
|
||||
const filter = {
|
||||
dimensions: ['driving'],
|
||||
sourceTypes: ['race', 'penalty'] as ('race' | 'penalty' | 'vote' | 'adminAction' | 'manualAdjustment')[],
|
||||
from: '2024-01-01T00:00:00Z',
|
||||
to: '2024-01-31T23:59:59Z',
|
||||
reasonCodes: ['RACE_FINISH', 'LATE_JOIN'],
|
||||
};
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [],
|
||||
total: 0,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId, filter };
|
||||
await handler.execute(query);
|
||||
|
||||
expect(mockRatingEventRepo.findEventsPaginated).toHaveBeenCalledWith(
|
||||
teamId,
|
||||
expect.objectContaining({
|
||||
filter: expect.objectContaining({
|
||||
dimensions: ['driving'],
|
||||
sourceTypes: ['race', 'penalty'],
|
||||
from: new Date('2024-01-01T00:00:00Z'),
|
||||
to: new Date('2024-01-31T23:59:59Z'),
|
||||
reasonCodes: ['RACE_FINISH', 'LATE_JOIN'],
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle events with optional weight', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
const eventWithWeight = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('driving'),
|
||||
delta: TeamRatingDelta.create(15),
|
||||
weight: 1.5,
|
||||
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-01T10:00:00Z'),
|
||||
source: { type: 'race', id: 'race-789' },
|
||||
reason: { code: 'PERFORMANCE_BONUS' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
const eventWithoutWeight = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('adminTrust'),
|
||||
delta: TeamRatingDelta.create(5),
|
||||
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-02T10:00:00Z'),
|
||||
source: { type: 'vote', id: 'vote-123' },
|
||||
reason: { code: 'POSITIVE_VOTE' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [eventWithWeight, eventWithoutWeight],
|
||||
total: 2,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
const entry1 = result.entries[0];
|
||||
const entry2 = result.entries[1];
|
||||
expect(entry1).toBeDefined();
|
||||
expect(entry2).toBeDefined();
|
||||
|
||||
if (entry1) {
|
||||
expect(entry1.weight).toBe(1.5);
|
||||
}
|
||||
if (entry2) {
|
||||
expect(entry2.weight).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle events with optional source.id', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
const eventWithId = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('driving'),
|
||||
delta: TeamRatingDelta.create(10),
|
||||
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-01T10:00:00Z'),
|
||||
source: { type: 'race', id: 'race-123' },
|
||||
reason: { code: 'RACE_FINISH' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
const eventWithoutId = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('adminTrust'),
|
||||
delta: TeamRatingDelta.create(5),
|
||||
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-02T10:00:00Z'),
|
||||
source: { type: 'manualAdjustment' },
|
||||
reason: { code: 'ADMIN_ADJUSTMENT' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [eventWithId, eventWithoutId],
|
||||
total: 2,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
const entry1 = result.entries[0];
|
||||
const entry2 = result.entries[1];
|
||||
expect(entry1).toBeDefined();
|
||||
expect(entry2).toBeDefined();
|
||||
|
||||
if (entry1) {
|
||||
expect(entry1.source.id).toBe('race-123');
|
||||
}
|
||||
if (entry2) {
|
||||
expect(entry2.source.id).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle events with optional reason.description', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
const eventWithDescription = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('driving'),
|
||||
delta: TeamRatingDelta.create(10),
|
||||
occurredAt: new Date('2024-01-01T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-01T10:00:00Z'),
|
||||
source: { type: 'race', id: 'race-123' },
|
||||
reason: { code: 'RACE_FINISH', description: 'Finished 1st in class' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
const eventWithoutDescription = TeamRatingEvent.create({
|
||||
id: TeamRatingEventId.generate(),
|
||||
teamId,
|
||||
dimension: TeamRatingDimensionKey.create('adminTrust'),
|
||||
delta: TeamRatingDelta.create(5),
|
||||
occurredAt: new Date('2024-01-02T10:00:00Z'),
|
||||
createdAt: new Date('2024-01-02T10:00:00Z'),
|
||||
source: { type: 'vote', id: 'vote-123' },
|
||||
reason: { code: 'POSITIVE_VOTE' },
|
||||
visibility: { public: true },
|
||||
version: 1,
|
||||
});
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [eventWithDescription, eventWithoutDescription],
|
||||
total: 2,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
const entry1 = result.entries[0];
|
||||
const entry2 = result.entries[1];
|
||||
expect(entry1).toBeDefined();
|
||||
expect(entry2).toBeDefined();
|
||||
|
||||
if (entry1) {
|
||||
expect(entry1.reason.description).toBe('Finished 1st in class');
|
||||
}
|
||||
if (entry2) {
|
||||
expect(entry2.reason.description).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return nextOffset when hasMore is true', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [],
|
||||
total: 50,
|
||||
limit: 20,
|
||||
offset: 20,
|
||||
hasMore: true,
|
||||
nextOffset: 40,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result.pagination.hasMore).toBe(true);
|
||||
expect(result.pagination.nextOffset).toBe(40);
|
||||
});
|
||||
|
||||
it('should return null nextOffset when hasMore is false', async () => {
|
||||
const teamId = 'team-123';
|
||||
|
||||
mockRatingEventRepo.findEventsPaginated.mockResolvedValue({
|
||||
items: [],
|
||||
total: 15,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
const query: GetTeamRatingLedgerQuery = { teamId };
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result.pagination.hasMore).toBe(false);
|
||||
expect(result.pagination.nextOffset).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user