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:
@@ -1,5 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { AgentVerifiedEvent } from '../events/agent-verified.event';
|
||||
import { UserDeactivatedEvent } from '../events/user-deactivated.event';
|
||||
import { UserKycUpdatedEvent } from '../events/user-kyc-updated.event';
|
||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||
|
||||
describe('Auth Domain Events', () => {
|
||||
@@ -35,4 +37,32 @@ describe('Auth Domain Events', () => {
|
||||
expect(event.occurredAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UserKycUpdatedEvent', () => {
|
||||
it('creates event with correct properties', () => {
|
||||
const event = new UserKycUpdatedEvent('user-1', 'APPROVED', 'PENDING');
|
||||
|
||||
expect(event.eventName).toBe('user.kyc_updated');
|
||||
expect(event.aggregateId).toBe('user-1');
|
||||
expect(event.newStatus).toBe('APPROVED');
|
||||
expect(event.previousStatus).toBe('PENDING');
|
||||
expect(event.occurredAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('creates event for rejection', () => {
|
||||
const event = new UserKycUpdatedEvent('user-2', 'REJECTED', 'PENDING');
|
||||
expect(event.newStatus).toBe('REJECTED');
|
||||
expect(event.previousStatus).toBe('PENDING');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UserDeactivatedEvent', () => {
|
||||
it('creates event with correct properties', () => {
|
||||
const event = new UserDeactivatedEvent('user-1');
|
||||
|
||||
expect(event.eventName).toBe('user.deactivated');
|
||||
expect(event.aggregateId).toBe('user-1');
|
||||
expect(event.occurredAt).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,20 +38,30 @@ describe('UserEntity', () => {
|
||||
expect(user.email?.value).toBe('test@example.com');
|
||||
});
|
||||
|
||||
it('should update KYC status', () => {
|
||||
it('should update KYC status and emit UserKycUpdatedEvent', () => {
|
||||
const user = UserEntity.createNew('user-3', phone, 'Lê Văn C', passwordHash);
|
||||
user.clearDomainEvents();
|
||||
user.updateKycStatus('PENDING', { idCard: '123456789' });
|
||||
|
||||
expect(user.kycStatus).toBe('PENDING');
|
||||
expect(user.kycData).toEqual({ idCard: '123456789' });
|
||||
|
||||
const events = user.domainEvents;
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].eventName).toBe('user.kyc_updated');
|
||||
});
|
||||
|
||||
it('should deactivate user', () => {
|
||||
it('should deactivate user and emit UserDeactivatedEvent', () => {
|
||||
const user = UserEntity.createNew('user-4', phone, 'Phạm Thị D', passwordHash);
|
||||
user.clearDomainEvents();
|
||||
expect(user.isActive).toBe(true);
|
||||
|
||||
user.deactivate();
|
||||
expect(user.isActive).toBe(false);
|
||||
|
||||
const events = user.domainEvents;
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].eventName).toBe('user.deactivated');
|
||||
});
|
||||
|
||||
it('should clear domain events', () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { type UserRole, type KYCStatus } from '@prisma/client';
|
||||
import { AggregateRoot } from '@modules/shared';
|
||||
import { UserDeactivatedEvent } from '../events/user-deactivated.event';
|
||||
import { UserKycUpdatedEvent } from '../events/user-kyc-updated.event';
|
||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||
import { type Email } from '../value-objects/email.vo';
|
||||
import { type HashedPassword } from '../value-objects/hashed-password.vo';
|
||||
@@ -76,14 +78,19 @@ export class UserEntity extends AggregateRoot<string> {
|
||||
}
|
||||
|
||||
updateKycStatus(status: KYCStatus, kycData?: unknown): void {
|
||||
const previousStatus = this._kycStatus;
|
||||
this._kycStatus = status;
|
||||
if (kycData !== undefined) this._kycData = kycData;
|
||||
this.updatedAt = new Date();
|
||||
|
||||
this.addDomainEvent(new UserKycUpdatedEvent(this.id, status, previousStatus));
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
this._isActive = false;
|
||||
this.updatedAt = new Date();
|
||||
|
||||
this.addDomainEvent(new UserDeactivatedEvent(this.id));
|
||||
}
|
||||
|
||||
activate(): void {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type DomainEvent } from '@modules/shared';
|
||||
|
||||
export class UserDeactivatedEvent implements DomainEvent {
|
||||
readonly eventName = 'user.deactivated';
|
||||
readonly occurredAt = new Date();
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: string,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { type KYCStatus } from '@prisma/client';
|
||||
import { type DomainEvent } from '@modules/shared';
|
||||
|
||||
export class UserKycUpdatedEvent implements DomainEvent {
|
||||
readonly eventName = 'user.kyc_updated';
|
||||
readonly occurredAt = new Date();
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: string,
|
||||
public readonly newStatus: KYCStatus,
|
||||
public readonly previousStatus: KYCStatus,
|
||||
) {}
|
||||
}
|
||||
@@ -8,5 +8,7 @@ export { UserEntity, type UserProps } from './domain/entities/user.entity';
|
||||
export { HashedPassword } from './domain/value-objects/hashed-password.vo';
|
||||
export { Phone } from './domain/value-objects/phone.vo';
|
||||
export { AgentVerifiedEvent } from './domain/events/agent-verified.event';
|
||||
export { UserDeactivatedEvent } from './domain/events/user-deactivated.event';
|
||||
export { UserKycUpdatedEvent } from './domain/events/user-kyc-updated.event';
|
||||
export { UserRegisteredEvent } from './domain/events/user-registered.event';
|
||||
export { USER_REPOSITORY, type IUserRepository } from './domain/repositories/user.repository';
|
||||
|
||||
Reference in New Issue
Block a user