import { createHmac } from 'node:crypto' import { PrismaPg } from '@prisma/adapter-pg' import { expect, test } from '@playwright/test' import { PrismaClient } from '../src/generated/prisma/client.js' import { SPIEGEL_GEHEIMNIS, VERBINDUNG } from './hilfe' /** * Der Eingang des Spiegels. * * Hier meldet sich kein Mensch an — die Gegenstelle weist sich allein über * eine Signatur aus. Deshalb wird hier nicht die Oberfläche geprüft, sondern * genau diese Tür: Sie muss zu sein, wenn irgendetwas nicht stimmt. */ const GEHEIMNIS = SPIEGEL_GEHEIMNIS const db = new PrismaClient({ adapter: new PrismaPg({ connectionString: VERBINDUNG }) }) const PRAEFIX = `E2E-S-${Date.now()}` function signieren(koerper: string, geheimnis = GEHEIMNIS): string { return createHmac('sha256', geheimnis).update(koerper).digest('hex') } function sendung(teile: Record = {}): string { return JSON.stringify({ gesendetAm: new Date().toISOString(), belegungen: [], aufgaben: [], ...teile, }) } test.describe('Spiegel', () => { test.afterAll(async () => { await db.spiegelbelegung.deleteMany({ where: { quelleRef: { startsWith: PRAEFIX } } }) await db.aufgabe.deleteMany({ where: { quelleRef: { startsWith: PRAEFIX } } }) await db.auditEintrag.deleteMany({ where: { aktion: 'spiegel.empfangen' } }) await db.$disconnect() }) test('weist eine Sendung ohne Signatur ab', async ({ request }) => { const antwort = await request.post('/api/spiegel', { headers: { 'Content-Type': 'application/json' }, data: sendung(), }) expect(antwort.status()).toBe(401) }) test('weist eine falsch signierte Sendung ab', async ({ request }) => { const koerper = sendung() const antwort = await request.post('/api/spiegel', { headers: { 'Content-Type': 'application/json', 'X-Planer-Signatur': signieren(koerper, 'das-ist-nicht-das-geheimnis'), }, data: koerper, }) expect(antwort.status()).toBe(401) }) test('weist eine Sendung ab, die zu lange unterwegs war', async ({ request }) => { test.skip(!GEHEIMNIS, 'Ohne SPIEGEL_GEHEIMNIS ist der Eingang ohnehin zu.') // Eine Stunde alt: richtig signiert, trotzdem wertlos. Sonst ließe sich // ein abgefangenes Paket beliebig oft erneut einspielen. const koerper = sendung({ gesendetAm: new Date(Date.now() - 3_600_000).toISOString(), }) const antwort = await request.post('/api/spiegel', { headers: { 'Content-Type': 'application/json', 'X-Planer-Signatur': signieren(koerper), }, data: koerper, }) expect(antwort.status()).toBe(401) }) test('nimmt eine gültige Sendung an und räumt Veraltetes weg', async ({ request }) => { test.skip( !GEHEIMNIS, 'Ohne SPIEGEL_GEHEIMNIS auf dem Testserver lässt sich das nicht prüfen.', ) const beginn = new Date(Date.now() + 86_400_000) const ende = new Date(beginn.getTime() + 3_600_000) const erste = sendung({ belegungen: [ { ref: `${PRAEFIX}-a`, beginn: beginn.toISOString(), ende: ende.toISOString(), }, ], aufgaben: [{ ref: `${PRAEFIX}-1`, titel: `${PRAEFIX} Angebot schreiben` }], }) const antwort = await request.post('/api/spiegel', { headers: { 'Content-Type': 'application/json', 'X-Planer-Signatur': signieren(erste), }, data: erste, }) expect(antwort.ok()).toBe(true) expect(await db.spiegelbelegung.count({ where: { quelleRef: `${PRAEFIX}-a` } })).toBe( 1, ) const aufgabe = await db.aufgabe.findFirst({ where: { quelle: 'SPIEGEL', quelleRef: `${PRAEFIX}-1` }, select: { titel: true }, }) expect(aufgabe?.titel).toBe(`${PRAEFIX} Angebot schreiben`) // Zweite Runde ohne die Belegung: Sie wurde drüben ausgeplant und muss // auch hier verschwinden — sonst stünde für immer eine Sperre, die es // längst nicht mehr gibt. const zweite = sendung({ aufgaben: [] }) const nachher = await request.post('/api/spiegel', { headers: { 'Content-Type': 'application/json', 'X-Planer-Signatur': signieren(zweite), }, data: zweite, }) expect(nachher.ok()).toBe(true) expect(await db.spiegelbelegung.count({ where: { quelleRef: `${PRAEFIX}-a` } })).toBe( 0, ) // Die Aufgabe bleibt: Sie ist Planung, keine Sperre. expect( await db.aufgabe.count({ where: { quelle: 'SPIEGEL', quelleRef: `${PRAEFIX}-1` } }), ).toBe(1) }) })