import EventStore from '../EventSourcing/EventStore'; import type DomainEvent from '../Domain/Event/DomainEvent'; import EventSourcedAggregateRoot from "../Domain/EventSourcedAggregateRoot"; import { Identity } from "../Domain/AggregateRoot"; /** * BDD-style scenario testing utility for event-sourced aggregates. * * Provides a fluent interface for writing Given-When-Then style tests: * - `given()`: Sets up initial aggregate state from historical events * - `when()`: Executes a command or operation on the aggregate * - `then()`: Asserts expected domain events were raised * * @typeParam T - The event-sourced aggregate root type being tested * * @example * ```typescript * const scenario = new Scenario(Order, eventStore); * * await scenario * .withAggregateId('order-123') * .given([new OrderCreated('order-123')]) * * scenario.when((order) => { * order.cancel(); * return order; * }); * * scenario.then([new OrderCancelled('order-123')]); * ``` */ export default class Scenario { private readonly aggregateFactory; private readonly eventStore; private aggregateId; private aggregateInstance?; /** * Creates a new test scenario for an event-sourced aggregate. * * @param aggregateFactory - Constructor function for the aggregate root * @param eventStore - EventStore instance (typically created with createTestEventStore) */ constructor(aggregateFactory: new (aggregateRootId: Identity) => T, eventStore: EventStore); /** * Sets the aggregate ID for this test scenario. * Call this before `given()` to test a specific aggregate instance. * Accepts either a UUID string or an Identity instance for convenience. * * @param aggregateId - The aggregate root identifier (string UUID or Identity) * @returns This scenario instance for method chaining */ withAggregateId(aggregateId: string | Identity): Scenario; /** * Sets up the initial state of the aggregate from historical events (Given). * Events are appended to the event store and replayed to reconstitute the aggregate. * * @param events - Array of domain events representing the aggregate's history */ given(events: DomainEvent[]): Promise; /** * Executes a command or operation on the aggregate (When). * The callable receives the aggregate instance (or undefined for new aggregates) * and must return the aggregate after applying changes. * * @param callable - Function that executes the command/operation on the aggregate */ when(callable: (aggregate?: T) => T): void; /** * Asserts that the expected domain events were raised (Then). * Compares the uncommitted events from the aggregate against the expected events. * Note: occurredAt timestamps are ignored in comparison to avoid flaky tests. * * @param thens - Array of expected domain events */ then(thens: object[] | DomainEvent[]): void; /** * Normalizes an event for comparison by removing timestamp fields. */ private normalizeEvent; private events; }