From 2fde1156da5f15bedb3984016e7ebc378522b68b Mon Sep 17 00:00:00 2001 From: lorentz Date: Wed, 15 Jul 2026 10:21:58 -0400 Subject: [PATCH] test(16-01): add failing tests for selectOriginalMessage (EVID-02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RED: covers all three selection tiers plus ambiguous/no-eml/empty edge cases. lib/services/eml-parser.ts does not exist yet — tests fail to resolve the module import. --- lib/services/eml-parser.fixtures.ts | 71 +++++++++++++++++++++++++++++ lib/services/eml-parser.test.ts | 57 +++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 lib/services/eml-parser.fixtures.ts create mode 100644 lib/services/eml-parser.test.ts diff --git a/lib/services/eml-parser.fixtures.ts b/lib/services/eml-parser.fixtures.ts new file mode 100644 index 0000000..9119d77 --- /dev/null +++ b/lib/services/eml-parser.fixtures.ts @@ -0,0 +1,71 @@ +/** + * Synthetic fixtures for eml-parser.test.ts. Nothing here is real customer + * content — all addresses, subjects, and bodies are invented for testing + * only (per this milestone's explicit synthetic-fixture-only constraint). + */ + +import type { Attachment } from '@/lib/types/autotask'; + +function makeAttachment(overrides: Partial & { id: number }): Attachment { + return { + attachmentType: 'FILE_ATTACHMENT', + fullPath: '', + title: '', + publish: 1, + contentType: 'message/rfc822', + ...overrides, + }; +} + +/** Tier 1: exact `rfc.eml` present among message/rfc822 attachments (Microsoft "Report Message" flow). */ +export const RFC_EML_TIER_ATTACHMENTS: Attachment[] = [ + makeAttachment({ id: 1, title: 'rfc.eml', fullPath: 'rfc.eml' }), + makeAttachment({ id: 2, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }), +]; + +/** Same tier, but with mixed case to verify case-insensitive matching. */ +export const RFC_EML_TIER_ATTACHMENTS_UPPERCASE: Attachment[] = [ + makeAttachment({ id: 1, title: 'RFC.EML', fullPath: 'RFC.EML' }), + makeAttachment({ id: 2, title: 'OriginatingEmail.EML', fullPath: 'OriginatingEmail.EML' }), +]; + +/** Tier 2: KnowBe4 PhishER flow — versioned filename, no literal rfc.eml. */ +export const KNOWBE4_TIER_ATTACHMENTS: Attachment[] = [ + makeAttachment({ + id: 10, + title: 'phish_alert_sp2_2.0.0.0.eml', + fullPath: 'phish_alert_sp2_2.0.0.0.eml', + }), + makeAttachment({ id: 11, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }), +]; + +/** Tier 3: only OriginatingEmail.eml present (older tickets, majority-case fallback). */ +export const ORIGINATING_ONLY_ATTACHMENTS: Attachment[] = [ + makeAttachment({ id: 20, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }), +]; + +/** Ambiguous: two non-OriginatingEmail message/rfc822 candidates, no rfc.eml — falls back to OriginatingEmail.eml. */ +export const AMBIGUOUS_WITH_FALLBACK_ATTACHMENTS: Attachment[] = [ + makeAttachment({ id: 30, title: 'weird_name_1.eml', fullPath: 'weird_name_1.eml' }), + makeAttachment({ id: 31, title: 'weird_name_2.eml', fullPath: 'weird_name_2.eml' }), + makeAttachment({ id: 32, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }), +]; + +/** Ambiguous with no fallback available — selectOriginalMessage should return null. */ +export const AMBIGUOUS_NO_FALLBACK_ATTACHMENTS: Attachment[] = [ + makeAttachment({ id: 40, title: 'weird_name_1.eml', fullPath: 'weird_name_1.eml' }), + makeAttachment({ id: 41, title: 'weird_name_2.eml', fullPath: 'weird_name_2.eml' }), +]; + +/** No .eml attachments at all — selectOriginalMessage should return null. */ +export const NO_EML_ATTACHMENTS: Attachment[] = [ + makeAttachment({ + id: 50, + title: 'screenshot.png', + fullPath: 'screenshot.png', + contentType: 'image/png', + }), +]; + +/** Empty attachment list. */ +export const EMPTY_ATTACHMENTS: Attachment[] = []; diff --git a/lib/services/eml-parser.test.ts b/lib/services/eml-parser.test.ts new file mode 100644 index 0000000..f01ff10 --- /dev/null +++ b/lib/services/eml-parser.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest'; +import { selectOriginalMessage } from './eml-parser'; +import { + RFC_EML_TIER_ATTACHMENTS, + RFC_EML_TIER_ATTACHMENTS_UPPERCASE, + KNOWBE4_TIER_ATTACHMENTS, + ORIGINATING_ONLY_ATTACHMENTS, + AMBIGUOUS_WITH_FALLBACK_ATTACHMENTS, + AMBIGUOUS_NO_FALLBACK_ATTACHMENTS, + NO_EML_ATTACHMENTS, + EMPTY_ATTACHMENTS, +} from './eml-parser.fixtures'; + +describe('selectOriginalMessage', () => { + it('selects rfc.eml over OriginatingEmail.eml when both are present', () => { + const result = selectOriginalMessage(RFC_EML_TIER_ATTACHMENTS); + expect(result?.id).toBe(1); + expect(result?.title).toBe('rfc.eml'); + }); + + it('selects rfc.eml case-insensitively (RFC.EML / OriginatingEmail.EML)', () => { + const result = selectOriginalMessage(RFC_EML_TIER_ATTACHMENTS_UPPERCASE); + expect(result?.id).toBe(1); + }); + + it('selects the KnowBe4-named attachment when OriginatingEmail.eml is also present', () => { + const result = selectOriginalMessage(KNOWBE4_TIER_ATTACHMENTS); + expect(result?.id).toBe(10); + expect(result?.title).toBe('phish_alert_sp2_2.0.0.0.eml'); + }); + + it('falls back to OriginatingEmail.eml when it is the only attachment', () => { + const result = selectOriginalMessage(ORIGINATING_ONLY_ATTACHMENTS); + expect(result?.id).toBe(20); + expect(result?.title).toBe('OriginatingEmail.eml'); + }); + + it('falls back to OriginatingEmail.eml when there are 2+ ambiguous non-OriginatingEmail candidates', () => { + const result = selectOriginalMessage(AMBIGUOUS_WITH_FALLBACK_ATTACHMENTS); + expect(result?.id).toBe(32); + expect(result?.title).toBe('OriginatingEmail.eml'); + }); + + it('returns null when candidates are ambiguous and no OriginatingEmail.eml fallback exists', () => { + const result = selectOriginalMessage(AMBIGUOUS_NO_FALLBACK_ATTACHMENTS); + expect(result).toBeNull(); + }); + + it('returns null (not throws) for a list with no .eml / message-rfc822 attachments', () => { + expect(() => selectOriginalMessage(NO_EML_ATTACHMENTS)).not.toThrow(); + expect(selectOriginalMessage(NO_EML_ATTACHMENTS)).toBeNull(); + }); + + it('returns null for an empty attachment list', () => { + expect(selectOriginalMessage(EMPTY_ATTACHMENTS)).toBeNull(); + }); +});