import { describe, it, expect, vi } from 'vitest'; import { selectOriginalMessage, parseEml, parseAuthResults, extractUrls, buildBodyPreview, MAX_EML_BYTES, } 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, RICH_MULTIPART_EML, RICH_MULTIPART_WITH_AUTH_ORIGINAL_EML, INLINE_ATTACHMENT_EML, LONG_BODY_EML, FUZZY_URL_EML, makeOversizedEmlBuffer, } 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(); }); }); describe('parseAuthResults', () => { it('parses spf/dkim/dmarc verdicts from a raw Authentication-Results header value', () => { const result = parseAuthResults( 'mx.wulfconsulting.test; spf=pass smtp.mailfrom=evil-example.test; dkim=fail header.d=evil-example.test; dmarc=none header.from=evil-example.test' ); expect(result).toEqual({ spf: 'pass', dkim: 'fail', dmarc: 'none' }); }); it('is case-insensitive on method and result tokens', () => { const result = parseAuthResults('mx.test; SPF=PASS; DKIM=Fail; DMARC=None'); expect(result).toEqual({ spf: 'pass', dkim: 'fail', dmarc: 'none' }); }); it('omits methods not present in the header', () => { const result = parseAuthResults('mx.test; spf=softfail'); expect(result).toEqual({ spf: 'softfail' }); }); }); describe('extractUrls', () => { it('extracts and dedupes URLs from both text and html parts', () => { const urls = extractUrls( 'Visit http://evil-example.test/verify now.', 'link' ); expect(urls).toEqual(['http://evil-example.test/verify']); }); it('extracts fuzzy (scheme-less) www. URLs', () => { const urls = extractUrls('Visit www.evil-example.com/login to reset.', null); expect(urls.some((u) => u.includes('evil-example.com/login'))).toBe(true); }); it('returns an empty array when no URLs are present', () => { expect(extractUrls('no links here', null)).toEqual([]); }); it('handles null/undefined text and html gracefully', () => { expect(extractUrls(null, undefined)).toEqual([]); }); }); describe('buildBodyPreview', () => { it('prefers plain text over html', () => { const preview = buildBodyPreview('plain text body', '

html body

'); expect(preview).toContain('plain text body'); }); it('falls back to a stripped version of html when text is absent', () => { const preview = buildBodyPreview(null, '

Hello world

'); expect(preview).toContain('Hello'); expect(preview).toContain('world'); expect(preview).not.toContain('

'); expect(preview).not.toContain(''); }); it('truncates a long body and stays distinct from the raw text', () => { const longText = 'x'.repeat(2000); const preview = buildBodyPreview(longText, null); expect(preview.length).toBeLessThan(longText.length); expect(preview).not.toBe(longText); }); }); describe('parseEml', () => { it('normalizes headers, auth results, received chain, urls, and attachment metadata from a synthetic fixture', async () => { const result = await parseEml(RICH_MULTIPART_EML); expect(result.from.email).toBe('attacker@evil-example.test'); expect(result.from.displayName).toBe('Attacker Corp'); expect(result.from.domain).toBe('evil-example.test'); expect(result.replyTo).toBe('reply@evil-example.test'); expect(result.returnPath).toBe('bounce@evil-example.test'); expect(result.to).toContain('victim@wulfconsulting.test'); expect(result.cc).toContain('cc-user@wulfconsulting.test'); expect(result.subject).toBe('Urgent: verify your account'); expect(result.date).toBeTruthy(); expect(result.messageId).toBe(''); expect(result.receivedChain).toHaveLength(2); expect(result.authResults).toEqual({ spf: 'pass', dkim: 'fail', dmarc: 'none' }); expect(result.authResultsOriginal).toBeNull(); expect(result.urls).toContain('http://evil-example.test/verify'); expect(result.attachments).toHaveLength(1); expect(result.attachments[0].filename).toBe('invoice.pdf'); expect(result.attachments[0].contentType).toBe('application/pdf'); expect(result.attachments[0].checksum).toMatch(/^[a-f0-9]{64}$/); expect(result.attachments[0].related).toBe(false); expect(result.bodyPreview).toBeTruthy(); }); it('populates authResultsOriginal when an Authentication-Results-Original header is present', async () => { const result = await parseEml(RICH_MULTIPART_WITH_AUTH_ORIGINAL_EML); expect(result.authResults).toEqual({ spf: 'fail', dkim: 'fail', dmarc: 'fail' }); expect(result.authResultsOriginal).toEqual({ spf: 'pass', dkim: 'pass', dmarc: 'pass' }); }); it('preserves inline/related attachments rather than dropping them', async () => { const result = await parseEml(INLINE_ATTACHMENT_EML); expect(result.attachments).toHaveLength(1); expect(result.attachments[0].related).toBe(true); }); it('produces a body preview that is truncated and distinct from the raw body', async () => { const result = await parseEml(LONG_BODY_EML); expect(result.bodyPreview.length).toBeLessThan(2000); }); it('extracts fuzzy www. URLs from a real parsed message', async () => { const result = await parseEml(FUZZY_URL_EML); expect(result.urls.some((u) => u.includes('evil-example.com/login'))).toBe(true); }); it('makes no network call (no network fetch) while parsing any fixture', async () => { const fetchSpy = vi.spyOn(global, 'fetch'); await parseEml(RICH_MULTIPART_EML); await parseEml(RICH_MULTIPART_WITH_AUTH_ORIGINAL_EML); await parseEml(INLINE_ATTACHMENT_EML); await parseEml(LONG_BODY_EML); await parseEml(FUZZY_URL_EML); expect(fetchSpy).not.toHaveBeenCalled(); fetchSpy.mockRestore(); }); it('rejects a buffer larger than MAX_EML_BYTES before simpleParser is called', async () => { const oversized = makeOversizedEmlBuffer(MAX_EML_BYTES); expect(oversized.byteLength).toBeGreaterThan(MAX_EML_BYTES); await expect(parseEml(oversized)).rejects.toThrow(); }); it('does not contain any real customer email content in any fixture', () => { const allFixtures = [ RICH_MULTIPART_EML, RICH_MULTIPART_WITH_AUTH_ORIGINAL_EML, INLINE_ATTACHMENT_EML, LONG_BODY_EML, FUZZY_URL_EML, ]; for (const fixture of allFixtures) { const text = fixture.toString('utf-8'); expect(text).not.toMatch(/wulfconsulting\.com/); expect(text.includes('evil-example.test') || text.includes('evil-example.com')).toBe(true); } }); });