diff --git a/src/mailer.js b/src/mailer.js new file mode 100644 index 0000000..1e5792b --- /dev/null +++ b/src/mailer.js @@ -0,0 +1,30 @@ +const nodemailer = require('nodemailer'); + +function createTransport(env = process.env) { + return nodemailer.createTransport({ + host: env.SMTP_HOST, + port: Number(env.SMTP_PORT || 587), + secure: Number(env.SMTP_PORT) === 465, + auth: env.SMTP_USER ? { user: env.SMTP_USER, pass: env.SMTP_PASS } : undefined, + }); +} + +function formatPrice(value) { + return value.toFixed(2).replace('.', ','); +} + +async function sendPriceChangeEmail(transport, { from, to, name, url, oldPrice, newPrice }) { + await transport.sendMail({ + from, + to, + subject: `Prijs gewijzigd: ${name}`, + text: [ + `De prijs van "${name}" is gewijzigd.`, + `Oude prijs: €${formatPrice(oldPrice)}`, + `Nieuwe prijs: €${formatPrice(newPrice)}`, + `Link: ${url}`, + ].join('\n'), + }); +} + +module.exports = { createTransport, sendPriceChangeEmail, formatPrice }; diff --git a/test/mailer.test.js b/test/mailer.test.js new file mode 100644 index 0000000..58d8fa2 --- /dev/null +++ b/test/mailer.test.js @@ -0,0 +1,43 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { sendPriceChangeEmail, formatPrice, createTransport } = require('../src/mailer'); + +test('formatPrice formats with comma decimal separator', () => { + assert.equal(formatPrice(19.9), '19,90'); + assert.equal(formatPrice(24.5), '24,50'); +}); + +test('sendPriceChangeEmail sends mail with expected fields', async () => { + const calls = []; + const fakeTransport = { sendMail: async (opts) => { calls.push(opts); } }; + await sendPriceChangeEmail(fakeTransport, { + from: 'korting@sjoerd.app', + to: 'me@example.com', + name: 'Voorbeeld product', + url: 'https://shop.example/product', + oldPrice: 19.99, + newPrice: 17.5, + }); + assert.equal(calls.length, 1); + assert.equal(calls[0].from, 'korting@sjoerd.app'); + assert.equal(calls[0].to, 'me@example.com'); + assert.equal(calls[0].subject, 'Prijs gewijzigd: Voorbeeld product'); + assert.match(calls[0].text, /19,99/); + assert.match(calls[0].text, /17,50/); + assert.match(calls[0].text, /https:\/\/shop\.example\/product/); +}); + +test('sendPriceChangeEmail propagates transport errors', async () => { + const fakeTransport = { sendMail: async () => { throw new Error('smtp down'); } }; + await assert.rejects( + () => sendPriceChangeEmail(fakeTransport, { + from: 'a@b.com', to: 'c@d.com', name: 'X', url: 'https://x', oldPrice: 1, newPrice: 2, + }), + /smtp down/ + ); +}); + +test('createTransport builds a transport from env vars', () => { + const transport = createTransport({ SMTP_HOST: 'mail.sjoerd.app', SMTP_PORT: '587', SMTP_USER: 'u', SMTP_PASS: 'p' }); + assert.equal(typeof transport.sendMail, 'function'); +});