import { test } from 'node:test'; import assert from 'node:assert/strict'; import { cleanEmailBody, normalizeChatwootWebhook } from '../src/domain/messageNormalizer.js'; import { bounceMessage, incomingEmailMessage, missingConversationId, noLabelsPayload, outgoingMessage, privateNote, statusUpdate, } from './fixtures/chatwoot.js'; test('accepts an incoming e-mail message', () => { const result = normalizeChatwootWebhook(incomingEmailMessage); assert.ok(result.ok); assert.equal(result.event.conversationId, 1311); assert.equal(result.event.messageId, '90210'); assert.equal(result.event.senderEmail, 'jan.kowalski@example.com'); assert.equal(result.event.senderName, 'Jan Kowalski'); assert.equal(result.event.channel, 'email'); assert.equal(result.event.subject, 'Pytanie o klimatyzację'); assert.equal(result.event.needsConversationFetch, false); }); test('rejects outgoing messages', () => { const result = normalizeChatwootWebhook(outgoingMessage); assert.equal(result.ok, false); assert.match((result as { reason: string }).reason, /not_incoming/); }); test('rejects private notes', () => { const result = normalizeChatwootWebhook(privateNote); assert.equal(result.ok, false); assert.equal((result as { reason: string }).reason, 'private_note'); }); test('rejects non message_created events', () => { const result = normalizeChatwootWebhook(statusUpdate); assert.equal(result.ok, false); assert.match((result as { reason: string }).reason, /unsupported_event/); }); test('rejects payloads without a conversation id', () => { const result = normalizeChatwootWebhook(missingConversationId); assert.equal(result.ok, false); assert.equal((result as { reason: string }).reason, 'missing_conversation_id'); }); test('flags payloads that need a conversation fetch', () => { const result = normalizeChatwootWebhook(noLabelsPayload); assert.ok(result.ok); assert.equal(result.event.needsConversationFetch, true); }); test('derives a stable surrogate id when the payload has no message id', () => { const { id, ...withoutId } = incomingEmailMessage; void id; const a = normalizeChatwootWebhook(withoutId); const b = normalizeChatwootWebhook(withoutId); assert.ok(a.ok && b.ok); assert.match(a.event.messageId, /^hash:1311:[0-9a-f]{32}$/); assert.equal(a.event.messageId, b.event.messageId); }); test('a different body yields a different surrogate id', () => { const { id, ...withoutId } = incomingEmailMessage; void id; const a = normalizeChatwootWebhook(withoutId); const b = normalizeChatwootWebhook({ ...withoutId, content: 'inna treść' }); assert.ok(a.ok && b.ok); assert.notEqual(a.event.messageId, b.event.messageId); }); test('bounce payloads still normalise (the spam gate decides, not the parser)', () => { const result = normalizeChatwootWebhook(bounceMessage); assert.ok(result.ok); assert.equal(result.event.senderEmail, 'mailer-daemon@example.com'); }); test('cleanEmailBody strips quoted replies and signatures', () => { const raw = [ 'Dzień dobry, mam pytanie o zamówienie.', '', 'W dniu 2026-08-19 Jan Kowalski napisał:', '> poprzednia wiadomość', '> druga linia cytatu', ].join('\n'); assert.equal(cleanEmailBody(raw), 'Dzień dobry, mam pytanie o zamówienie.'); }); test('cleanEmailBody converts HTML e-mails to text', () => { const raw = '
Witam,
czy produkt jest dostępny?