Files
gridpilot.gg/core/shared/domain/DomainEvent.test.ts
Marc Mintel bf2c0fdb0c
Some checks failed
CI / lint-typecheck (pull_request) Failing after 1m29s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
code quality
2026-01-26 01:54:57 +01:00

298 lines
9.2 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { DomainEvent, DomainEventPublisher, DomainEventAlias } from './DomainEvent';
describe('DomainEvent', () => {
describe('DomainEvent interface', () => {
it('should have required properties', () => {
const event: DomainEvent<{ userId: string }> = {
eventType: 'USER_CREATED',
aggregateId: 'user-123',
eventData: { userId: 'user-123' },
occurredAt: new Date('2024-01-01T00:00:00Z')
};
expect(event.eventType).toBe('USER_CREATED');
expect(event.aggregateId).toBe('user-123');
expect(event.eventData).toEqual({ userId: 'user-123' });
expect(event.occurredAt).toEqual(new Date('2024-01-01T00:00:00Z'));
});
it('should support different event data types', () => {
const stringEvent: DomainEvent<string> = {
eventType: 'STRING_EVENT',
aggregateId: 'agg-1',
eventData: 'some data',
occurredAt: new Date()
};
const objectEvent: DomainEvent<{ id: number; name: string }> = {
eventType: 'OBJECT_EVENT',
aggregateId: 'agg-2',
eventData: { id: 1, name: 'test' },
occurredAt: new Date()
};
const arrayEvent: DomainEvent<string[]> = {
eventType: 'ARRAY_EVENT',
aggregateId: 'agg-3',
eventData: ['a', 'b', 'c'],
occurredAt: new Date()
};
expect(stringEvent.eventData).toBe('some data');
expect(objectEvent.eventData).toEqual({ id: 1, name: 'test' });
expect(arrayEvent.eventData).toEqual(['a', 'b', 'c']);
});
it('should support default unknown type', () => {
const event: DomainEvent = {
eventType: 'UNKNOWN_EVENT',
aggregateId: 'agg-1',
eventData: { any: 'data' },
occurredAt: new Date()
};
expect(event.eventType).toBe('UNKNOWN_EVENT');
expect(event.aggregateId).toBe('agg-1');
});
it('should support complex event data structures', () => {
interface ComplexEventData {
userId: string;
changes: {
field: string;
oldValue: unknown;
newValue: unknown;
}[];
metadata: {
source: string;
timestamp: string;
};
}
const event: DomainEvent<ComplexEventData> = {
eventType: 'USER_UPDATED',
aggregateId: 'user-456',
eventData: {
userId: 'user-456',
changes: [
{ field: 'email', oldValue: 'old@example.com', newValue: 'new@example.com' }
],
metadata: {
source: 'admin-panel',
timestamp: '2024-01-01T12:00:00Z'
}
},
occurredAt: new Date('2024-01-01T12:00:00Z')
};
expect(event.eventData.userId).toBe('user-456');
expect(event.eventData.changes).toHaveLength(1);
expect(event.eventData.metadata.source).toBe('admin-panel');
});
});
describe('DomainEventPublisher interface', () => {
it('should have publish method', async () => {
const mockPublisher: DomainEventPublisher = {
publish: async () => {
// Mock implementation
return Promise.resolve();
}
};
const event: DomainEvent<{ message: string }> = {
eventType: 'TEST_EVENT',
aggregateId: 'test-1',
eventData: { message: 'test' },
occurredAt: new Date()
};
// Should not throw
await expect(mockPublisher.publish(event)).resolves.toBeUndefined();
});
it('should support async publish operations', async () => {
const publishedEvents: DomainEvent[] = [];
const mockPublisher: DomainEventPublisher = {
publish: async (event: DomainEvent) => {
publishedEvents.push(event);
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 10));
return Promise.resolve();
}
};
const event1: DomainEvent = {
eventType: 'EVENT_1',
aggregateId: 'agg-1',
eventData: { data: 'value1' },
occurredAt: new Date()
};
const event2: DomainEvent = {
eventType: 'EVENT_2',
aggregateId: 'agg-2',
eventData: { data: 'value2' },
occurredAt: new Date()
};
await mockPublisher.publish(event1);
await mockPublisher.publish(event2);
expect(publishedEvents).toHaveLength(2);
expect(publishedEvents[0].eventType).toBe('EVENT_1');
expect(publishedEvents[1].eventType).toBe('EVENT_2');
});
});
describe('DomainEvent behavior', () => {
it('should support event ordering by occurredAt', () => {
const events: DomainEvent[] = [
{
eventType: 'EVENT_3',
aggregateId: 'agg-3',
eventData: {},
occurredAt: new Date('2024-01-03T00:00:00Z')
},
{
eventType: 'EVENT_1',
aggregateId: 'agg-1',
eventData: {},
occurredAt: new Date('2024-01-01T00:00:00Z')
},
{
eventType: 'EVENT_2',
aggregateId: 'agg-2',
eventData: {},
occurredAt: new Date('2024-01-02T00:00:00Z')
}
];
const sorted = [...events].sort((a, b) =>
a.occurredAt.getTime() - b.occurredAt.getTime()
);
expect(sorted[0].eventType).toBe('EVENT_1');
expect(sorted[1].eventType).toBe('EVENT_2');
expect(sorted[2].eventType).toBe('EVENT_3');
});
it('should support filtering events by aggregateId', () => {
const events: DomainEvent[] = [
{ eventType: 'EVENT_1', aggregateId: 'user-1', eventData: {}, occurredAt: new Date() },
{ eventType: 'EVENT_2', aggregateId: 'user-2', eventData: {}, occurredAt: new Date() },
{ eventType: 'EVENT_3', aggregateId: 'user-1', eventData: {}, occurredAt: new Date() }
];
const user1Events = events.filter(e => e.aggregateId === 'user-1');
expect(user1Events).toHaveLength(2);
expect(user1Events[0].eventType).toBe('EVENT_1');
expect(user1Events[1].eventType).toBe('EVENT_3');
});
it('should support event replay from event store', () => {
// Simulating event replay pattern
const eventStore: DomainEvent[] = [
{
eventType: 'USER_CREATED',
aggregateId: 'user-123',
eventData: { userId: 'user-123', name: 'John' },
occurredAt: new Date('2024-01-01T00:00:00Z')
},
{
eventType: 'USER_UPDATED',
aggregateId: 'user-123',
eventData: { userId: 'user-123', email: 'john@example.com' },
occurredAt: new Date('2024-01-02T00:00:00Z')
}
];
// Replay events to build current state
let currentState: { userId: string; name?: string; email?: string } = { userId: 'user-123' };
for (const event of eventStore) {
if (event.eventType === 'USER_CREATED') {
const data = event.eventData as { userId: string; name: string };
currentState.name = data.name;
} else if (event.eventType === 'USER_UPDATED') {
const data = event.eventData as { userId: string; email: string };
currentState.email = data.email;
}
}
expect(currentState.name).toBe('John');
expect(currentState.email).toBe('john@example.com');
});
it('should support event sourcing pattern', () => {
// Event sourcing: state is derived from events
interface AccountState {
balance: number;
transactions: number;
}
const events: DomainEvent[] = [
{
eventType: 'ACCOUNT_CREATED',
aggregateId: 'account-1',
eventData: { initialBalance: 100 },
occurredAt: new Date('2024-01-01T00:00:00Z')
},
{
eventType: 'DEPOSIT',
aggregateId: 'account-1',
eventData: { amount: 50 },
occurredAt: new Date('2024-01-02T00:00:00Z')
},
{
eventType: 'WITHDRAWAL',
aggregateId: 'account-1',
eventData: { amount: 30 },
occurredAt: new Date('2024-01-03T00:00:00Z')
}
];
const state: AccountState = {
balance: 0,
transactions: 0
};
for (const event of events) {
switch (event.eventType) {
case 'ACCOUNT_CREATED':
state.balance = (event.eventData as { initialBalance: number }).initialBalance;
state.transactions = 1;
break;
case 'DEPOSIT':
state.balance += (event.eventData as { amount: number }).amount;
state.transactions += 1;
break;
case 'WITHDRAWAL':
state.balance -= (event.eventData as { amount: number }).amount;
state.transactions += 1;
break;
}
}
expect(state.balance).toBe(120); // 100 + 50 - 30
expect(state.transactions).toBe(3);
});
});
describe('DomainEventAlias type', () => {
it('should be assignable to DomainEvent', () => {
const alias: DomainEventAlias<{ id: string }> = {
eventType: 'TEST',
aggregateId: 'agg-1',
eventData: { id: 'test' },
occurredAt: new Date()
};
expect(alias.eventType).toBe('TEST');
expect(alias.aggregateId).toBe('agg-1');
});
});
});