← Public packages
@danj/hey-email
HEY.com email for Kody: search threads, read messages, download PDF attachments, and write drafts. Sending is barred until you allow it.
test/drafts.test.js
169 lines · 6.2 KB · JavaScriptimport 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: '<blockquote>Earlier</blockquote>',
creator: { id: 7, name_tag: '<p>Dan</p>' },
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: '<div>Hello World</div>' },
entry: { status: 'drafted', addressed: { directly: ['danielj@hey.com'] } },
})
assert.equal(draft.id, 12345)
assert.equal(draft.url, `${HEY}/messages/12345`)
assert.equal(draft.edit_url, `${HEY}/messages/12345/edit`)
assert.deepEqual(draft.to, ['danielj@hey.com'])
})
test('createDraft saves to, cc, and bcc as directly, copied, and blindcopied', async () => {
routeIdentity()
on('POST', `${HEY}/messages.json`, savedAt(`${HEY}/messages/12345`))
await createDraft({
subject: 'Notes',
html: '<p>Rich</p>',
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: '<div>Thanks</div><p>Dan</p>' },
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'] },
})
})