import { beforeEach, describe, expect, it, vi } from 'vitest' import { createEvent, mockSuccessReadBody } from './setup' vi.mock('../server/utils/auth.js', () => ({ verifyToken: vi.fn(), getUserById: vi.fn(), hasAnyRole: vi.fn((user, ...roles) => { if (!user) return false const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []) return roles.some(r => userRoles.includes(r)) }) })) vi.mock('../server/utils/news.js', () => ({ readNews: vi.fn(), saveNews: vi.fn(), deleteNews: vi.fn() })) const authUtils = await import('../server/utils/auth.js') const newsUtils = await import('../server/utils/news.js') import newsGetHandler from '../server/api/news.get.js' import newsPostHandler from '../server/api/news.post.js' import newsDeleteHandler from '../server/api/news.delete.js' describe('News API Endpoints', () => { beforeEach(() => { vi.clearAllMocks() }) const adminEvent = () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['admin'], name: 'Admin' }) authUtils.hasAnyRole.mockReturnValue(true) return event } describe('GET /api/news', () => { it('verlangt Authentifizierung', async () => { const event = createEvent() await expect(newsGetHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('lehnt ungültiges Token ab', async () => { const event = createEvent({ cookies: { auth_token: 'bad' } }) authUtils.verifyToken.mockReturnValue(null) await expect(newsGetHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('liefert News nach Datum sortiert (neueste zuerst)', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) newsUtils.readNews.mockResolvedValue([ { id: 'a', title: 'Alt', created: '2024-01-01' }, { id: 'b', title: 'Neu', created: '2025-06-01' } ]) const result = await newsGetHandler(event) expect(result.success).toBe(true) expect(result.news[0].id).toBe('b') expect(result.news[1].id).toBe('a') }) }) describe('POST /api/news', () => { it('verlangt Authentifizierung', async () => { const event = createEvent() mockSuccessReadBody({}) await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('lehnt ungültiges Token ab', async () => { const event = createEvent({ cookies: { auth_token: 'bad' } }) authUtils.verifyToken.mockReturnValue(null) mockSuccessReadBody({ title: 'T', content: 'C' }) await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('verlangt Admin- oder Vorstand-Rolle', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['mitglied'] }) authUtils.hasAnyRole.mockReturnValue(false) mockSuccessReadBody({ title: 'T', content: 'C' }) await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('validiert Pflichtfelder – kein Titel', async () => { const event = adminEvent() mockSuccessReadBody({ content: 'Nur Inhalt ohne Titel' }) await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('validiert Pflichtfelder – kein Inhalt', async () => { const event = adminEvent() mockSuccessReadBody({ title: 'Nur Titel' }) await expect(newsPostHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('speichert News erfolgreich', async () => { const event = adminEvent() newsUtils.saveNews.mockResolvedValue(undefined) mockSuccessReadBody({ title: 'Neue Info', content: 'Inhalt hier', isPublic: true }) const result = await newsPostHandler(event) expect(result.success).toBe(true) expect(newsUtils.saveNews).toHaveBeenCalledWith( expect.objectContaining({ title: 'Neue Info', content: 'Inhalt hier', isPublic: true }) ) }) it('setzt autor auf den angemeldeten Benutzer', async () => { const event = adminEvent() newsUtils.saveNews.mockResolvedValue(undefined) mockSuccessReadBody({ title: 'Titel', content: 'Inhalt' }) await newsPostHandler(event) expect(newsUtils.saveNews).toHaveBeenCalledWith( expect.objectContaining({ author: 'Admin' }) ) }) }) describe('DELETE /api/news', () => { it('verlangt Authentifizierung', async () => { const event = createEvent() await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 401 }) }) it('verlangt Admin- oder Vorstand-Rolle', async () => { const event = createEvent({ cookies: { auth_token: 'token' } }) authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.getUserById.mockResolvedValue({ id: '1', roles: ['mitglied'] }) authUtils.hasAnyRole.mockReturnValue(false) await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 403 }) }) it('validiert fehlende News-ID', async () => { const event = adminEvent() // query ist leer → kein id await expect(newsDeleteHandler(event)).rejects.toMatchObject({ statusCode: 400 }) }) it('löscht News erfolgreich', async () => { const event = adminEvent() event.__query = { id: 'abc-123' } newsUtils.deleteNews.mockResolvedValue(undefined) const result = await newsDeleteHandler(event) expect(result.success).toBe(true) expect(newsUtils.deleteNews).toHaveBeenCalledWith('abc-123') }) }) })