- Cover verdict/confidence rendering, reasons/summary sections - Cover both BlastRadiusResult branches (ok and unavailable) - Cover remediation-state rendering (empty and populated) - Cover URL sanitization and null-verdict graceful handling
104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { formatTriageNote, type TriageNoteEvidence } from './triage-note-format';
|
|
import type { BlastRadiusResult } from './mimecast-blast-radius';
|
|
|
|
function baseEvidence(overrides: Partial<TriageNoteEvidence> = {}): TriageNoteEvidence {
|
|
return {
|
|
campaignId: 'campaign-1',
|
|
reportCount: 3,
|
|
companyName: 'Acme Corp',
|
|
subject: 'Your invoice is ready',
|
|
verdict: 'THREAT',
|
|
confidence: 0.85,
|
|
summary: 'Credential-harvesting link found in message body.',
|
|
reasons: ['Sender domain not in allowlist', 'URL matches known phishing indicator'],
|
|
recommendedActions: ['block_sender', 'purge_message'],
|
|
requiresApproval: true,
|
|
blastRadius: { status: 'unavailable', reason: 'not_configured' },
|
|
remediationActions: [],
|
|
urls: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('formatTriageNote', () => {
|
|
it('includes the verdict label and confidence value', () => {
|
|
const output = formatTriageNote(baseEvidence());
|
|
expect(output).toContain('THREAT');
|
|
expect(output).toContain('0.85');
|
|
});
|
|
|
|
it('includes a Summary line and a Reasons section listing each reason', () => {
|
|
const output = formatTriageNote(baseEvidence());
|
|
expect(output).toContain('Credential-harvesting link found in message body.');
|
|
expect(output).toContain('Sender domain not in allowlist');
|
|
expect(output).toContain('URL matches known phishing indicator');
|
|
});
|
|
|
|
it('shows delivered/held/rejected/clicked counts when blast radius is ok', () => {
|
|
const ok: BlastRadiusResult = {
|
|
status: 'ok',
|
|
matched: 5,
|
|
delivered: 3,
|
|
held: 1,
|
|
rejected: 1,
|
|
clicked: 2,
|
|
perRecipient: [{ recipient: 'user@acme.example', status: 'delivered' }],
|
|
source: 'fan-out',
|
|
};
|
|
const output = formatTriageNote(baseEvidence({ blastRadius: ok }));
|
|
expect(output).toContain('3');
|
|
expect(output).toContain('1');
|
|
expect(output).toContain('2');
|
|
});
|
|
|
|
it('shows an explicit unavailable reason when blast radius is unavailable', () => {
|
|
const output = formatTriageNote(
|
|
baseEvidence({ blastRadius: { status: 'unavailable', reason: 'not_configured' } })
|
|
);
|
|
expect(output).toContain('unavailable');
|
|
expect(output).toContain('not_configured');
|
|
});
|
|
|
|
it('states no action taken when remediationActions is empty', () => {
|
|
const output = formatTriageNote(baseEvidence({ remediationActions: [] }));
|
|
expect(output.toLowerCase()).toMatch(/proposed|no action/);
|
|
});
|
|
|
|
it('lists each remediation action with status and approver when present', () => {
|
|
const output = formatTriageNote(
|
|
baseEvidence({
|
|
remediationActions: [
|
|
{ actionType: 'block_sender', status: 'approved', approvedBy: 'operator@example.com', approvedAt: '2026-07-01T00:00:00Z' },
|
|
{ actionType: 'purge_message', status: 'completed', approvedBy: 'operator@example.com', approvedAt: '2026-07-01T00:00:00Z' },
|
|
],
|
|
})
|
|
);
|
|
expect(output).toContain('block_sender');
|
|
expect(output).toContain('approved');
|
|
expect(output).toContain('purge_message');
|
|
expect(output).toContain('completed');
|
|
expect(output).toContain('operator@example.com');
|
|
});
|
|
|
|
it('renders indicator URLs in sanitized form, dropping query strings', () => {
|
|
const output = formatTriageNote(baseEvidence({ urls: ['http://evil.example/p?token=leak'] }));
|
|
expect(output).not.toContain('token=leak');
|
|
expect(output).toContain('http://evil.example/p');
|
|
});
|
|
|
|
it('never leaks a secret embedded in free-text fields (sanitizeNoteText applied to whole output)', () => {
|
|
const output = formatTriageNote(
|
|
baseEvidence({ summary: 'Found link http://evil.example/x?access_token=SECRETVALUE in body' })
|
|
);
|
|
expect(output).not.toContain('access_token=SECRETVALUE');
|
|
});
|
|
|
|
it('handles null verdict/confidence gracefully without throwing', () => {
|
|
expect(() =>
|
|
formatTriageNote(baseEvidence({ verdict: null, confidence: null }))
|
|
).not.toThrow();
|
|
const output = formatTriageNote(baseEvidence({ verdict: null, confidence: null }));
|
|
expect(output.toLowerCase()).toContain('not yet classified');
|
|
});
|
|
});
|