feat(listings): add source field to PriceHistory + unit tests

- Add `source` column to PriceHistory Prisma model (manual_update, admin_override, market_adjustment)
- Add migration for the new column with default 'manual_update'
- Update ListingPriceChangedEvent domain event with optional source parameter
- Update RecordPriceHistoryHandler to persist source
- Update GetPriceHistoryHandler to return source in query results
- Add unit tests for RecordPriceHistoryHandler (5 cases)
- Add unit tests for GetPriceHistoryHandler (3 cases)
- Add ListingPriceChangedEvent tests to domain events spec (4 cases)
- Add getPriceHistory controller tests (2 cases)

All 1805 tests pass, typecheck clean.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 17:43:48 +07:00
parent f3a2a012c4
commit 6cf2c23170
9 changed files with 213 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { ListingApprovedEvent } from '../events/listing-approved.event';
import { ListingCreatedEvent } from '../events/listing-created.event';
import { ListingPriceChangedEvent } from '../events/listing-price-changed.event';
import { ListingSoldEvent } from '../events/listing-sold.event';
import { ListingStatusChangedEvent } from '../events/listing-status-changed.event';
@@ -51,6 +52,34 @@ describe('Listings Domain Events', () => {
});
});
describe('ListingPriceChangedEvent', () => {
it('creates event with correct properties', () => {
const event = new ListingPriceChangedEvent('listing-1', 5_000_000_000n, 6_000_000_000n, 'manual_update');
expect(event.eventName).toBe('listing.price_changed');
expect(event.aggregateId).toBe('listing-1');
expect(event.oldPrice).toBe(5_000_000_000n);
expect(event.newPrice).toBe(6_000_000_000n);
expect(event.source).toBe('manual_update');
expect(event.occurredAt).toBeInstanceOf(Date);
});
it('defaults source to manual_update', () => {
const event = new ListingPriceChangedEvent('listing-2', 1_000_000n, 2_000_000n);
expect(event.source).toBe('manual_update');
});
it('accepts admin_override source', () => {
const event = new ListingPriceChangedEvent('listing-3', 1n, 2n, 'admin_override');
expect(event.source).toBe('admin_override');
});
it('accepts market_adjustment source', () => {
const event = new ListingPriceChangedEvent('listing-4', 1n, 2n, 'market_adjustment');
expect(event.source).toBe('market_adjustment');
});
});
describe('ListingStatusChangedEvent', () => {
it('creates event with correct properties', () => {
const event = new ListingStatusChangedEvent('listing-1', 'prop-1', 'DRAFT', 'PENDING_REVIEW');

View File

@@ -1,5 +1,7 @@
import type { DomainEvent } from '@modules/shared';
export type PriceChangeSource = 'manual_update' | 'admin_override' | 'market_adjustment';
export class ListingPriceChangedEvent implements DomainEvent {
readonly eventName = 'listing.price_changed';
readonly occurredAt = new Date();
@@ -8,5 +10,6 @@ export class ListingPriceChangedEvent implements DomainEvent {
public readonly aggregateId: string,
public readonly oldPrice: bigint,
public readonly newPrice: bigint,
public readonly source: PriceChangeSource = 'manual_update',
) {}
}