Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions app/src/__tests__/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import path from 'path'
import { beforeAll, describe, expect, it } from 'vitest'

import { buildHtml } from '@/lib/email/build.js'
import { directory } from '@/utils/helpers.js'
import { EmailSchemaMessages } from '@/types/email.schema.js'

// buildHtml resolves the EJS template relative to __dirname (set to the build.ts location)
beforeAll(() => {
globalThis.__dirname = path.resolve(directory(import.meta.url), '..', 'lib', 'email')
})

const TEST_RECIPIENTS = ['tester@gmail.com']
const TEST_SENDER = 'sender@gmail.com'

describe('buildHtml test', () => {
it('should return an HTML string from paragraph content', async () => {
const html = await buildHtml({
content: ['Hello, World!', 'Second paragraph'],
recipients: TEST_RECIPIENTS,
sender: TEST_SENDER,
})

expect(typeof html).toBe('string')
expect(html.length).toBeGreaterThan(0)
})

it('should return an HTML string from wysiwyg content', async () => {
const html = await buildHtml({
content: [],
recipients: TEST_RECIPIENTS,
sender: TEST_SENDER,
wysiwyg: '<p>Hello, wysiwyg!</p>',
})

expect(typeof html).toBe('string')
expect(html).toContain('Hello, wysiwyg!')
})

it('should strip script tags from wysiwyg content', async () => {
const html = await buildHtml({
content: [],
recipients: TEST_RECIPIENTS,
sender: TEST_SENDER,
wysiwyg: '<script>alert("xss")</script><p>Safe content</p>',
})

expect(html).not.toContain('<script>')
expect(html).not.toContain('alert("xss")')
})

it('should reject if both content and wysiwyg are missing', async () => {
await expect(
buildHtml({
content: [],
recipients: TEST_RECIPIENTS,
sender: TEST_SENDER,
}),
).rejects.toThrow()
})

it('should reject with an invalid sender email', async () => {
await expect(
buildHtml({
content: ['Hello'],
recipients: TEST_RECIPIENTS,
sender: 'not-an-email',
}),
).rejects.toThrow(EmailSchemaMessages.RECIPIENT_EMAIL)
})

it('should reject if recipients list exceeds 20', async () => {
const tooManyRecipients = Array.from({ length: 21 }, (_, i) => `user${i}@gmail.com`)

await expect(
buildHtml({
content: ['Hello'],
recipients: tooManyRecipients,
sender: TEST_SENDER,
}),
).rejects.toThrow(EmailSchemaMessages.RECIPIENT_EMAIL_MAX)
})
})
57 changes: 57 additions & 0 deletions app/src/__tests__/transport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest'
import EmailTransport from '@/lib/email/transport.js'

describe('EmailTransport test', () => {
it('should initialize with a null transporter', () => {
const transport = new EmailTransport()
expect(transport.transporter).toBeNull()
})

it('should throw when getTransportOptions is called before createTransport3LO', () => {
const transport = new EmailTransport()
expect(() => transport.getTransportOptions()).toThrow('Transport not initialized')
})

it('should throw when googleUserEmail is not a valid email address', async () => {
const transport = new EmailTransport()

await expect(
transport.createTransport3LO({
googleUserEmail: 'not-an-email',
googleClientId: 'some-client-id',
googleClientSecret: 'some-secret',
googleRefreshToken: 'some-refresh-token',
}),
).rejects.toThrow()
})

it('should have a non-null transporter after createTransport3LO with valid credentials', async () => {
const transport = new EmailTransport()

await transport.createTransport3LO({
googleUserEmail: process.env.GOOGLE_USER_EMAIL || 'user@gmail.com',
googleClientId: process.env.GOOGLE_CLIENT_ID || 'client-id',
googleClientSecret: process.env.GOOGLE_CLIENT_SECRET || 'client-secret',
googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN || 'refresh-token',
})

expect(transport.transporter).not.toBeNull()
})

it('should expose transport options after createTransport3LO', async () => {
const transport = new EmailTransport()

await transport.createTransport3LO({
googleUserEmail: process.env.GOOGLE_USER_EMAIL || 'user@gmail.com',
googleClientId: process.env.GOOGLE_CLIENT_ID || 'client-id',
googleClientSecret: process.env.GOOGLE_CLIENT_SECRET || 'client-secret',
googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN || 'refresh-token',
})

const options = transport.getTransportOptions()
expect(options).toBeDefined()
expect(options.host).toBe('smtp.gmail.com')
expect(options.port).toBe(465)
expect(options.secure).toBe(true)
})
})
Loading