import { Report } from '../../src/modules/reporting/domain/entities/Report'; import { ReportFormat } from '../../src/modules/reporting/domain/value-objects/ReportFormat'; import { ReportStatus } from '../../src/modules/reporting/domain/value-objects/ReportStatus'; import { GenerateReportCommand } from '../../src/modules/reporting/application/commands/GenerateReportCommand'; import { IReportRepository } from '../../src/modules/reporting/domain/ports/IReportRepository'; import { EventBus } from '../../src/shared/application/EventBus'; import { DomainEvent } from '../../src/shared/domain/DomainEvent'; import { EventHandler } from '../../src/shared/application/EventHandler'; // ─── Mock Repository ────────────────────────────────────────────────────────── class InMemoryReportRepository implements IReportRepository { private store = new Map(); async save(report: Report): Promise { this.store.set(report.id.toString(), report); } async findById(id: string): Promise { return this.store.get(id); } async findAll(): Promise { return Array.from(this.store.values()); } async update(report: Report): Promise { this.store.set(report.id.toString(), report); } } // ─── Mock EventBus ──────────────────────────────────────────────────────────── class MockEventBus implements EventBus { published: DomainEvent[] = []; async publish(event: DomainEvent): Promise { this.published.push(event); } subscribe(_name: string, _handler: EventHandler): void { // no-op } } // ─── Report domain tests ─────────────────────────────────────────────────────── describe('ReportFormat', () => { it('parses valid formats', () => { expect(ReportFormat.fromString('html').value).toBe('html'); expect(ReportFormat.fromString('json').value).toBe('json'); expect(ReportFormat.fromString('pdf').value).toBe('pdf'); }); it('throws on invalid format', () => { expect(() => ReportFormat.fromString('xml')).toThrow(); }); }); describe('ReportStatus', () => { it('creates statuses', () => { expect(ReportStatus.pending().value).toBe('pending'); expect(ReportStatus.generating().value).toBe('generating'); expect(ReportStatus.ready().value).toBe('ready'); expect(ReportStatus.failed().value).toBe('failed'); }); it('parses from string', () => { expect(ReportStatus.fromString('ready').value).toBe('ready'); }); it('throws on unknown status', () => { expect(() => ReportStatus.fromString('unknown')).toThrow(); }); }); describe('Report aggregate', () => { it('creates a report with pending status and emits ReportRequested event', () => { const report = Report.create({ title: 'Test Report', format: ReportFormat.fromString('html'), filters: { severity: 'high' }, }); expect(report.title).toBe('Test Report'); expect(report.status.value).toBe('pending'); expect(report.totalFindings).toBe(0); const events = report.clearEvents(); expect(events).toHaveLength(1); expect(events[0]!.eventName).toBe('reporting.report_requested'); }); it('marks report as generating', () => { const report = Report.create({ title: 'T', format: ReportFormat.fromString('json'), filters: {}, }); report.clearEvents(); report.markGenerating(); expect(report.status.value).toBe('generating'); }); it('marks report as ready and emits ReportGenerated', () => { const report = Report.create({ title: 'T', format: ReportFormat.fromString('json'), filters: {}, }); report.clearEvents(); report.markReady('/reports/123/report.json', 5); expect(report.status.value).toBe('ready'); expect(report.filePath).toBe('/reports/123/report.json'); expect(report.totalFindings).toBe(5); expect(report.completedAt).toBeInstanceOf(Date); const events = report.clearEvents(); expect(events[0]!.eventName).toBe('reporting.report_generated'); }); it('marks report as failed and emits ReportFailed', () => { const report = Report.create({ title: 'T', format: ReportFormat.fromString('pdf'), filters: {}, }); report.clearEvents(); report.markFailed('Playwright error'); expect(report.status.value).toBe('failed'); expect(report.errorMessage).toBe('Playwright error'); const events = report.clearEvents(); expect(events[0]!.eventName).toBe('reporting.report_failed'); }); }); // ─── GenerateReportCommand tests ─────────────────────────────────────────────── describe('GenerateReportCommand', () => { let repo: InMemoryReportRepository; let eventBus: MockEventBus; let cmd: GenerateReportCommand; beforeEach(() => { repo = new InMemoryReportRepository(); eventBus = new MockEventBus(); cmd = new GenerateReportCommand(repo, eventBus); }); it('creates and persists a report, returns Ok with reportId', async () => { const result = await cmd.execute({ title: 'My Report', format: 'html' }); expect(result.ok).toBe(true); if (!result.ok) return; const { reportId, status } = result.value; expect(status).toBe('pending'); const saved = await repo.findById(reportId); expect(saved).toBeDefined(); expect(saved!.title).toBe('My Report'); expect(saved!.format.value).toBe('html'); }); it('publishes ReportRequested event after creation', async () => { await cmd.execute({ title: 'Report', format: 'json' }); expect(eventBus.published).toHaveLength(1); expect(eventBus.published[0]!.eventName).toBe('reporting.report_requested'); }); it('applies filters when provided', async () => { const result = await cmd.execute({ title: 'Filtered', format: 'pdf', filters: { sessionId: 'abc-123', severity: 'critical' }, }); expect(result.ok).toBe(true); if (!result.ok) return; const saved = await repo.findById(result.value.reportId); expect(saved!.filters.sessionId).toBe('abc-123'); expect(saved!.filters.severity).toBe('critical'); }); it('returns Err for invalid format', async () => { const result = await cmd.execute({ title: 'Bad', format: 'xml' as 'html' }); expect(result.ok).toBe(false); if (result.ok) return; expect(result.error).toContain('Invalid format'); }); });