199 lines
6.7 KiB
TypeScript
199 lines
6.7 KiB
TypeScript
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<string, Report>();
|
|
|
|
async save(report: Report): Promise<void> {
|
|
this.store.set(report.id.toString(), report);
|
|
}
|
|
|
|
async findById(id: string): Promise<Report | undefined> {
|
|
return this.store.get(id);
|
|
}
|
|
|
|
async findAll(): Promise<Report[]> {
|
|
return Array.from(this.store.values());
|
|
}
|
|
|
|
async update(report: Report): Promise<void> {
|
|
this.store.set(report.id.toString(), report);
|
|
}
|
|
}
|
|
|
|
// ─── Mock EventBus ────────────────────────────────────────────────────────────
|
|
|
|
class MockEventBus implements EventBus {
|
|
published: DomainEvent[] = [];
|
|
|
|
async publish(event: DomainEvent): Promise<void> {
|
|
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');
|
|
});
|
|
});
|