import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createEvent, mockSuccessReadBody } from './setup' vi.mock('../server/utils/auth.js', () => { return { readUsers: vi.fn(), writeUsers: vi.fn(), verifyPassword: vi.fn(), generateToken: vi.fn(), createSession: vi.fn(), hashPassword: vi.fn(), verifyToken: vi.fn(), deleteSession: vi.fn(), getUserFromToken: vi.fn(), readSessions: vi.fn(), writeSessions: vi.fn() } }) vi.mock('nodemailer', () => { const sendMail = vi.fn().mockResolvedValue(true) const createTransport = vi.fn(() => ({ sendMail })) return { default: { createTransport }, createTransport } }) const authUtils = await import('../server/utils/auth.js') const nodemailer = await import('nodemailer') import loginHandler from '../server/api/auth/login.post.js' import logoutHandler from '../server/api/auth/logout.post.js' import registerHandler from '../server/api/auth/register.post.js' import resetPasswordHandler from '../server/api/auth/reset-password.post.js' import statusHandler from '../server/api/auth/status.get.js' describe('Auth API Endpoints', () => { beforeEach(() => { vi.clearAllMocks() }) describe('POST /api/auth/login', () => { it('wirft 400 bei fehlenden Feldern', async () => { const event = createEvent() mockSuccessReadBody({}) await expect(loginHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('wirft 401 bei unbekanntem Benutzer', async () => { const event = createEvent() mockSuccessReadBody({ email: 'test@example.com', password: 'password' }) authUtils.readUsers.mockResolvedValue([]) await expect(loginHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('gibt Token und Cookie bei Erfolg zurück', async () => { const event = createEvent() const user = { id: '1', email: 'test@example.com', password: 'hash', role: 'mitglied', active: true, lastLogin: null } mockSuccessReadBody({ email: user.email, password: 'plain' }) authUtils.readUsers.mockResolvedValue([user]) authUtils.verifyPassword.mockResolvedValue(true) authUtils.generateToken.mockReturnValue('jwt-token') authUtils.createSession.mockResolvedValue({}) authUtils.writeUsers.mockResolvedValue(true) const response = await loginHandler(event) expect(response.success).toBe(true) expect(response.token).toBe('jwt-token') expect(response.user).toMatchObject({ id: '1', email: user.email }) expect(authUtils.createSession).toHaveBeenCalledWith('1', 'jwt-token') expect(authUtils.writeUsers).toHaveBeenCalled() }) }) describe('POST /api/auth/logout', () => { it('löscht Session und Cookie, wenn Token vorhanden ist', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.deleteSession.mockResolvedValue() const response = await logoutHandler(event) expect(response.success).toBe(true) expect(authUtils.deleteSession).toHaveBeenCalledWith('token') }) it('wirft 500 bei Fehlern', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.deleteSession.mockRejectedValue(new Error('boom')) await expect(logoutHandler(event)).rejects.toMatchObject({ statusCode: 500 }) }) }) describe('POST /api/auth/register', () => { it('prüft Pflichtfelder', async () => { const event = createEvent() mockSuccessReadBody({}) await expect(registerHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('verhindert doppelte Benutzer', async () => { const event = createEvent() mockSuccessReadBody({ name: 'Max', email: 'max@example.com', password: '12345678' }) authUtils.readUsers.mockResolvedValue([{ email: 'max@example.com' }]) await expect(registerHandler(event)).rejects.toMatchObject({ statusCode: 409 }) }) it('legt Benutzer an und versendet E-Mails', async () => { const event = createEvent() mockSuccessReadBody({ name: 'Max', email: 'max@example.com', password: '12345678', phone: '123' }) authUtils.readUsers.mockResolvedValue([]) authUtils.hashPassword.mockResolvedValue('hashed') authUtils.writeUsers.mockResolvedValue(true) const response = await registerHandler(event) expect(response.success).toBe(true) expect(authUtils.writeUsers).toHaveBeenCalled() expect(nodemailer.default.createTransport).toHaveBeenCalled() }) }) describe('POST /api/auth/reset-password', () => { it('prüft Pflichtfelder', async () => { const event = createEvent() mockSuccessReadBody({}) const response = await resetPasswordHandler(event) expect(response.success).toBe(true) }) it('aktualisiert Passwort bei vorhandenem Benutzer', async () => { const event = createEvent() const user = { id: '1', email: 'user@example.com', name: 'User', password: 'hash' } mockSuccessReadBody({ email: user.email }) authUtils.readUsers.mockResolvedValue([user]) authUtils.hashPassword.mockResolvedValue('new-hash') authUtils.writeUsers.mockResolvedValue(true) const response = await resetPasswordHandler(event) expect(response.success).toBe(true) expect(authUtils.writeUsers).toHaveBeenCalled() }) }) describe('GET /api/auth/status', () => { it('liefert loggedOut, wenn kein Cookie gesetzt ist', async () => { const event = createEvent() const response = await statusHandler(event) expect(response.isLoggedIn).toBe(false) }) it('liefert Benutzerinformationen bei gültigem Token', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.getUserFromToken.mockResolvedValue({ id: '1', email: 'user@example.com', name: 'User', role: 'mitglied' }) const response = await statusHandler(event) expect(response.isLoggedIn).toBe(true) expect(response.user).toMatchObject({ id: '1' }) }) }) })