From 0e7daf9a6a32b5bba3c51c0c0e0e4a7749b52a6d Mon Sep 17 00:00:00 2001 From: lorentz Date: Wed, 15 Jul 2026 07:41:14 -0400 Subject: [PATCH] test(15-02): add failing tests for phishing pattern matcher + content hash - Covers all 8 locked DETECT-01 patterns individually, negative case, case-insensitivity, and content-hash stability/change/null-normalization --- lib/services/phishing-detector.test.ts | 106 +++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 lib/services/phishing-detector.test.ts diff --git a/lib/services/phishing-detector.test.ts b/lib/services/phishing-detector.test.ts new file mode 100644 index 0000000..aa7bb8a --- /dev/null +++ b/lib/services/phishing-detector.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect } from 'vitest'; +import { + KNOWN_PHISHING_PATTERNS, + matchesPhishingPatterns, + computePhishingContentHash, +} from './phishing-detector'; + +describe('KNOWN_PHISHING_PATTERNS', () => { + it('has exactly 8 locked patterns', () => { + expect(KNOWN_PHISHING_PATTERNS).toHaveLength(8); + }); +}); + +describe('matchesPhishingPatterns', () => { + it('flags a title containing "Phishing Report"', () => { + const result = matchesPhishingPatterns('Fwd: Phishing Report', null); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Phishing Report'); + }); + + it('flags a title containing "Spam Alert"', () => { + const result = matchesPhishingPatterns('Spam Alert - user reported', null); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Spam Alert'); + }); + + it('flags a title containing "Phishing Alert - Email Security Report"', () => { + const result = matchesPhishingPatterns('Phishing Alert - Email Security Report', null); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Phishing Alert - Email Security Report'); + }); + + it('flags a description containing "KnowBe4 Phish Alert Report"', () => { + const result = matchesPhishingPatterns(null, 'This is a KnowBe4 Phish Alert Report for review'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('KnowBe4 Phish Alert Report'); + }); + + it('flags a description containing "Source: KnowBe4 Phish Alert Button"', () => { + const result = matchesPhishingPatterns(null, 'Source: KnowBe4 Phish Alert Button'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Source: KnowBe4 Phish Alert Button'); + }); + + it('flags a description containing "userSubmissionsReportMessage"', () => { + const result = matchesPhishingPatterns(null, 'Generated by userSubmissionsReportMessage flow'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('userSubmissionsReportMessage'); + }); + + it('flags a description containing "reported message destinations"', () => { + const result = matchesPhishingPatterns(null, 'See reported message destinations below'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('reported message destinations'); + }); + + it('flags a description containing "Microsoft directly"', () => { + const result = matchesPhishingPatterns(null, 'This message was reported to Microsoft directly'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Microsoft directly'); + }); + + it('does not flag a ticket with none of the patterns', () => { + const result = matchesPhishingPatterns('Re: order confirmation', 'please review invoice'); + expect(result.flagged).toBe(false); + expect(result.matched).toEqual([]); + }); + + it('matches case-insensitively', () => { + const result = matchesPhishingPatterns('SPAM ALERT from user', null); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Spam Alert'); + }); + + it('returns only the patterns actually present in matched[]', () => { + const result = matchesPhishingPatterns('Phishing Report', 'unrelated body text'); + expect(result.matched).toEqual(['Phishing Report']); + }); + + it('checks both title and description for a match', () => { + const result = matchesPhishingPatterns('Unrelated subject', 'Spam Alert triggered'); + expect(result.flagged).toBe(true); + expect(result.matched).toContain('Spam Alert'); + }); +}); + +describe('computePhishingContentHash', () => { + it('is stable for identical title+description', () => { + expect(computePhishingContentHash('t', 'd')).toBe(computePhishingContentHash('t', 'd')); + }); + + it('changes when the title changes', () => { + expect(computePhishingContentHash('t', 'd')).not.toBe(computePhishingContentHash('t2', 'd')); + }); + + it('changes when the description changes', () => { + expect(computePhishingContentHash('t', 'd')).not.toBe(computePhishingContentHash('t', 'd2')); + }); + + it('is defined and stable when description is null', () => { + const hash1 = computePhishingContentHash('t', null); + const hash2 = computePhishingContentHash('t', null); + expect(hash1).toBeTruthy(); + expect(hash1).toBe(hash2); + }); +});