import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createEvent } from './setup' vi.mock('nodemailer', () => ({ default: { createTransport: vi.fn(() => ({ sendMail: vi.fn().mockResolvedValue({ messageId: 'real' }) })) } })) vi.mock('../server/utils/auth.js', () => ({ readUsers: vi.fn().mockResolvedValue([]), getTokenFromEvent: vi.fn(), getUserFromToken: vi.fn() })) import nodemailer from 'nodemailer' import * as auth from '../server/utils/auth.js' import { createMailTransport } from '../server/utils/mail-transport.js' import { sendMembershipEmail, sendRegistrationNotification } from '../server/utils/email-service.js' describe('Mail delivery gate', () => { beforeEach(() => { vi.clearAllMocks() vi.stubEnv('MAIL_DELIVERY_DISABLED', '') vi.stubEnv('PLAYSTORE_REVIEW_EMAIL', '') auth.readUsers.mockResolvedValue([]) auth.getTokenFromEvent.mockReturnValue(undefined) }) afterEach(() => vi.unstubAllEnvs()) it('does not create an SMTP transport when all delivery is disabled', async () => { vi.stubEnv('MAIL_DELIVERY_DISABLED', 'TRUE') expect(await createMailTransport(createEvent(), {}).sendMail({ to: 'board@example.com' })) .toMatchObject({ suppressed: true, accepted: [] }) expect(nodemailer.createTransport).not.toHaveBeenCalled() }) it('suppresses board mail for an authenticated review user with another form address', async () => { auth.getTokenFromEvent.mockReturnValue('verified-by-auth') auth.getUserFromToken.mockResolvedValue({ accountType: 'playstore_review' }) await createMailTransport(createEvent(), {}).sendMail({ to: 'board@example.com', replyTo: 'other@example.com' }) expect(auth.getUserFromToken).toHaveBeenCalledWith('verified-by-auth') expect(nodemailer.createTransport).not.toHaveBeenCalled() }) it('suppresses signed-out contact mail using the configured review reply address', async () => { vi.stubEnv('PLAYSTORE_REVIEW_EMAIL', 'review@example.com') await createMailTransport(undefined, {}).sendMail({ to: 'board@example.com', replyTo: 'Review ' }) expect(nodemailer.createTransport).not.toHaveBeenCalled() }) it('checks stored review accounts in cc and bcc as well', async () => { auth.readUsers.mockResolvedValue([{ email: 'review@example.com', accountType: 'playstore_review' }]) await createMailTransport(undefined, {}).sendMail({ to: 'member@example.com', bcc: [{ address: 'review@example.com' }] }) expect(nodemailer.createTransport).not.toHaveBeenCalled() }) it('suppresses membership and registration notifications before SMTP/config loading', async () => { vi.stubEnv('PLAYSTORE_REVIEW_EMAIL', 'review@example.com') expect(await sendMembershipEmail({ email: 'review@example.com' }, '/unused.pdf')).toMatchObject({ success: true, suppressed: true }) expect(await sendRegistrationNotification({ email: 'review@example.com' })).toMatchObject({ success: true, suppressed: true }) expect(nodemailer.createTransport).not.toHaveBeenCalled() }) it('delivers ordinary member mail and does not trust a client demo flag', async () => { const event = createEvent({ body: { demo: true, accountType: 'playstore_review' } }) const mail = { to: 'member@example.com', text: 'Hello' } expect(await createMailTransport(event, { host: 'smtp.example.com' }).sendMail(mail)).toEqual({ messageId: 'real' }) expect(nodemailer.createTransport).toHaveBeenCalledWith({ host: 'smtp.example.com' }) expect(nodemailer.createTransport.mock.results[0].value.sendMail).toHaveBeenCalledWith(mail) }) })