Skip to content

Commit

Permalink
feat: more types (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
meghein committed Jan 23, 2024
1 parent c929752 commit 0125e2d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 39 deletions.
24 changes: 19 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
declare module '@autotelic/fastify-mail' {
import { FastifyInstance, FastifyPluginCallback } from 'fastify';
import { Transporter } from 'nodemailer';
import { FastifyPluginCallback } from 'fastify';
import { Transporter } from 'nodemailer';

type FastifyMail = FastifyPluginCallback<fastifyMail.FastifyMailOptions>;

declare module 'fastify' {
interface FastifyInstance {
mail: fastifyMail.FastifyMailDecorator;
}
}

declare namespace fastifyMail {
// Define the options for the plugin
export interface FastifyMailOptions {
pov?: {
Expand All @@ -13,7 +21,7 @@ declare module '@autotelic/fastify-mail' {
}

// Define the shape of the mail decorator
interface FastifyMailDecorator {
export interface FastifyMailDecorator {
sendMail: (message: MailMessage, opts?: SendMailOptions) => Promise<any>;
createMessage: (message: MailMessage, templatePath: string, context: any) => Promise<MailMessage>;
validateMessage: (message: MailMessage) => string[];
Expand Down Expand Up @@ -46,5 +54,11 @@ declare module '@autotelic/fastify-mail' {
// The exported plugin function
const fastifyMail: FastifyPluginCallback<FastifyMailOptions>;

export default fastifyMail;
export { fastifyMail as default };
}

declare function fastifyMail(
...params: Parameters<FastifyMail>
): ReturnType<FastifyMail>;

export = fastifyMail;
74 changes: 42 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,41 +56,51 @@ const fastifyMail = async (fastify, opts) => {
// Creates the message object that will be sent to nodemailer.
// It will either render the templates or use the data in the message object as is
createMessage: async function (message, templatePath, context) {
const from = message.from
const to = message.to
const subject = message.subject
const replyTo = message.replyTo
const cc = message.cc
const bcc = message.bcc
const attachments = message.attachments
const html = message.html
const text = message.text

const [
renderedHtml,
renderedText
] = await Promise.all([
await renderTemplate('html'),
await renderTemplate('text')
])

return {
from,
to,
cc,
bcc,
attachments,
replyTo,
subject,
html: renderedHtml || html,
text: renderedText || text
const formattedMessage = {
from: message.from,
to: message.to,
subject: message.subject,
replyTo: message.replyTo,
cc: message.cc,
bcc: message.bcc,
html: message.html,
text: message.text
}

// renders a template with the given context based on the templateName which
// should be found in the path provided by templates. Returns "" if the promise is rejected.
if (templatePath) {
const [
{ template: renderedHtml, error: htmlError },
{ template: renderedText, error: textError }
] = await Promise.all([
await renderTemplate('html'),
await renderTemplate('text')
])

if (!renderedHtml && !renderedText) {
fastify.log.error(`fastify-mail: ${htmlError}`)
fastify.log.error(`fastify-mail: ${textError}`)
}

if (renderedHtml) {
formattedMessage.html = renderedHtml
}

if (renderedText) {
formattedMessage.text = renderedText
}
}

return formattedMessage

// renders a template with the given context based on the templateName & templatePath,
// if it fails to render the template, it returns an error message instead.
async function renderTemplate (templateName) {
return await fastify[propertyName](join(templatePath, templateName), context)
.catch(() => { return null })
try {
const template = await fastify[propertyName](join(templatePath, templateName), context)
return { template }
} catch (error) {
return { error: error.message }
}
}
},
// validates the message object includes to, from, subject and returns an error message if it does not
Expand Down
53 changes: 51 additions & 2 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ test('view decorator does not exist if the engine is not provided', async ({ tea
notOk(fastify.hasDecorator('view'))
})

test('throws an error if point-of-view is not registered', async ({ teardown, notOk, rejects, ok, equal }) => {
test('throws an error if point-of-view is not registered', async ({ teardown, notOk, rejects }) => {
teardown(() => fastify.close())
const fastify = Fastify()
fastify.register(fastifyMail, { transporter: { jsonTransport: true } })
Expand All @@ -89,7 +89,7 @@ test('throws an error if point-of-view is not registered', async ({ teardown, no
notOk(fastify.hasDecorator('view'))
})

test('throws an error if an invalid transporter is given', async ({ teardown, rejects, ok, equal }) => {
test('throws an error if an invalid transporter is given', async ({ teardown, rejects }) => {
teardown(() => fastify.close())
const fastify = Fastify()
fastify.register(fastifyMail, { pov: { engine: { nunjucks } }, transporter: 'error' })
Expand Down Expand Up @@ -145,6 +145,10 @@ test('fastify-mail uses string variables (for text and html) when a template is

const fastify = Fastify()
fastify.register(require('@fastify/view'), povConfig)

const loggedErrors = []
fastify.log.error = (msg) => { loggedErrors.push(msg) }

fastify.after(() => {
fastify.register(fastifyMail, { pov: { propertyName: 'foo' }, transporter: { jsonTransport: true } })
})
Expand All @@ -156,6 +160,7 @@ test('fastify-mail uses string variables (for text and html) when a template is

ok(fastify.hasDecorator('foo'))
same(sendMailStub.args[0], [testMessage])
equal(loggedErrors.length, 0)
equal(sendMailStub.args.length, 1)
})

Expand All @@ -178,6 +183,10 @@ test('fastify-mail uses text template when available but defaults to provided ht

const fastify = Fastify()
fastify.register(require('@fastify/view'), povConfig)

const loggedErrors = []
fastify.log.error = (msg) => { loggedErrors.push(msg) }

fastify.after(() => {
fastify.register(fastifyMail, { pov: { propertyName: 'foo' }, transporter: { jsonTransport: true } })
})
Expand All @@ -189,6 +198,7 @@ test('fastify-mail uses text template when available but defaults to provided ht

ok(fastify.hasDecorator('foo'))
same(sendMailStub.args[0][0].html, testHtml)
equal(loggedErrors.length, 0)
equal(sendMailStub.args.length, 1)
})

Expand All @@ -211,6 +221,10 @@ test('fastify-mail uses html template when available but defaults to provided te

const fastify = Fastify()
fastify.register(require('@fastify/view'), povConfig)

const loggedErrors = []
fastify.log.error = (msg) => { loggedErrors.push(msg) }

fastify.after(() => {
fastify.register(fastifyMail, { pov: { propertyName: 'foo' }, transporter: { jsonTransport: true } })
})
Expand All @@ -223,9 +237,44 @@ test('fastify-mail uses html template when available but defaults to provided te
ok(fastify.hasDecorator('foo'))
same(sendMailStub.args[0][0].html, testHtml)
same(sendMailStub.args[0][0].text, 'This is a plain text email message.')
equal(loggedErrors.length, 0)
equal(sendMailStub.args.length, 1)
})

test('fastify-mail will throw errors if templatePath is defined, but does not exist', async ({ teardown, testdir, equal }) => {
teardown(() => {
fastify.close()
sendMailStub.restore()
})

const testTemplates = testdir({})

const povConfig = {
propertyName: 'foo',
engine: { nunjucks },
includeViewExtension: true,
options: { filename: resolve('templates') }
}

const fastify = Fastify()
fastify.register(require('@fastify/view'), povConfig)

const loggedErrors = []
fastify.log.error = (msg) => { loggedErrors.push(msg) }

fastify.after(() => {
fastify.register(fastifyMail, { pov: { propertyName: 'foo' }, transporter: { jsonTransport: true } })
})
await fastify.ready()

const sendMailStub = sinon.stub(fastify.nodemailer, 'sendMail')

await fastify.mail.sendMail(testMessage, { templatePath: relative(__dirname, testTemplates), context: testContext })

equal(loggedErrors[0], 'fastify-mail: template not found: .tap/fixtures/.-index.test.js-fastify-mail-will-throw-errors-if-templatePath-is-defined-but-does-not-exist/html.njk')
equal(loggedErrors[1], 'fastify-mail: template not found: .tap/fixtures/.-index.test.js-fastify-mail-will-throw-errors-if-templatePath-is-defined-but-does-not-exist/text.njk')
})

test('fastify.mail.sendMail calls nodemailer.sendMail with correct arguments', async ({ teardown, testdir, fixture, same, equal }) => {
teardown(() => {
fastify.close()
Expand Down

0 comments on commit 0125e2d

Please sign in to comment.