import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import fs from 'fs/promises' vi.mock('nodemailer', () => { const sendMail = vi.fn().mockResolvedValue({ messageId: 'test-message' }) const createTransport = vi.fn(() => ({ sendMail })) return { default: { createTransport }, createTransport } }) vi.mock('../server/utils/auth.js', () => ({ readUsers: vi.fn(), migrateUserRoles: vi.fn((user) => { if (!user) return user if (Array.isArray(user.roles)) return user if (user.role) { user.roles = [user.role] delete user.role } else { user.roles = ['mitglied'] } return user }), isHiddenUser: vi.fn(user => user?.hidden === true || user?.invisible === true || user?.isHidden === true || user?.systemAccount === true || user?.accountType === 'playstore_review') })) const nodemailer = await import('nodemailer') const authUtils = await import('../server/utils/auth.js') const emailService = await import('../server/utils/email-service.js') describe('Email service recipients', () => { beforeEach(() => { vi.restoreAllMocks() vi.clearAllMocks() process.env.SMTP_USER = 'smtp@example.com' process.env.SMTP_PASS = 'smtp-password' authUtils.readUsers.mockResolvedValue([]) }) afterEach(() => { delete process.env.SMTP_USER delete process.env.SMTP_PASS delete process.env.NODE_ENV delete process.env.APP_ENV delete process.env.DEBUG }) it('sendet bei DEBUG=FALSE in production an Vorstand statt Entwickleradresse', async () => { process.env.NODE_ENV = 'production' process.env.APP_ENV = 'test' process.env.DEBUG = 'FALSE' vi.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify({ vorstand: { vorsitzender: { email: 'vorstand@example.com' } } })) await emailService.sendRegistrationNotification({ name: 'Max Muster', email: 'max@example.com', phone: '069123456' }) const transporter = nodemailer.default.createTransport.mock.results[0].value expect(transporter.sendMail).toHaveBeenCalledWith(expect.objectContaining({ to: 'vorstand@example.com' })) expect(transporter.sendMail.mock.calls[0][0].to).not.toContain('tsschulz@tsschulz.de') }) it('bevorzugt aktive Vorstand-Benutzer vor config.json', async () => { process.env.NODE_ENV = 'production' process.env.DEBUG = 'FALSE' authUtils.readUsers.mockResolvedValue([ { id: '1', email: 'rolle-vorstand@example.com', roles: ['vorstand'], active: true }, { id: '2', email: 'inaktiv@example.com', roles: ['vorstand'], active: false }, { id: '3', email: 'mitglied@example.com', roles: ['mitglied'], active: true } ]) vi.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify({ vorstand: { vorsitzender: { email: 'config-vorstand@example.com' } } })) await emailService.sendRegistrationNotification({ name: 'Max Muster', email: 'max@example.com' }) const transporter = nodemailer.default.createTransport.mock.results[0].value const to = transporter.sendMail.mock.calls[0][0].to expect(to).toBe('rolle-vorstand@example.com') expect(to).not.toContain('config-vorstand@example.com') expect(to).not.toContain('inaktiv@example.com') }) it('sendet nur bei explizitem DEBUG=true an die Entwickleradresse', async () => { process.env.NODE_ENV = 'production' process.env.DEBUG = 'true' vi.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify({ vorstand: { vorsitzender: { email: 'vorstand@example.com' } } })) await emailService.sendRegistrationNotification({ name: 'Max Muster', email: 'max@example.com' }) const transporter = nodemailer.default.createTransport.mock.results[0].value expect(transporter.sendMail.mock.calls[0][0].to).toBe('tsschulz@tsschulz.de') }) })