Files
gridpilot.gg/core/identity/domain/entities/RatingEvent.test.ts
2026-01-16 19:46:49 +01:00

174 lines
6.4 KiB
TypeScript

import { IdentityDomainInvariantError, IdentityDomainValidationError } from '../errors/IdentityDomainError';
import { RatingDelta } from '../value-objects/RatingDelta';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingEvent } from './RatingEvent';
describe('RatingEvent', () => {
const validProps = {
id: RatingEventId.create('123e4567-e89b-12d3-a456-426614174000'),
userId: 'user-123',
dimension: RatingDimensionKey.create('driving'),
delta: RatingDelta.create(10),
occurredAt: new Date('2024-01-01T00:00:00Z'),
createdAt: new Date('2024-01-01T00:00:00Z'),
source: {
type: 'race' as const,
id: 'race-456',
},
reason: {
code: 'DRIVING_FINISH_STRENGTH_GAIN',
summary: 'Finished 3rd in strong field',
details: { position: 3, fieldStrength: 2500 },
},
visibility: {
public: true,
redactedFields: [] as string[],
},
version: 1,
};
describe('create', () => {
it('should create a valid rating event', () => {
const event = RatingEvent.create(validProps);
expect(event.id.value).toBe(validProps.id.value);
expect(event.userId).toBe(validProps.userId);
expect(event.dimension.value).toBe('driving');
expect(event.delta.value).toBe(10);
expect(event.occurredAt).toEqual(validProps.occurredAt);
expect(event.source.type).toBe('race');
expect(event.reason.code).toBe('DRIVING_FINISH_STRENGTH_GAIN');
expect(event.visibility.public).toBe(true);
expect(event.version).toBe(1);
});
it('should create event with optional weight', () => {
const props = { ...validProps, weight: 2 };
const event = RatingEvent.create(props);
expect(event.weight).toBe(2);
});
it('should create event with non-public visibility', () => {
const props = {
...validProps,
visibility: { public: false, redactedFields: ['reason.summary'] },
};
const event = RatingEvent.create(props);
expect(event.visibility.public).toBe(false);
expect(event.visibility.redactedFields).toEqual(['reason.summary']);
});
it('should throw for missing userId', () => {
const props = { ...validProps, userId: '' };
expect(() => RatingEvent.create(props)).toThrow(IdentityDomainValidationError);
});
it('should throw for missing dimension', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { dimension: _dimension, ...rest } = validProps;
expect(() => RatingEvent.create(rest as typeof validProps)).toThrow(IdentityDomainValidationError);
});
it('should throw for missing delta', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { delta: _delta, ...rest } = validProps;
expect(() => RatingEvent.create(rest as typeof validProps)).toThrow(IdentityDomainValidationError);
});
it('should throw for missing source', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { source: _source, ...rest } = validProps;
expect(() => RatingEvent.create(rest as typeof validProps)).toThrow(IdentityDomainValidationError);
});
it('should throw for missing reason', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { reason: _reason, ...rest } = validProps;
expect(() => RatingEvent.create(rest as typeof validProps)).toThrow(IdentityDomainValidationError);
});
it('should throw for missing visibility', () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { visibility: _visibility, ...rest } = validProps;
expect(() => RatingEvent.create(rest as typeof validProps)).toThrow(IdentityDomainValidationError);
});
it('should throw for invalid version', () => {
const props = { ...validProps, version: 0 };
expect(() => RatingEvent.create(props)).toThrow(IdentityDomainValidationError);
});
it('should throw for future occurredAt', () => {
const futureDate = new Date(Date.now() + 86400000);
const props = { ...validProps, occurredAt: futureDate };
expect(() => RatingEvent.create(props)).toThrow(IdentityDomainValidationError);
});
it('should throw for future createdAt', () => {
const futureDate = new Date(Date.now() + 86400000);
const props = { ...validProps, createdAt: futureDate };
expect(() => RatingEvent.create(props)).toThrow(IdentityDomainValidationError);
});
it('should throw for occurredAt after createdAt', () => {
const props = {
...validProps,
occurredAt: new Date('2024-01-02T00:00:00Z'),
createdAt: new Date('2024-01-01T00:00:00Z'),
};
expect(() => RatingEvent.create(props)).toThrow(IdentityDomainInvariantError);
});
});
describe('rehydrate', () => {
it('should rehydrate event from stored data', () => {
const event = RatingEvent.rehydrate(validProps);
expect(event.id.value).toBe(validProps.id.value);
expect(event.userId).toBe(validProps.userId);
expect(event.dimension.value).toBe('driving');
});
it('should rehydrate with optional weight', () => {
const props = { ...validProps, weight: 2 };
const event = RatingEvent.rehydrate(props);
expect(event.weight).toBe(2);
});
});
describe('equals', () => {
it('should return true for same ID', () => {
const event1 = RatingEvent.create(validProps);
const event2 = RatingEvent.rehydrate(validProps);
expect(event1.equals(event2)).toBe(true);
});
it('should return false for different IDs', () => {
const event1 = RatingEvent.create(validProps);
const event2 = RatingEvent.create({
...validProps,
id: RatingEventId.create('123e4567-e89b-12d3-a456-426614174001'),
});
expect(event1.equals(event2)).toBe(false);
});
});
describe('toJSON', () => {
it('should return plain object representation', () => {
const event = RatingEvent.create(validProps);
const json = event.toJSON();
expect(json.id).toBe(validProps.id.value);
expect(json.userId).toBe(validProps.userId);
expect(json.dimension).toBe('driving');
expect(json.delta).toBe(10);
expect(json.source).toEqual({ type: 'race', id: 'race-456' });
});
});
});