import { describe, it, expect } from 'vitest'; import { Hono } from 'hono'; import { shareRouter } from '../src/routes/share.ts'; import type { CardsDb } from '../src/db/connection.ts'; function buildApp() { const stub = {} as CardsDb; const app = new Hono(); app.route('/api/v1/share', shareRouter({ db: stub })); return { app }; } const userA = '00000000-0000-0000-0000-00000000aaaa'; const userB = '00000000-0000-0000-0000-00000000bbbb'; function envelope(overrides: Record = {}) { return { envelope_version: '0.1', share_id: '01HZ0EJW6V6N4SM3X5RHKR8B5T', // ULID-Pattern from: { app: 'zitate', app_version: '1.0.0', user_id: userA, timestamp: new Date().toISOString(), }, to: { app: 'cards', user_id: userA, }, type: 'mana/quote', payload: { text: 'Lernen ohne Nachdenken ist verlorene Mühe', source: 'Konfuzius' }, intent: 'user_action', consent_recorded_at: new Date().toISOString(), ...overrides, }; } describe('shareRouter — Auth-Gate', () => { it('POST /receive ohne X-User-Id ist 401', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/share/receive', { method: 'POST', body: JSON.stringify(envelope()), }); expect(res.status).toBe(401); }); }); describe('shareRouter — Envelope-Validation', () => { it('Body kein JSON → 400', async () => { const { app } = buildApp(); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: 'not-json', }); expect(res.status).toBe(400); }); it('Cross-User-Share ist 422 (envelope_invalid)', async () => { const { app } = buildApp(); const env = envelope({ to: { app: 'cards', user_id: userB }, // anderer User → Cross-User }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify(env), }); expect(res.status).toBe(422); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('envelope_invalid'); }); it('User-Mismatch (envelope.to.user_id != X-User-Id) ist 403', async () => { const { app } = buildApp(); const env = envelope({ from: { ...envelope().from, user_id: userB }, to: { app: 'cards', user_id: userB }, }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify(env), }); expect(res.status).toBe(403); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('user_id_mismatch'); }); it('Wrong-Recipient (to.app != cards) ist 422', async () => { const { app } = buildApp(); const env = envelope({ to: { app: 'memoro', user_id: userA } }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify(env), }); expect(res.status).toBe(422); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('wrong_recipient'); }); it('Unbekannter Type ist 422', async () => { const { app } = buildApp(); const env = envelope({ type: 'mana/unknown-thing', payload: {} }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify(env), }); expect(res.status).toBe(422); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('type_not_accepted'); }); it('Quote-Payload ohne text ist 422', async () => { const { app } = buildApp(); const env = envelope({ payload: { source: 'X' } }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify(env), }); expect(res.status).toBe(422); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('invalid_payload'); }); it('Wrapped Body { envelope, delivery_token } akzeptiert', async () => { const { app } = buildApp(); const env = envelope({ to: { app: 'memoro', user_id: userA } }); const res = await app.request('/api/v1/share/receive', { method: 'POST', headers: { 'X-User-Id': userA, 'Content-Type': 'application/json' }, body: JSON.stringify({ envelope: env, delivery_token: 'tok_xyz' }), }); // Wrong-Recipient nicht 422 → Wrapper wurde korrekt unwrapped (sonst wäre es envelope_invalid) expect(res.status).toBe(422); const body = (await res.json()) as { reason: string }; expect(body.reason).toBe('wrong_recipient'); }); });