feat(api): complete domain event publishing with aggregate root pattern
- Add getUncommittedEvents() and commit() to AggregateRoot base class - Create 6 new domain events: SubscriptionExpired, SubscriptionRenewed, ListingStatusChanged, UserKycUpdated, UserDeactivated, PaymentRefunded - Wire events into entity state changes: SubscriptionEntity (markExpired, renewPeriod), ListingEntity (all transitions), UserEntity (KYC, deactivate), PaymentEntity (markRefunded) - Add 7 new event listeners across notifications, admin, and search modules (25 total @OnEvent handlers) - Fix ReviewDeletedListener to handle LISTING target type - Restore watcher notifications in ListingSoldListener - Update barrel exports and module registrations Resolves: TEC-1564 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -40,4 +40,56 @@ describe('AggregateRoot', () => {
|
||||
});
|
||||
expect(agg.domainEvents).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should return uncommitted events without clearing via getUncommittedEvents()', () => {
|
||||
const agg = new TestAggregate('agg-1');
|
||||
agg.doSomething();
|
||||
agg.doSomething();
|
||||
|
||||
const events = agg.getUncommittedEvents();
|
||||
expect(events).toHaveLength(2);
|
||||
// Should not clear events
|
||||
expect(agg.domainEvents).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should return defensive copy from getUncommittedEvents()', () => {
|
||||
const agg = new TestAggregate('agg-1');
|
||||
agg.doSomething();
|
||||
|
||||
const events = agg.getUncommittedEvents();
|
||||
events.push({ eventName: 'Fake', occurredAt: new Date(), aggregateId: 'x' });
|
||||
expect(agg.getUncommittedEvents()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should clear and return events via commit()', () => {
|
||||
const agg = new TestAggregate('agg-1');
|
||||
agg.doSomething();
|
||||
agg.doSomething();
|
||||
agg.doSomething();
|
||||
|
||||
const events = agg.commit();
|
||||
expect(events).toHaveLength(3);
|
||||
expect(agg.domainEvents).toHaveLength(0);
|
||||
expect(agg.getUncommittedEvents()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('commit() should behave identically to clearDomainEvents()', () => {
|
||||
const agg1 = new TestAggregate('agg-1');
|
||||
const agg2 = new TestAggregate('agg-2');
|
||||
agg1.doSomething();
|
||||
agg2.doSomething();
|
||||
|
||||
const cleared = agg1.clearDomainEvents();
|
||||
const committed = agg2.commit();
|
||||
|
||||
expect(cleared).toHaveLength(committed.length);
|
||||
expect(agg1.domainEvents).toHaveLength(0);
|
||||
expect(agg2.domainEvents).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return empty arrays when no events exist', () => {
|
||||
const agg = new TestAggregate('agg-1');
|
||||
expect(agg.getUncommittedEvents()).toHaveLength(0);
|
||||
expect(agg.commit()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,13 +8,33 @@ export abstract class AggregateRoot<TId = string> extends BaseEntity<TId> {
|
||||
return [...this._domainEvents];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all domain events that have not yet been published.
|
||||
* Use this to inspect pending events before committing.
|
||||
*/
|
||||
getUncommittedEvents(): DomainEvent[] {
|
||||
return [...this._domainEvents];
|
||||
}
|
||||
|
||||
protected addDomainEvent(event: DomainEvent): void {
|
||||
this._domainEvents.push(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears and returns all uncommitted domain events.
|
||||
* Call this after persisting the aggregate and before publishing events.
|
||||
*/
|
||||
clearDomainEvents(): DomainEvent[] {
|
||||
const events = [...this._domainEvents];
|
||||
this._domainEvents = [];
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for clearDomainEvents(). Marks all pending events as committed
|
||||
* by clearing the internal event list and returning them for publishing.
|
||||
*/
|
||||
commit(): DomainEvent[] {
|
||||
return this.clearDomainEvents();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user