Der Eingang des Spiegels ist die einzige Tuer, die ohne Anmeldung aufgeht. Vier Faelle: ohne Signatur, falsch signiert, zu alt, und der gute Fall — bei dem eine nicht mehr geschickte Belegung auch hier verschwinden muss. Der Testserver bekommt dafuer dasselbe Geheimnis wie der Test. Eine Pruefung, die immer uebersprungen wird, prueft nichts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
139 lines
4.5 KiB
TypeScript
139 lines
4.5 KiB
TypeScript
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, unknown> = {}): 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)
|
|
})
|
|
})
|