Add sharp image processing library and update dependencies in package.json and package-lock.json; enhance Navigation component with new 'Galerie' link for improved user navigation.

This commit is contained in:
Torsten Schulz (local)
2025-12-17 17:06:47 +01:00
parent b6f39f83a8
commit 220c337996
12 changed files with 1679 additions and 5 deletions

View File

@@ -0,0 +1,163 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createEvent, mockSuccessReadBody } from './setup'
import fs from 'fs/promises'
import sharp from 'sharp'
vi.mock('../server/utils/auth.js', () => ({
getUserFromToken: vi.fn(),
verifyToken: vi.fn(),
readUsers: vi.fn(),
writeUsers: vi.fn()
}))
vi.mock('sharp', () => ({
default: vi.fn(() => ({
resize: vi.fn().mockReturnThis(),
toFile: vi.fn().mockResolvedValue({})
}))
}))
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
}
})
const authUtils = await import('../server/utils/auth.js')
import uploadHandler from '../server/api/galerie/upload.post.js'
import listHandler from '../server/api/galerie/list.get.js'
import imageHandler from '../server/api/galerie/[id].get.js'
describe('Galerie API Endpoints', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.spyOn(fs, 'readFile').mockResolvedValue('[]')
vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined)
vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined)
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
})
describe('POST /api/galerie/upload', () => {
it('erfordert Authentifizierung', async () => {
const event = createEvent({ method: 'POST' })
event.node.req.__mockFile = { filename: 'test.jpg', path: 'tmp/test.jpg', originalname: 'test.jpg', mimetype: 'image/jpeg' }
event.node.req.body = { title: 'Test', isPublic: 'true' }
await expect(uploadHandler(event)).rejects.toMatchObject({ statusCode: 401 })
})
it('erfordert Admin- oder Vorstand-Rolle', async () => {
const event = createEvent({ method: 'POST', cookies: { auth_token: 'token' } })
event.node.req.__mockFile = { filename: 'test.jpg', path: 'tmp/test.jpg', originalname: 'test.jpg', mimetype: 'image/jpeg' }
event.node.req.body = { title: 'Test', isPublic: 'true' }
authUtils.verifyToken.mockReturnValue({ id: '1' })
authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'mitglied', active: true })
await expect(uploadHandler(event)).rejects.toMatchObject({ statusCode: 403 })
})
it('lädt Bild hoch und erstellt Thumbnail', async () => {
const event = createEvent({ method: 'POST', cookies: { auth_token: 'token' } })
event.node.req.__mockFile = { filename: 'test.jpg', path: 'tmp/test.jpg', originalname: 'test.jpg', mimetype: 'image/jpeg' }
event.node.req.body = { title: 'Test Bild', description: 'Beschreibung', isPublic: 'true' }
authUtils.verifyToken.mockReturnValue({ id: '1' })
authUtils.getUserFromToken.mockResolvedValue({ id: '1', role: 'admin', active: true })
const response = await uploadHandler(event)
expect(response.success).toBe(true)
expect(sharp).toHaveBeenCalled()
expect(fs.writeFile).toHaveBeenCalled()
})
})
describe('GET /api/galerie/list', () => {
it('zeigt öffentliche Bilder für alle', async () => {
const event = createEvent()
vi.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify([
{ id: '1', title: 'Öffentlich', isPublic: true, previewFilename: 'preview_1.jpg', uploadedAt: '2025-01-01' },
{ id: '2', title: 'Privat', isPublic: false, previewFilename: 'preview_2.jpg', uploadedAt: '2025-01-02' }
]))
const response = await listHandler(event)
expect(response.success).toBe(true)
expect(response.images).toHaveLength(1)
expect(response.images[0].id).toBe('1')
})
it('zeigt alle Bilder für eingeloggte Mitglieder', async () => {
const event = createEvent({ cookies: { auth_token: 'token' } })
vi.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify([
{ id: '1', title: 'Öffentlich', isPublic: true, previewFilename: 'preview_1.jpg', uploadedAt: '2025-01-01' },
{ id: '2', title: 'Privat', isPublic: false, previewFilename: 'preview_2.jpg', uploadedAt: '2025-01-02' }
]))
authUtils.verifyToken.mockReturnValue({ id: '1' })
authUtils.getUserFromToken.mockResolvedValue({ id: '1', active: true })
const response = await listHandler(event)
expect(response.images).toHaveLength(2)
})
})
describe('GET /api/galerie/[id]', () => {
it('verweigert Zugriff auf private Bilder für nicht eingeloggte Benutzer', async () => {
const event = createEvent()
event.context.params = { id: '1' }
vi.spyOn(fs, 'readFile')
.mockResolvedValueOnce(JSON.stringify([
{ id: '1', filename: 'test.jpg', previewFilename: 'preview_test.jpg', isPublic: false }
]))
await expect(imageHandler(event)).rejects.toMatchObject({ statusCode: 403 })
})
it('liefert Bild für eingeloggte Mitglieder', async () => {
const event = createEvent({ cookies: { auth_token: 'token' } })
event.context.params = { id: '1' }
vi.spyOn(fs, 'readFile')
.mockResolvedValueOnce(JSON.stringify([
{ id: '1', filename: 'test.jpg', previewFilename: 'preview_test.jpg', isPublic: false }
]))
.mockResolvedValueOnce(Buffer.from('image data'))
authUtils.verifyToken.mockReturnValue({ id: '1' })
authUtils.getUserFromToken.mockResolvedValue({ id: '1', active: true })
const response = await imageHandler(event)
expect(response).toBeInstanceOf(Buffer)
})
it('liefert Preview-Bild bei preview=true', async () => {
const event = createEvent({ query: { preview: 'true' } })
event.context.params = { id: '1' }
vi.spyOn(fs, 'readFile')
.mockResolvedValueOnce(JSON.stringify([
{ id: '1', filename: 'test.jpg', previewFilename: 'preview_test.jpg', isPublic: true }
]))
.mockResolvedValueOnce(Buffer.from('preview data'))
const response = await imageHandler(event)
expect(response).toBeInstanceOf(Buffer)
})
})
})

View File

@@ -20,11 +20,20 @@ const getCookieStore = (event: any): CookieStore => {
global.readBody = vi.fn(async (event: any) => event.__body ?? null)
global.getQuery = (event: any) => event.__query ?? {}
global.getRouterParam = (event: any, name: string) => {
return event.context?.params?.[name] ?? null
}
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.setHeader = vi.fn((event: any, name: string, value: string) => {
if (event.node?.res) {
event.node.res.headers = event.node.res.headers || {}
event.node.res.headers[name] = value
}
})
global.setCookie = (event: any, name: string, value: string, options = {}) => {
const store = getCookieStore(event)