feat: Datenmodell und Regeln fuer die oeffentliche Schnittstelle
Gespeichert wird nur der Hash eines Tokens. Der Klartext existiert genau einmal — beim Anlegen, auf dem Bildschirm. Ein Token ist ein Passwort; es gibt keinen Grund, es zurueckzulesen, und jeden Grund, es nicht zu koennen. Drei Rechte, und keines schliesst ein anderes ein. Ein Dienst, der nur meldet "hier ist etwas Neues", braucht den Bestand nicht zu sehen — genau das ist der haeufigste Fall und der, bei dem ein mitgelesener Token am wenigsten anrichtet. Der sichtbare Anfang "planer_..." ist Absicht: Wer so einen Wert versehentlich in ein oeffentliches Verzeichnis schiebt, soll ihn beim Durchsehen erkennen. 30 Tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bbac268c59
commit
d5c7974af3
@@ -0,0 +1,177 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import {
|
||||
aenderungEingabe,
|
||||
abfrage,
|
||||
aufgabeEingabe,
|
||||
darf,
|
||||
rechteLesen,
|
||||
tokenAusKopfzeilen,
|
||||
tokenZerlegen,
|
||||
type Recht,
|
||||
} from '@/lib/api'
|
||||
|
||||
describe('darf', () => {
|
||||
test('lässt durch, was ausdrücklich erlaubt ist', () => {
|
||||
expect(darf(['lesen'], 'lesen')).toBe(true)
|
||||
})
|
||||
|
||||
test('Erfassen schließt Lesen nicht ein', () => {
|
||||
// Ein Dienst, der nur meldet „hier ist etwas Neues", braucht den
|
||||
// Bestand nicht zu sehen.
|
||||
expect(darf(['erfassen'], 'lesen')).toBe(false)
|
||||
})
|
||||
|
||||
test('Schreiben schließt Erfassen nicht ein', () => {
|
||||
expect(darf(['schreiben'], 'erfassen')).toBe(false)
|
||||
})
|
||||
|
||||
test('mehrere Rechte gelten nebeneinander', () => {
|
||||
const rechte: Recht[] = ['lesen', 'erfassen']
|
||||
expect(darf(rechte, 'lesen')).toBe(true)
|
||||
expect(darf(rechte, 'erfassen')).toBe(true)
|
||||
expect(darf(rechte, 'schreiben')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rechteLesen', () => {
|
||||
test('liest die gespeicherte Liste', () => {
|
||||
expect(rechteLesen('lesen,erfassen')).toEqual(['lesen', 'erfassen'])
|
||||
})
|
||||
|
||||
test('verträgt Leerzeichen', () => {
|
||||
expect(rechteLesen(' lesen , schreiben ')).toEqual(['lesen', 'schreiben'])
|
||||
})
|
||||
|
||||
test('wirft Unbekanntes weg, statt daran zu scheitern', () => {
|
||||
// Ein Recht, das es nicht mehr gibt, darf keine Anfrage abbrechen –
|
||||
// es soll schlicht nicht gelten.
|
||||
expect(rechteLesen('lesen,zaubern')).toEqual(['lesen'])
|
||||
})
|
||||
|
||||
test('aus leer wird leer', () => {
|
||||
expect(rechteLesen('')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenZerlegen', () => {
|
||||
const gueltig = `planer_ab12cd34_${'x'.repeat(43)}`
|
||||
|
||||
test('zerlegt einen gültigen Token', () => {
|
||||
expect(tokenZerlegen(gueltig)).toEqual({
|
||||
praefix: 'ab12cd34',
|
||||
geheimnis: 'x'.repeat(43),
|
||||
})
|
||||
})
|
||||
|
||||
test('verträgt Leerzeichen drumherum', () => {
|
||||
expect(tokenZerlegen(` ${gueltig} `)?.praefix).toBe('ab12cd34')
|
||||
})
|
||||
|
||||
test('lehnt eine fremde Marke ab', () => {
|
||||
expect(tokenZerlegen(`fremd_ab12cd34_${'x'.repeat(43)}`)).toBeNull()
|
||||
})
|
||||
|
||||
test('lehnt ein zu kurzes Präfix ab', () => {
|
||||
expect(tokenZerlegen(`planer_ab12_${'x'.repeat(43)}`)).toBeNull()
|
||||
})
|
||||
|
||||
test('lehnt ein zu kurzes Geheimnis ab', () => {
|
||||
expect(tokenZerlegen('planer_ab12cd34_kurz')).toBeNull()
|
||||
})
|
||||
|
||||
test('lehnt leeren Text ab', () => {
|
||||
expect(tokenZerlegen('')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('tokenAusKopfzeilen', () => {
|
||||
test('liest Bearer', () => {
|
||||
expect(tokenAusKopfzeilen('Bearer abc', null)).toBe('abc')
|
||||
})
|
||||
|
||||
test('liest Bearer auch klein geschrieben', () => {
|
||||
expect(tokenAusKopfzeilen('bearer abc', null)).toBe('abc')
|
||||
})
|
||||
|
||||
test('liest die eigene Kopfzeile', () => {
|
||||
expect(tokenAusKopfzeilen(null, 'abc')).toBe('abc')
|
||||
})
|
||||
|
||||
test('Bearer geht vor', () => {
|
||||
expect(tokenAusKopfzeilen('Bearer aus-bearer', 'aus-eigener')).toBe('aus-bearer')
|
||||
})
|
||||
|
||||
test('ohne beides null', () => {
|
||||
expect(tokenAusKopfzeilen(null, null)).toBeNull()
|
||||
})
|
||||
|
||||
test('leerer Bearer zählt nicht', () => {
|
||||
expect(tokenAusKopfzeilen('Bearer ', null)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('aufgabeEingabe', () => {
|
||||
test('nimmt das Nötigste an', () => {
|
||||
const gelesen = aufgabeEingabe.safeParse({ titel: 'Angebot schreiben' })
|
||||
expect(gelesen.success).toBe(true)
|
||||
expect(gelesen.success && gelesen.data.ref).toBeNull()
|
||||
})
|
||||
|
||||
test('lehnt einen leeren Titel ab', () => {
|
||||
expect(aufgabeEingabe.safeParse({ titel: ' ' }).success).toBe(false)
|
||||
})
|
||||
|
||||
test('lehnt einen unmöglichen Aufwand ab', () => {
|
||||
// Mehr als ein Tag am Stück ist keine Aufgabe mehr, sondern ein Projekt.
|
||||
expect(aufgabeEingabe.safeParse({ titel: 'x', aufwandMinuten: 2000 }).success).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
test('lehnt eine kaputte Fälligkeit ab', () => {
|
||||
expect(aufgabeEingabe.safeParse({ titel: 'x', faelligAm: 'morgen' }).success).toBe(
|
||||
false,
|
||||
)
|
||||
})
|
||||
|
||||
test('kennt Status, Bereich und Projekt nicht', () => {
|
||||
const gelesen = aufgabeEingabe.safeParse({
|
||||
titel: 'x',
|
||||
status: 'ERLEDIGT',
|
||||
bereichId: 'b1',
|
||||
})
|
||||
expect(gelesen.success).toBe(true)
|
||||
expect(gelesen.success && 'status' in gelesen.data).toBe(false)
|
||||
expect(gelesen.success && 'bereichId' in gelesen.data).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('aenderungEingabe', () => {
|
||||
test('nimmt eine einzelne Änderung an', () => {
|
||||
expect(aenderungEingabe.safeParse({ erledigt: true }).success).toBe(true)
|
||||
})
|
||||
|
||||
test('lehnt eine leere Änderung ab', () => {
|
||||
expect(aenderungEingabe.safeParse({}).success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('abfrage', () => {
|
||||
test('setzt Voreinstellungen', () => {
|
||||
const gelesen = abfrage.safeParse({})
|
||||
expect(gelesen.success && gelesen.data).toEqual({
|
||||
grenze: 25,
|
||||
versatz: 0,
|
||||
offen: null,
|
||||
})
|
||||
})
|
||||
|
||||
test('deckelt die Grenze', () => {
|
||||
expect(abfrage.safeParse({ grenze: '5000' }).success).toBe(false)
|
||||
})
|
||||
|
||||
test('liest offen als Wahrheitswert', () => {
|
||||
const gelesen = abfrage.safeParse({ offen: 'true' })
|
||||
expect(gelesen.success && gelesen.data.offen).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user