import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { Hono } from 'hono'; import { dsgvoRouter } from '../src/routes/dsgvo.ts'; import type { CardsDb } from '../src/db/connection.ts'; const ORIG_ENV = process.env.CARDS_DSGVO_SERVICE_KEY; const TEST_KEY = 'msk_test_dsgvo_key_42'; beforeEach(() => { process.env.CARDS_DSGVO_SERVICE_KEY = TEST_KEY; }); afterEach(() => { if (ORIG_ENV === undefined) { delete process.env.CARDS_DSGVO_SERVICE_KEY; } else { process.env.CARDS_DSGVO_SERVICE_KEY = ORIG_ENV; } }); function buildApp() { const stub = { select: () => ({ from: () => ({ where: () => [] as never[] }) }), delete: () => ({ where: () => ({ returning: async () => [] }) }), transaction: async (fn: (tx: unknown) => unknown) => fn({ delete: () => ({ where: () => ({ returning: async () => [] }) }), }), }; const app = new Hono(); app.route('/api/v1/dsgvo', dsgvoRouter({ db: stub as unknown as CardsDb })); return { app }; } describe('dsgvoRouter — Service-Key-Gate', () => { it('GET /export ohne Service-Key ist 401', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/export?user_id=u-1'); expect(res.status).toBe(401); }); it('POST /delete mit falschem Key ist 401', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/delete', { method: 'POST', headers: { 'X-Service-Key': 'wrong', 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: 'u-1' }), }); expect(res.status).toBe(401); }); it('GET /export mit Key + ohne user_id ist 400', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/export', { headers: { 'X-Service-Key': TEST_KEY }, }); expect(res.status).toBe(400); }); it('GET /export mit Key + user_id liefert JSON-Bundle', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/export?user_id=u-1', { headers: { 'X-Service-Key': TEST_KEY }, }); expect(res.status).toBe(200); const body = (await res.json()) as { user_id: string; app: string; data: { decks: unknown[]; cards: unknown[]; reviews: unknown[] }; }; expect(body.user_id).toBe('u-1'); expect(body.app).toBe('cards'); expect(Array.isArray(body.data.decks)).toBe(true); expect(Array.isArray(body.data.cards)).toBe(true); expect(Array.isArray(body.data.reviews)).toBe(true); }); it('POST /delete mit Key + user_id liefert counts', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/delete', { method: 'POST', headers: { 'X-Service-Key': TEST_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: 'u-1' }), }); expect(res.status).toBe(200); const body = (await res.json()) as { deleted: boolean; user_id: string; counts: { decks: number; import_jobs: number }; }; expect(body.deleted).toBe(true); expect(body.user_id).toBe('u-1'); expect(body.counts.decks).toBe(0); expect(body.counts.import_jobs).toBe(0); }); }); describe('dsgvoRouter — Key-not-configured Pfad', () => { it('Wenn ENV fehlt → 500 service_key_not_configured', async () => { delete process.env.CARDS_DSGVO_SERVICE_KEY; const { app } = buildApp(); const res = await app.request('/api/v1/dsgvo/export?user_id=u-1', { headers: { 'X-Service-Key': 'whatever' }, }); expect(res.status).toBe(500); const body = (await res.json()) as { error: string }; expect(body.error).toBe('service_key_not_configured'); }); });