5428eeccc1
49 tests for all 10 Zod schemas in validate.ts, 26 tests for auth (hashPassword, verifyPassword, createSession, getSession, getCurrentUser, deleteSession), 11 for storage (magic-byte validation, saveImage, deleteImageFile), 9 for email (sendMail scenarios), and 6 for rate limiting logic.
110 lines
4.0 KiB
TypeScript
110 lines
4.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
const { mockSendMail, mockCreateTransport } = vi.hoisted(() => {
|
|
const mockSendMail = vi.fn().mockResolvedValue({ messageId: 'mock-id' })
|
|
const mockCreateTransport = vi.fn().mockReturnValue({ sendMail: mockSendMail })
|
|
return { mockSendMail, mockCreateTransport }
|
|
})
|
|
|
|
vi.mock('nodemailer', () => ({
|
|
default: { createTransport: mockCreateTransport },
|
|
}))
|
|
|
|
import { sendEmail, sendOrderConfirmationEmail, sendPasswordResetEmail } from '@/lib/email'
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockSendMail.mockResolvedValue({ messageId: 'mock-id' })
|
|
})
|
|
|
|
// ─── sendEmail ────────────────────────────────────────────────────────────────
|
|
|
|
describe('sendEmail', () => {
|
|
it('calls sendMail with correct to, subject, html', async () => {
|
|
await sendEmail({ to: 'user@example.com', subject: 'Test', html: '<p>Hello</p>' })
|
|
expect(mockSendMail).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
to: 'user@example.com',
|
|
subject: 'Test',
|
|
html: '<p>Hello</p>',
|
|
})
|
|
)
|
|
})
|
|
|
|
it('includes optional text field when provided', async () => {
|
|
await sendEmail({ to: 'u@e.com', subject: 'S', html: '<p>h</p>', text: 'plain text' })
|
|
expect(mockSendMail).toHaveBeenCalledWith(expect.objectContaining({ text: 'plain text' }))
|
|
})
|
|
|
|
it('propagates sendMail errors', async () => {
|
|
mockSendMail.mockRejectedValueOnce(new Error('SMTP down'))
|
|
await expect(sendEmail({ to: 'u@e.com', subject: 'S', html: '<p>h</p>' })).rejects.toThrow('SMTP down')
|
|
})
|
|
})
|
|
|
|
// ─── sendOrderConfirmationEmail ───────────────────────────────────────────────
|
|
|
|
describe('sendOrderConfirmationEmail', () => {
|
|
it('sends email with correct subject containing orderId', async () => {
|
|
await sendOrderConfirmationEmail('customer@example.com', {
|
|
orderId: 'order-42',
|
|
grandTotal: 2999,
|
|
currency: 'EUR',
|
|
})
|
|
expect(mockSendMail).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
to: 'customer@example.com',
|
|
subject: expect.stringContaining('order-42'),
|
|
})
|
|
)
|
|
})
|
|
|
|
it('formats price correctly (cents to euros)', async () => {
|
|
await sendOrderConfirmationEmail('customer@example.com', {
|
|
orderId: 'order-1',
|
|
grandTotal: 2999,
|
|
currency: 'EUR',
|
|
})
|
|
const callArg = mockSendMail.mock.calls[0][0]
|
|
expect(callArg.html).toContain('29.99')
|
|
expect(callArg.html).toContain('EUR')
|
|
})
|
|
|
|
it('includes orderId in the email body', async () => {
|
|
await sendOrderConfirmationEmail('customer@example.com', {
|
|
orderId: 'order-xyz',
|
|
grandTotal: 1000,
|
|
currency: 'USD',
|
|
})
|
|
const callArg = mockSendMail.mock.calls[0][0]
|
|
expect(callArg.html).toContain('order-xyz')
|
|
})
|
|
|
|
it('includes account orders link', async () => {
|
|
await sendOrderConfirmationEmail('customer@example.com', {
|
|
orderId: 'order-1',
|
|
grandTotal: 100,
|
|
currency: 'EUR',
|
|
})
|
|
const callArg = mockSendMail.mock.calls[0][0]
|
|
expect(callArg.html).toContain('/account/orders')
|
|
})
|
|
})
|
|
|
|
// ─── sendPasswordResetEmail ───────────────────────────────────────────────────
|
|
|
|
describe('sendPasswordResetEmail', () => {
|
|
it('sends email with reset token in URL', async () => {
|
|
await sendPasswordResetEmail('user@example.com', 'reset-token-abc')
|
|
const callArg = mockSendMail.mock.calls[0][0]
|
|
expect(callArg.html).toContain('reset-token-abc')
|
|
expect(callArg.to).toBe('user@example.com')
|
|
})
|
|
|
|
it('subject mentions password reset', async () => {
|
|
await sendPasswordResetEmail('user@example.com', 'token')
|
|
const callArg = mockSendMail.mock.calls[0][0]
|
|
expect(callArg.subject).toMatch(/[Pp]assword [Rr]eset/)
|
|
})
|
|
})
|