import { describe, it, expect } from 'vitest'; import { EmailAdapter, emailInboundFixture, emailReplyFixture, signedFixture } from './index'; const ctx = { scope: { orgId: 'o', appId: 'a' } }; describe('EmailAdapter', () => { const adapter = new EmailAdapter(); it('normalizes an inbound email into an ingest request', () => { const req = adapter.normalize(emailInboundFixture, ctx); expect(req.channel.type).toBe('EMAIL'); expect(req.source.handleKind).toBe('EXTERNAL_VISITOR'); expect(req.source.externalId).toBe(emailInboundFixture.from); expect(req.parts[0]?.bodyText).toBe(emailInboundFixture.text); expect(req.providerEventId).toBe(''); expect(req.thread?.externalThreadId).toBe('email:'); expect(req.thread?.subject).toBe('Need help with my order'); // Re:/Fwd: stripped expect((req.metadata as { messageId: string }).messageId).toBe(''); }); it('threads a reply onto the SAME email thread as the original', () => { const first = adapter.normalize(emailInboundFixture, ctx); const reply = adapter.normalize(emailReplyFixture, ctx); expect(reply.thread?.externalThreadId).toBe(first.thread?.externalThreadId); // email: expect(reply.providerEventId).toBe(''); // but its own dedup id }); it('falls back to the reply target when there is no references chain', () => { const req = adapter.normalize({ ...emailReplyFixture, references: undefined }, ctx); expect(req.thread?.externalThreadId).toBe('email:'); // uses inReplyTo }); it('derives text from html when no plain text is present', () => { const req = adapter.normalize({ messageId: '', from: 'x@y', html: '

Hello there

' }, ctx); expect(req.parts[0]?.bodyText).toBe('Hello there'); }); it('verifies an HMAC-signed body and rejects a bad/absent signature', () => { const secret = 'sekret'; const { body, headers } = signedFixture(emailInboundFixture, secret); expect(adapter.verifySignature(body, headers, secret)).toBe(true); expect(adapter.verifySignature(body, { 'x-iios-signature': 'sha256=bad' }, secret)).toBe(false); expect(adapter.verifySignature(body, {}, secret)).toBe(false); }); });