Update dependencies in package.json and package-lock.json; add testing scripts for Vitest, and include new packages such as supertest and vitest. Refactor Navigation component to improve event handling and cleanup, ensuring better performance and user experience. Enhance error handling in various API endpoints for PDF uploads and CSV saves, ensuring robust error propagation. Update nodemailer transport configuration for consistency across API handlers.

This commit is contained in:
Torsten Schulz (local)
2025-11-10 13:08:50 +01:00
parent 95d7a3dfe8
commit bde1d32b14
15 changed files with 2055 additions and 15 deletions

View File

@@ -0,0 +1,142 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createEvent, mockSuccessReadBody } from './setup'
import fsPromises from 'fs/promises'
import { promises as fs } from 'fs'
vi.mock('nodemailer', () => {
const sendMail = vi.fn().mockResolvedValue(true)
const createTransport = vi.fn(() => ({ sendMail }))
return {
default: { createTransport },
createTransport
}
})
vi.mock('../server/utils/news.js', () => ({
readNews: vi.fn()
}))
const nodemailer = await import('nodemailer')
const newsUtils = await import('../server/utils/news.js')
import contactHandler from '../server/api/contact.post.js'
import galerieHandler from '../server/api/galerie.get.js'
import newsPublicHandler from '../server/api/news-public.get.js'
import termineHandler from '../server/api/termine.get.js'
import spielplaeneHandler from '../server/api/spielplaene.get.js'
describe('Öffentliche API-Endpunkte', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.clearAllMocks()
})
describe('POST /api/contact', () => {
it('validiert Pflichtfelder', async () => {
const event = createEvent()
mockSuccessReadBody({})
await expect(contactHandler(event)).rejects.toMatchObject({ statusCode: 400 })
})
it('validiert E-Mail-Adresse', async () => {
const event = createEvent()
mockSuccessReadBody({ name: 'Max', email: 'invalid', subject: 'Hi', message: 'Test' })
await expect(contactHandler(event)).rejects.toMatchObject({ statusCode: 400 })
})
it('sendet E-Mail bei gültigen Daten', async () => {
const event = createEvent()
mockSuccessReadBody({ name: 'Max', email: 'max@example.com', subject: 'Frage', message: 'Hallo' })
const response = await contactHandler(event)
expect(response.success).toBe(true)
expect(nodemailer.default.createTransport).toHaveBeenCalled()
})
})
describe('GET /api/galerie', () => {
it('liefert leeres Array, wenn Verzeichnis fehlt', async () => {
vi.spyOn(fs, 'access').mockRejectedValue(new Error('not found'))
const result = await galerieHandler(createEvent())
expect(result).toEqual([])
})
it('filtert nur Bilddateien', async () => {
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
vi.spyOn(fs, 'readdir').mockResolvedValue(['foto-1.jpg', 'readme.md'])
const result = await galerieHandler(createEvent())
expect(result).toHaveLength(1)
expect(result[0].filename).toBe('foto-1.jpg')
})
})
describe('GET /api/news-public', () => {
it('filtert nicht öffentliche News', async () => {
const now = new Date()
newsUtils.readNews.mockResolvedValue([
{ id: 1, isPublic: true, isHidden: false, created: now.toISOString(), title: 'ok' },
{ id: 2, isPublic: false, isHidden: false, created: now.toISOString() },
{ id: 3, isPublic: true, isHidden: true, created: now.toISOString() }
])
const response = await newsPublicHandler(createEvent())
expect(response.success).toBe(true)
expect(response.news).toHaveLength(1)
expect(response.news[0].id).toBe(1)
})
it('begrenzt Ergebnis auf drei Einträge', async () => {
const now = Date.now()
newsUtils.readNews.mockResolvedValue(
Array.from({ length: 5 }).map((_, index) => ({
id: index + 1,
isPublic: true,
isHidden: false,
created: new Date(now - index * 1000).toISOString()
}))
)
const response = await newsPublicHandler(createEvent())
expect(response.news).toHaveLength(3)
expect(response.news[0].id).toBe(1)
})
})
describe('GET /api/termine', () => {
it('parst CSV-Dateien', async () => {
const csv = 'Datum,Uhrzeit,Titel,Beschreibung,Kategorie\n2025-01-01,19:00,Training,Beschreibung,Sport'
vi.spyOn(fsPromises, 'readFile').mockResolvedValue(csv)
const response = await termineHandler(createEvent())
expect(response.success).toBe(true)
expect(response.termine[0]).toMatchObject({ titel: 'Training', kategorie: 'Sport' })
})
it('gibt leeres Array bei Fehlern zurück', async () => {
vi.spyOn(fsPromises, 'readFile').mockRejectedValue(new Error('kaputt'))
const response = await termineHandler(createEvent())
expect(response.termine).toEqual([])
})
})
describe('GET /api/spielplaene', () => {
it('filtert nur erlaubte Dateitypen', async () => {
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
vi.spyOn(fs, 'readdir').mockResolvedValue(['plan.pdf', 'notizen.txt'])
const result = await spielplaeneHandler(createEvent())
expect(result).toEqual(['plan.pdf'])
})
it('gibt leeres Array zurück, wenn Verzeichnis fehlt', async () => {
vi.spyOn(fs, 'access').mockRejectedValue(new Error('not found'))
const result = await spielplaeneHandler(createEvent())
expect(result).toEqual([])
})
})
})