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:
@@ -485,7 +485,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { Menu, X, ChevronDown } from 'lucide-vue-next'
|
import { Menu, X, ChevronDown } from 'lucide-vue-next'
|
||||||
|
|
||||||
@@ -520,9 +520,6 @@ const currentSubmenu = computed(() => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
// Manuelles Toggle für Click-Events
|
|
||||||
const manualSubmenu = ref(null)
|
|
||||||
|
|
||||||
const toggleMobileSubmenu = (menu) => {
|
const toggleMobileSubmenu = (menu) => {
|
||||||
mobileSubmenu.value = mobileSubmenu.value === menu ? null : menu
|
mobileSubmenu.value = mobileSubmenu.value === menu ? null : menu
|
||||||
}
|
}
|
||||||
@@ -557,7 +554,7 @@ const loadMannschaften = async () => {
|
|||||||
}
|
}
|
||||||
values.push(current.trim())
|
values.push(current.trim())
|
||||||
|
|
||||||
if (values.length < 10) return null
|
if (!values[0]) return null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mannschaft: values[0].trim(),
|
mannschaft: values[0].trim(),
|
||||||
@@ -583,17 +580,23 @@ const toggleCmsDropdown = () => {
|
|||||||
showCmsDropdown.value = !showCmsDropdown.value
|
showCmsDropdown.value = !showCmsDropdown.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDocumentClick = (e) => {
|
||||||
|
if (!e.target.closest('.relative.inline-block')) {
|
||||||
|
showCmsDropdown.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMannschaften()
|
loadMannschaften()
|
||||||
checkGalleryImages()
|
checkGalleryImages()
|
||||||
authStore.checkAuth()
|
authStore.checkAuth()
|
||||||
|
|
||||||
// Close CMS dropdown when clicking outside
|
// Close CMS dropdown when clicking outside
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', handleDocumentClick)
|
||||||
if (!e.target.closest('.relative.inline-block')) {
|
})
|
||||||
showCmsDropdown.value = false
|
|
||||||
}
|
onUnmounted(() => {
|
||||||
})
|
document.removeEventListener('click', handleDocumentClick)
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleSubmenu = (menu) => {
|
const toggleSubmenu = (menu) => {
|
||||||
|
|||||||
1188
package-lock.json
generated
1188
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,9 @@
|
|||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview --port 3100",
|
"preview": "nuxt preview --port 3100",
|
||||||
"start": "nuxt start --port 3100",
|
"start": "nuxt start --port 3100",
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinia/nuxt": "^0.11.2",
|
"@pinia/nuxt": "^0.11.2",
|
||||||
@@ -29,6 +31,8 @@
|
|||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
"lucide-vue-next": "^0.344.0",
|
"lucide-vue-next": "^0.344.0",
|
||||||
"postcss": "^8.4.0",
|
"postcss": "^8.4.0",
|
||||||
"tailwindcss": "^3.4.0"
|
"tailwindcss": "^3.4.0",
|
||||||
|
"supertest": "^7.1.0",
|
||||||
|
"vitest": "^2.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,9 @@ Satzungsänderungen können nur in einer Mitgliederversammlung mit einer Mehrhei
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('PDF Upload Error:', error)
|
console.error('PDF Upload Error:', error)
|
||||||
|
if (error.statusCode) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: error.message || 'Fehler beim Verarbeiten der PDF-Datei'
|
statusMessage: error.message || 'Fehler beim Verarbeiten der PDF-Datei'
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Speichern der CSV-Datei:', error)
|
console.error('Fehler beim Speichern der CSV-Datei:', error)
|
||||||
|
if (error.statusCode) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: 'Fehler beim Speichern der Datei'
|
statusMessage: 'Fehler beim Speichern der Datei'
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ export default defineEventHandler(async (event) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim PDF-Upload:', error)
|
console.error('Fehler beim PDF-Upload:', error)
|
||||||
|
|
||||||
|
if (error.statusCode) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
if (error.code === 'LIMIT_FILE_SIZE') {
|
if (error.code === 'LIMIT_FILE_SIZE') {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 413,
|
statusCode: 413,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
// Send approval email
|
// Send approval email
|
||||||
try {
|
try {
|
||||||
const transporter = nodemailer.createTransporter({
|
const transporter = nodemailer.createTransport({
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
port: process.env.SMTP_PORT || 587,
|
port: process.env.SMTP_PORT || 587,
|
||||||
secure: false,
|
secure: false,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SMTP-Konfiguration (hier können Sie Ihre SMTP-Daten eintragen)
|
// SMTP-Konfiguration (hier können Sie Ihre SMTP-Daten eintragen)
|
||||||
const transporter = nodemailer.createTransporter({
|
const transporter = nodemailer.createTransport({
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
port: process.env.SMTP_PORT || 587,
|
port: process.env.SMTP_PORT || 587,
|
||||||
secure: false, // true für 465, false für andere Ports
|
secure: false, // true für 465, false für andere Ports
|
||||||
|
|||||||
168
tests/auth-endpoints.spec.ts
Normal file
168
tests/auth-endpoints.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
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' })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
96
tests/cms-files-endpoints.spec.ts
Normal file
96
tests/cms-files-endpoints.spec.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createEvent, mockSuccessReadBody } from './setup'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
|
||||||
|
vi.mock('multer', () => {
|
||||||
|
const single = vi.fn((field) => (req, _res, cb) => {
|
||||||
|
if (req.__mockMulterError) {
|
||||||
|
cb(req.__mockMulterError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.file = req.__mockFile || null
|
||||||
|
req.body = req.body || {}
|
||||||
|
cb(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
const multerFn = vi.fn(() => ({ single }))
|
||||||
|
const diskStorage = vi.fn(() => ({}))
|
||||||
|
multerFn.diskStorage = diskStorage
|
||||||
|
|
||||||
|
return {
|
||||||
|
default: multerFn,
|
||||||
|
diskStorage
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('child_process', () => ({
|
||||||
|
exec: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('util', () => ({
|
||||||
|
promisify: () => () => Promise.resolve({ stdout: 'PDF Inhalt', stderr: '' })
|
||||||
|
}))
|
||||||
|
|
||||||
|
import saveCsvHandler from '../server/api/cms/save-csv.post.js'
|
||||||
|
import uploadSpielplanHandler from '../server/api/cms/upload-spielplan-pdf.post.js'
|
||||||
|
import satzungUploadHandler from '../server/api/cms/satzung-upload.post.js'
|
||||||
|
|
||||||
|
describe('CMS File Endpoints', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/save-csv', () => {
|
||||||
|
it('validiert Eingaben', async () => {
|
||||||
|
const event = createEvent()
|
||||||
|
mockSuccessReadBody({})
|
||||||
|
|
||||||
|
await expect(saveCsvHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('speichert erlaubte Datei', async () => {
|
||||||
|
const event = createEvent()
|
||||||
|
mockSuccessReadBody({ filename: 'mannschaften.csv', content: 'data' })
|
||||||
|
vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined)
|
||||||
|
vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined)
|
||||||
|
|
||||||
|
const response = await saveCsvHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(fs.writeFile).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/upload-spielplan-pdf', () => {
|
||||||
|
it('erfordert Authorization Header', async () => {
|
||||||
|
const event = createEvent({ method: 'POST' })
|
||||||
|
event.node.req.__mockFile = { filename: 'file.pdf', originalname: 'orig.pdf', path: 'tmp', mimetype: 'application/pdf' }
|
||||||
|
event.node.req.body = { type: 'gesamt' }
|
||||||
|
|
||||||
|
await expect(uploadSpielplanHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('lädt PDF hoch und gibt Erfolg zurück', async () => {
|
||||||
|
const event = createEvent({ method: 'POST', headers: { authorization: 'Bearer token' } })
|
||||||
|
event.node.req.__mockFile = { filename: 'spielplan_gesamt.pdf', originalname: 'orig.pdf', path: 'tmp', mimetype: 'application/pdf' }
|
||||||
|
event.node.req.body = { type: 'gesamt' }
|
||||||
|
|
||||||
|
const response = await uploadSpielplanHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(response.filename).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/satzung-upload', () => {
|
||||||
|
it('verarbeitet hochgeladene Satzung', async () => {
|
||||||
|
const event = createEvent({ method: 'POST' })
|
||||||
|
event.node.req.__mockFile = { path: 'public/documents/satzung.pdf', filename: 'satzung.pdf', originalname: 'satzung.pdf', mimetype: 'application/pdf' }
|
||||||
|
vi.spyOn(fs, 'readFile').mockResolvedValueOnce(JSON.stringify({ seiten: {}, vorstand: { vorsitzender: { email: '' }, schriftfuehrer: { email: '' } } }))
|
||||||
|
vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined)
|
||||||
|
|
||||||
|
const response = await satzungUploadHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(fs.writeFile).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
138
tests/cms-users-endpoints.spec.ts
Normal file
138
tests/cms-users-endpoints.spec.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createEvent, mockSuccessReadBody } from './setup'
|
||||||
|
|
||||||
|
vi.mock('../server/utils/auth.js', () => ({
|
||||||
|
getUserFromToken: vi.fn(),
|
||||||
|
readUsers: vi.fn(),
|
||||||
|
writeUsers: 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 usersListHandler from '../server/api/cms/users/list.get.js'
|
||||||
|
import usersApproveHandler from '../server/api/cms/users/approve.post.js'
|
||||||
|
import usersDeactivateHandler from '../server/api/cms/users/deactivate.post.js'
|
||||||
|
import usersRejectHandler from '../server/api/cms/users/reject.post.js'
|
||||||
|
import usersUpdateRoleHandler from '../server/api/cms/users/update-role.post.js'
|
||||||
|
|
||||||
|
describe('CMS User Management Endpoints', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const adminEvent = () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
authUtils.getUserFromToken.mockResolvedValue({ id: 'admin', role: 'admin' })
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('GET /api/cms/users/list', () => {
|
||||||
|
it('verweigert Zugriff für Nicht-Admins', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
authUtils.getUserFromToken.mockResolvedValue({ id: 'user', role: 'mitglied' })
|
||||||
|
|
||||||
|
await expect(usersListHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('liefert Benutzerliste ohne Passwörter', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
authUtils.readUsers.mockResolvedValue([{ id: '1', email: 'a@b.de', name: 'Anna', role: 'mitglied', phone: '1', active: true, created: 'now', lastLogin: null, password: 'secret' }])
|
||||||
|
|
||||||
|
const response = await usersListHandler(event)
|
||||||
|
expect(response.users[0]).not.toHaveProperty('password')
|
||||||
|
expect(response.users).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/users/approve', () => {
|
||||||
|
it('erfordert administrative Rolle', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
authUtils.getUserFromToken.mockResolvedValue({ id: 'user', role: 'mitglied' })
|
||||||
|
mockSuccessReadBody({ userId: '1' })
|
||||||
|
|
||||||
|
await expect(usersApproveHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('meldet 404 bei unbekanntem Benutzer', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '1' })
|
||||||
|
authUtils.readUsers.mockResolvedValue([])
|
||||||
|
|
||||||
|
await expect(usersApproveHandler(event)).rejects.toMatchObject({ statusCode: 404 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('aktiviert Benutzer und sendet Mail', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '1', role: 'vorstand' })
|
||||||
|
authUtils.readUsers.mockResolvedValue([{ id: '1', email: 'user@test.de', name: 'Udo', active: false }])
|
||||||
|
authUtils.writeUsers.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await usersApproveHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(authUtils.writeUsers).toHaveBeenCalled()
|
||||||
|
expect(nodemailer.default.createTransport).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/users/deactivate', () => {
|
||||||
|
it('verhindert Selbst-Deaktivierung', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: 'admin' })
|
||||||
|
await expect(usersDeactivateHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('deaktiviert Benutzer', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '1' })
|
||||||
|
authUtils.readUsers.mockResolvedValue([{ id: '1', active: true }])
|
||||||
|
authUtils.writeUsers.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await usersDeactivateHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(authUtils.writeUsers).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/users/reject', () => {
|
||||||
|
it('löscht Benutzer aus der Liste', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '2' })
|
||||||
|
authUtils.readUsers.mockResolvedValue([{ id: '1' }, { id: '2' }])
|
||||||
|
authUtils.writeUsers.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await usersRejectHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(authUtils.writeUsers).toHaveBeenCalledWith([{ id: '1' }])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/cms/users/update-role', () => {
|
||||||
|
it('validiert Rolle', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '1', role: 'invalid' })
|
||||||
|
|
||||||
|
await expect(usersUpdateRoleHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('aktualisiert Rolle', async () => {
|
||||||
|
const event = adminEvent()
|
||||||
|
mockSuccessReadBody({ userId: '1', role: 'vorstand' })
|
||||||
|
authUtils.readUsers.mockResolvedValue([{ id: '1', role: 'mitglied' }])
|
||||||
|
authUtils.writeUsers.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await usersUpdateRoleHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(authUtils.writeUsers).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
158
tests/members-endpoints.spec.ts
Normal file
158
tests/members-endpoints.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
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(),
|
||||||
|
readUsers: vi.fn(),
|
||||||
|
readMembers: vi.fn(),
|
||||||
|
writeUsers: vi.fn(),
|
||||||
|
readSessions: vi.fn(),
|
||||||
|
writeSessions: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('../server/utils/members.js', () => ({
|
||||||
|
readMembers: vi.fn(),
|
||||||
|
writeMembers: vi.fn(),
|
||||||
|
saveMember: vi.fn(),
|
||||||
|
deleteMember: vi.fn(),
|
||||||
|
normalizeDate: vi.fn((value) => value)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const authUtils = await import('../server/utils/auth.js')
|
||||||
|
const memberUtils = await import('../server/utils/members.js')
|
||||||
|
|
||||||
|
import membersGetHandler from '../server/api/members.get.js'
|
||||||
|
import membersPostHandler from '../server/api/members.post.js'
|
||||||
|
import membersDeleteHandler from '../server/api/members.delete.js'
|
||||||
|
import membersBulkHandler from '../server/api/members/bulk.post.js'
|
||||||
|
|
||||||
|
describe('Members API Endpoints', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /api/members', () => {
|
||||||
|
it('verlangt Authentifizierung', async () => {
|
||||||
|
const event = createEvent()
|
||||||
|
await expect(membersGetHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('gibt zusammengeführte Mitglieder zurück', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||||
|
memberUtils.readMembers.mockResolvedValue([
|
||||||
|
{ id: 'm1', firstName: 'Anna', lastName: 'Muster', email: 'anna@club.de' }
|
||||||
|
])
|
||||||
|
authUtils.readUsers.mockResolvedValue([
|
||||||
|
{ id: 'u1', name: 'Ben Nutzer', email: 'ben@club.de', role: 'mitglied', active: true }
|
||||||
|
])
|
||||||
|
|
||||||
|
const response = await membersGetHandler(event)
|
||||||
|
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(response.members).toHaveLength(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/members', () => {
|
||||||
|
const baseBody = {
|
||||||
|
firstName: 'Lisa',
|
||||||
|
lastName: 'Beispiel',
|
||||||
|
geburtsdatum: '2000-01-01',
|
||||||
|
email: 'lisa@example.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
it('verweigert Zugriff ohne Token', async () => {
|
||||||
|
const event = createEvent()
|
||||||
|
mockSuccessReadBody(baseBody)
|
||||||
|
await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 401 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('verlangt Admin- oder Vorstand-Rolle', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody(baseBody)
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '2' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '2', role: 'mitglied' })
|
||||||
|
|
||||||
|
await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 403 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('gibt 409 bei Duplikaten zurück', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody(baseBody)
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '2' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '2', role: 'admin' })
|
||||||
|
memberUtils.saveMember.mockRejectedValue(new Error('existiert bereits'))
|
||||||
|
|
||||||
|
await expect(membersPostHandler(event)).rejects.toMatchObject({ statusCode: 409 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('speichert Mitglied erfolgreich', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody(baseBody)
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '2' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '2', role: 'admin' })
|
||||||
|
memberUtils.saveMember.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await membersPostHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(memberUtils.saveMember).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('DELETE /api/members', () => {
|
||||||
|
it('validiert die ID', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody({})
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' })
|
||||||
|
|
||||||
|
await expect(membersDeleteHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('löscht Mitglied erfolgreich', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody({ id: 'm1' })
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '1', role: 'vorstand' })
|
||||||
|
memberUtils.deleteMember.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await membersDeleteHandler(event)
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(memberUtils.deleteMember).toHaveBeenCalledWith('m1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('POST /api/members/bulk', () => {
|
||||||
|
const importBody = {
|
||||||
|
members: [
|
||||||
|
{ firstName: 'Paul', lastName: 'Team', geburtsdatum: '2001-02-03' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
it('validiert members-Array', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody({ members: [] })
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' })
|
||||||
|
|
||||||
|
await expect(membersBulkHandler(event)).rejects.toMatchObject({ statusCode: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('importiert Mitglieder und schreibt Datei', async () => {
|
||||||
|
const event = createEvent({ cookies: { auth_token: 'token' } })
|
||||||
|
mockSuccessReadBody(importBody)
|
||||||
|
authUtils.verifyToken.mockReturnValue({ id: '1' })
|
||||||
|
authUtils.getUserById.mockResolvedValue({ id: '1', role: 'admin' })
|
||||||
|
memberUtils.readMembers.mockResolvedValue([])
|
||||||
|
memberUtils.writeMembers.mockResolvedValue(true)
|
||||||
|
|
||||||
|
const response = await membersBulkHandler(event)
|
||||||
|
|
||||||
|
expect(response.success).toBe(true)
|
||||||
|
expect(response.summary.imported).toBe(1)
|
||||||
|
expect(memberUtils.writeMembers).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
142
tests/public-endpoints.spec.ts
Normal file
142
tests/public-endpoints.spec.ts
Normal 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([])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
121
tests/setup.ts
Normal file
121
tests/setup.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { vi } from 'vitest'
|
||||||
|
|
||||||
|
type CookieStore = Record<string, { value: string; options?: Record<string, any> }>
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var __testCookies__: WeakMap<any, CookieStore>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.__testCookies__) {
|
||||||
|
global.__testCookies__ = new WeakMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCookieStore = (event: any): CookieStore => {
|
||||||
|
if (!global.__testCookies__.has(event)) {
|
||||||
|
global.__testCookies__.set(event, {})
|
||||||
|
}
|
||||||
|
return global.__testCookies__.get(event) as CookieStore
|
||||||
|
}
|
||||||
|
|
||||||
|
global.readBody = vi.fn(async (event: any) => event.__body ?? null)
|
||||||
|
global.getQuery = (event: any) => event.__query ?? {}
|
||||||
|
global.getHeader = (event: any, name: string) => {
|
||||||
|
const headers = event.node?.req?.headers || {}
|
||||||
|
const key = Object.keys(headers).find(h => h.toLowerCase() === name.toLowerCase())
|
||||||
|
return key ? headers[key] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
global.setCookie = (event: any, name: string, value: string, options = {}) => {
|
||||||
|
const store = getCookieStore(event)
|
||||||
|
store[name] = { value, options }
|
||||||
|
}
|
||||||
|
|
||||||
|
global.getCookie = (event: any, name: string) => {
|
||||||
|
const store = getCookieStore(event)
|
||||||
|
return store[name]?.value ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
global.deleteCookie = (event: any, name: string) => {
|
||||||
|
const store = getCookieStore(event)
|
||||||
|
if (store[name]) {
|
||||||
|
delete store[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.createError = ({ statusCode = 500, statusMessage, message }: any) => {
|
||||||
|
const error = new Error(statusMessage || message || 'Unbekannter Fehler')
|
||||||
|
error.name = 'H3Error'
|
||||||
|
// @ts-ignore
|
||||||
|
error.statusCode = statusCode
|
||||||
|
// @ts-ignore
|
||||||
|
error.statusMessage = statusMessage || message || 'Fehler'
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
global.useRuntimeConfig = () => ({
|
||||||
|
encryptionKey: 'test-encryption-key',
|
||||||
|
smtp: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 1025,
|
||||||
|
user: 'test',
|
||||||
|
pass: 'pass'
|
||||||
|
},
|
||||||
|
membership: {
|
||||||
|
adminEmails: ['admin@test.local']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
global.defineEventHandler = (handler: any) => handler
|
||||||
|
|
||||||
|
export const createEvent = (overrides: Record<string, any> = {}) => {
|
||||||
|
const event = {
|
||||||
|
context: {},
|
||||||
|
node: {
|
||||||
|
req: {
|
||||||
|
headers: overrides.headers || {},
|
||||||
|
method: overrides.method || 'GET'
|
||||||
|
},
|
||||||
|
res: {
|
||||||
|
setHeader: vi.fn(),
|
||||||
|
getHeader: vi.fn(),
|
||||||
|
end: vi.fn(),
|
||||||
|
statusCode: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__body: overrides.body,
|
||||||
|
__query: overrides.query,
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrides.cookies) {
|
||||||
|
const store = getCookieStore(event)
|
||||||
|
Object.entries(overrides.cookies).forEach(([key, value]) => {
|
||||||
|
store[key] = { value: value as string }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetEventMocks = () => {
|
||||||
|
(global.readBody as any).mockClear()
|
||||||
|
global.__testCookies__ = new WeakMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const expectError = async (fn: () => Promise<any>, statusCode: number) => {
|
||||||
|
try {
|
||||||
|
await fn()
|
||||||
|
throw new Error('Erwarteter Fehler wurde nicht ausgelöst')
|
||||||
|
} catch (error: any) {
|
||||||
|
expect(error.statusCode).toBe(statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mockSuccessReadBody = (payload: any) => {
|
||||||
|
(global.readBody as any).mockResolvedValue(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
resetEventMocks()
|
||||||
|
})
|
||||||
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'node',
|
||||||
|
setupFiles: ['./tests/setup.ts'],
|
||||||
|
coverage: {
|
||||||
|
provider: 'istanbul',
|
||||||
|
reporter: ['text', 'lcov'],
|
||||||
|
reportsDirectory: './coverage'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user