import { describe, it, expect } from 'vitest' import { ensureHtmlDocument, buildEmailBodyHtmlDocument, EMAIL_ALLOWED_MIME_TYPES, UPLOAD_ALLOWED_MIME_TYPES, } from '@/extensions/general/invoice-inbox/lib/upload-and-extract' import { htmlToText } from '@/extensions/general/invoice-inbox/lib/extract-invoice-fields' import { validateDocumentMagicBytes } from '@/lib/core/documents/document-service' const decode = (buf: ArrayBuffer) => new TextDecoder().decode(new Uint8Array(buf)) describe('EMAIL_ALLOWED_MIME_TYPES', () => { it('is the upload set plus text/html, and only reachable from the email pipeline', () => { for (const type of UPLOAD_ALLOWED_MIME_TYPES) { expect(EMAIL_ALLOWED_MIME_TYPES.has(type)).toBe(true) } expect(EMAIL_ALLOWED_MIME_TYPES.has('text/html')).toBe(true) // The manual upload surface must stay strict. expect(UPLOAD_ALLOWED_MIME_TYPES.has('text/html')).toBe(false) }) }) describe('ensureHtmlDocument', () => { it('wraps a fragment-shaped mail body into a full document', () => { const out = decode(ensureHtmlDocument('
Faktura 123
')) expect(out.toLowerCase().startsWith('') expect(out).toContain('
Faktura 123
') }) it('passes a full document through unchanged', () => { const full = '\nHej' expect(decode(ensureHtmlDocument(full))).toBe(full) const rooted = 'Hej' expect(decode(ensureHtmlDocument(rooted))).toBe(rooted) }) it('recognises a full document behind leading whitespace', () => { const full = '\n ' expect(decode(ensureHtmlDocument(full))).toBe(full) }) it('produces bytes that pass the document-service magic check', () => { expect( validateDocumentMagicBytes(ensureHtmlDocument('999 kr'), 'text/html'), ).toBeNull() }) }) describe('buildEmailBodyHtmlDocument', () => { it('prefers the HTML part over the text part', () => { const out = decode(buildEmailBodyHtmlDocument('

Belopp: 100 kr

', 'plain fallback')!) expect(out).toContain('

Belopp: 100 kr

') expect(out).not.toContain('plain fallback') }) it('escapes a plain-text-only body into a
', () => {
    const out = decode(
      buildEmailBodyHtmlDocument(null, 'Total  & 100 kr')!,
    )
    expect(out).toContain('
')
    expect(out).toContain('Total <script>alert(1)</script> & 100 kr')
    expect(out).not.toContain('

Faktura 42

', ) expect(text).toBe('Faktura 42') }) it('keeps block boundaries as newlines so labels and amounts stay separated', () => { const text = htmlToText('
Att betala
1 234,56 kr
') expect(text).toContain('Att betala') expect(text).toContain('1 234,56 kr') expect(text.split('\n').length).toBeGreaterThan(1) }) it('decodes entities, ampersand strictly last', () => { expect(htmlToText('Moms & frakt: 25 kr')).toBe('Moms & frakt: 25 kr') // Double-decoding would turn this into a real "<" instead of the literal. expect(htmlToText('visar &lt; som text')).toBe('visar < som text') expect(htmlToText('€100 och 'citat'')).toBe("€100 och 'citat'") }) it('caps the output length', () => { const long = `

${'a'.repeat(80_000)}

` expect(htmlToText(long).length).toBeLessThanOrEqual(50_000) }) })