test(22-01): add failing test for mergeTimeline
- Covers ascending chronological sort across reports/classifications/audit - Asserts discriminant kind + source fields per variant, plus stable tie-break order
This commit is contained in:
parent
86cffc45b8
commit
37f6657839
1 changed files with 63 additions and 0 deletions
63
lib/services/phishing-timeline.test.ts
Normal file
63
lib/services/phishing-timeline.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { mergeTimeline, type TimelineEntry } from './phishing-timeline';
|
||||
|
||||
describe('mergeTimeline', () => {
|
||||
const reports = [
|
||||
{ createdAt: '2026-07-15T00:00:00.000Z', reportId: 'report-1', ticketNumber: 'T-0001', companyName: 'Acme' },
|
||||
{ createdAt: '2026-07-15T04:00:00.000Z', reportId: 'report-2', ticketNumber: 'T-0002', companyName: 'Acme' },
|
||||
];
|
||||
const classifications = [
|
||||
{ createdAt: '2026-07-15T02:00:00.000Z', verdict: 'THREAT', confidence: '85' },
|
||||
];
|
||||
const auditEvents = [
|
||||
{ createdAt: '2026-07-15T01:00:00.000Z', eventType: 'remediation_approved', actor: 'lorentz@wulfconsulting.com', payload: { actions: ['block_sender'] } },
|
||||
{ createdAt: '2026-07-15T05:00:00.000Z', eventType: 'remediation_completed', actor: null, payload: {} },
|
||||
];
|
||||
|
||||
it('returns a single array sorted ascending by `at` across all 3 sources', () => {
|
||||
const result = mergeTimeline(reports, classifications, auditEvents);
|
||||
|
||||
expect(result).toHaveLength(5);
|
||||
const timestamps = result.map((entry) => new Date(entry.at).getTime());
|
||||
for (let i = 1; i < timestamps.length; i++) {
|
||||
expect(timestamps[i]).toBeGreaterThanOrEqual(timestamps[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
it('output length equals the sum of the three input array lengths', () => {
|
||||
const result = mergeTimeline(reports, classifications, auditEvents);
|
||||
expect(result).toHaveLength(reports.length + classifications.length + auditEvents.length);
|
||||
});
|
||||
|
||||
it('carries a discriminant `kind` and the source-specific fields for each entry', () => {
|
||||
const result = mergeTimeline(reports, classifications, auditEvents);
|
||||
|
||||
const reportEntry = result.find((e) => e.kind === 'report' && e.reportId === 'report-1');
|
||||
expect(reportEntry).toMatchObject({ kind: 'report', reportId: 'report-1', ticketNumber: 'T-0001', companyName: 'Acme' });
|
||||
|
||||
const classificationEntry = result.find((e) => e.kind === 'classification');
|
||||
expect(classificationEntry).toMatchObject({ kind: 'classification', verdict: 'THREAT', confidence: '85' });
|
||||
|
||||
const auditEntry = result.find((e) => e.kind === 'audit' && e.eventType === 'remediation_approved');
|
||||
expect(auditEntry).toMatchObject({
|
||||
kind: 'audit',
|
||||
eventType: 'remediation_approved',
|
||||
actor: 'lorentz@wulfconsulting.com',
|
||||
payload: { actions: ['block_sender'] },
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves a stable tie-break order (report, then classification, then audit) on identical timestamps and does not throw', () => {
|
||||
const tiedAt = '2026-07-15T10:00:00.000Z';
|
||||
const tiedReports = [{ createdAt: tiedAt, reportId: 'r', ticketNumber: 'T', companyName: 'C' }];
|
||||
const tiedClassifications = [{ createdAt: tiedAt, verdict: 'SPAM', confidence: '50' }];
|
||||
const tiedAudit = [{ createdAt: tiedAt, eventType: 'campaign_marked_false_positive', actor: null, payload: {} }];
|
||||
|
||||
let result: TimelineEntry[] = [];
|
||||
expect(() => {
|
||||
result = mergeTimeline(tiedReports, tiedClassifications, tiedAudit);
|
||||
}).not.toThrow();
|
||||
|
||||
expect(result.map((e) => e.kind)).toEqual(['report', 'classification', 'audit']);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue