import { describe, it, expect } from 'vitest'; import { Hono } from 'hono'; import { cardsRouter } from '../src/routes/cards.ts'; import type { CardsDb } from '../src/db/connection.ts'; /** * Routen-Tests ohne echte DB. Drizzle-Aufrufe werden durch eine * minimale Stub-DB ersetzt, die nur die Validations-Pfade abdeckt. */ function buildApp() { const app = new Hono(); const stub = { select: () => ({ from: () => ({ where: () => ({ limit: () => [] }), }), }), }; app.route('/api/v1/cards', cardsRouter({ db: stub as unknown as CardsDb })); return { app }; } describe('cardsRouter — auth-gate', () => { it('GET ohne X-User-Id ist 401', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards'); expect(res.status).toBe(401); }); }); describe('cardsRouter — Input-Validation', () => { it('POST mit leerem Body ist 422', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards', { method: 'POST', headers: { 'X-User-Id': 'u-1', 'Content-Type': 'application/json' }, body: '{}', }); expect(res.status).toBe(422); }); it('POST mit basic-Card ohne back-Feld ist 422', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards', { method: 'POST', headers: { 'X-User-Id': 'u-1', 'Content-Type': 'application/json' }, body: JSON.stringify({ deck_id: 'd-1', type: 'basic', fields: { front: 'Q' }, }), }); expect(res.status).toBe(422); }); it('POST mit unknown CardType ist 422', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards', { method: 'POST', headers: { 'X-User-Id': 'u-1', 'Content-Type': 'application/json' }, body: JSON.stringify({ deck_id: 'd-1', type: 'cloze', fields: { text: 'x' }, }), }); expect(res.status).toBe(422); }); it('PATCH mit extra prop ist 422', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards/c-1', { method: 'PATCH', headers: { 'X-User-Id': 'u-1', 'Content-Type': 'application/json' }, body: JSON.stringify({ fields: { front: 'X' }, leak: 'bad' }), }); expect(res.status).toBe(422); }); it('POST mit gültigem basic-Card erreicht Deck-Lookup (404 bei stub)', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/cards', { method: 'POST', headers: { 'X-User-Id': 'u-1', 'Content-Type': 'application/json' }, body: JSON.stringify({ deck_id: 'd-1', type: 'basic', fields: { front: 'Q', back: 'A' }, }), }); // Stub-DB gibt empty array → Deck-Not-Found-Pfad expect(res.status).toBe(404); const body = (await res.json()) as { error: string }; expect(body.error).toBe('deck_not_found'); }); });