import assert from 'node:assert/strict' import { afterEach, beforeEach, test } from 'node:test' import { createDraft, createReplyDraft } from '../src/drafts.js' import { HEY, installFetch, jsonResponse, resetKodySecrets } from './helpers.mjs' let fixture beforeEach(() => { resetKodySecrets() fixture = installFetch(async (url, init) => { const handler = fixture.routes.get(`${String(init.method || 'GET').toUpperCase()} ${url}`) if (handler) return handler(url, init) throw new Error(`unexpected ${init.method || 'GET'} ${url}`) }) fixture.routes = new Map() }) afterEach(() => { fixture?.restore() }) function on(method, url, handler) { fixture.routes.set(`${method} ${url}`, handler) } function writes() { return fixture.requests.filter((row) => row.method !== 'GET') } function routeIdentity(identity = { senders: [{ id: 3 }, { id: 5, default: true }], primary_contact: { id: 1 } }) { on('GET', `${HEY}/identity.json`, () => jsonResponse(200, identity)) } function savedAt(location) { return () => new Response(null, { status: 204, headers: location ? { location } : {} }) } function routeReplyPrefill(extra = {}) { on('GET', `${HEY}/entries/42/replies/new.json`, () => jsonResponse(200, { url: `${HEY}/entries/42/replies`, subject: 'Re: Notes', content: '
Earlier', creator: { id: 7, name_tag: '
Dan
' }, addressed: { directly: [{ email_address: 'ada@example.com' }], copied: [{ email_address: 'cy@example.com' }], blindcopied: [], }, ...extra, }) ) } test('createDraft posts the nested drafted body with the default sender from /identity.json', async () => { routeIdentity() on('POST', `${HEY}/messages.json`, savedAt(`${HEY}/messages/12345`)) const draft = await createDraft({ subject: 'Hello World', text: 'Hello World', to: 'danielj@hey.com' }) const [post] = writes() assert.equal(post.method, 'POST') assert.equal(post.url, `${HEY}/messages.json`) assert.equal(post.headers['content-type'], 'application/json') assert.deepEqual(JSON.parse(post.body), { acting_sender_id: 5, message: { subject: 'Hello World', content: 'Rich
', to: ['ada@example.com', 'bob@example.com'], cc: 'cy@example.com', bcc: ['eve@example.com'], }) assert.deepEqual(JSON.parse(writes()[0].body).entry, { status: 'drafted', addressed: { directly: ['ada@example.com', 'bob@example.com'], copied: ['cy@example.com'], blindcopied: ['eve@example.com'], }, }) }) test('createDraft with no recipients still sends entry.status drafted and an empty addressed', async () => { routeIdentity() on('POST', `${HEY}/messages.json`, savedAt(`${HEY}/messages/12345`)) await createDraft({ subject: 'Notes', text: 'Later' }) assert.deepEqual(JSON.parse(writes()[0].body).entry, { status: 'drafted', addressed: {} }) }) test('createDraft picks the first sender, then the primary contact, when no sender is default', async () => { const cases = [ [{ senders: [{ id: 3 }, { id: 4 }], primary_contact: { id: 1 } }, 3], [{ senders: [], primary_contact: { id: 9 } }, 9], ] for (const [identity, expected] of cases) { fixture.requests.length = 0 routeIdentity(identity) on('POST', `${HEY}/messages.json`, savedAt(`${HEY}/messages/12345`)) await createDraft({ subject: 'Notes', text: 'Hi' }) assert.equal(JSON.parse(writes()[0].body).acting_sender_id, expected) } }) test('createDraft reads the id from a relative Location', async () => { routeIdentity() on('POST', `${HEY}/messages.json`, savedAt('/messages/98')) const draft = await createDraft({ subject: 'Notes', text: 'Hi' }) assert.equal(draft.id, 98) }) test('createDraft rejects when HEY names no Location', async () => { routeIdentity() on('POST', `${HEY}/messages.json`, savedAt(null)) await assert.rejects(() => createDraft({ subject: 'Notes', text: 'Hi' }), /Location/) }) test('createReplyDraft posts the drafted body with the thread recipients and no echoed quote', async () => { routeIdentity() routeReplyPrefill() on('POST', `${HEY}/entries/42/replies.json`, savedAt(`${HEY}/messages/777`)) const draft = await createReplyDraft({ entryId: 42, text: 'Thanks' }) const [post] = writes() assert.equal(post.method, 'POST') assert.equal(post.url, `${HEY}/entries/42/replies.json`) assert.equal(post.headers['content-type'], 'application/json') assert.deepEqual(JSON.parse(post.body), { acting_sender_id: 5, message: { subject: 'Re: Notes', content: 'Dan
' }, entry: { status: 'drafted', addressed: { directly: ['ada@example.com'], copied: ['cy@example.com'] }, }, }) assert.equal(draft.id, 777) assert.equal(draft.edit_url, `${HEY}/messages/777/edit`) assert.deepEqual(draft.to, ['ada@example.com']) assert.deepEqual(draft.cc, ['cy@example.com']) }) test('createReplyDraft acts as the prefill sender and skips the identity lookup', async () => { routeReplyPrefill({ sender: { id: 4242 } }) on('POST', `${HEY}/entries/42/replies.json`, savedAt(`${HEY}/messages/777`)) await createReplyDraft({ entryId: 42, text: 'Thanks' }) assert.equal(JSON.parse(writes()[0].body).acting_sender_id, 4242) assert.equal(fixture.requests.some((row) => row.url === `${HEY}/identity.json`), false) }) test('createReplyDraft input recipients replace the thread recipients', async () => { routeIdentity() routeReplyPrefill() on('POST', `${HEY}/entries/42/replies.json`, savedAt(`${HEY}/messages/777`)) await createReplyDraft({ entryId: 42, text: 'Thanks', to: 'bob@example.com' }) assert.deepEqual(JSON.parse(writes()[0].body).entry, { status: 'drafted', addressed: { directly: ['bob@example.com'] }, }) })