Ausschliesslich Zeilenenden. Nachgewiesen mit "git diff --cached --ignore-cr-at-eol": Der Diff ist leer, es hat sich kein Zeichen Inhalt geaendert. Deshalb ein eigener Commit: 7448 geaenderte Zeilen in einem Commit mit echten Aenderungen zu verstecken, macht die Historie an dieser Stelle unlesbar. So laesst er sich beim Durchsehen ueberspringen. Refs #26
666 lines
23 KiB
TypeScript
666 lines
23 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
||
import path from 'node:path'
|
||
import { describe, expect, it } from 'vitest'
|
||
|
||
import {
|
||
ERGAENZEN,
|
||
GLIEDERUNG,
|
||
aufzaehlung,
|
||
dauerAusgeschrieben,
|
||
entwurfAlsText,
|
||
jahresberichtEntwurfBauen,
|
||
jahresberichtEntwurfText,
|
||
malAngabe,
|
||
offeneErgaenzungen,
|
||
textZuAbschnitten,
|
||
type JahresberichtQuellen,
|
||
} from '@/lib/berichte/jahresbericht.entwurf'
|
||
|
||
import {
|
||
berichtDateiname,
|
||
exportDokumentPfad,
|
||
pfadIstSicher,
|
||
sichereEndung,
|
||
sichererDateiname,
|
||
belegmappeSchema,
|
||
unterlagenpaketSchema,
|
||
jahresberichtErzeugenSchema,
|
||
} from '@/lib/validierung/berichte'
|
||
|
||
import {
|
||
HinweisdateiSchema,
|
||
ausloeserPasst,
|
||
hinweiseAusDaten,
|
||
hinweiseAusText,
|
||
hinweiseSuchen,
|
||
passendeHinweise,
|
||
type Hinweiskarte,
|
||
} from '@/lib/hinweise/laden'
|
||
|
||
import {
|
||
beschreibbareModule,
|
||
freigabeAenderung,
|
||
freigabenNormalisieren,
|
||
letzterAdminGeschuetzt,
|
||
sichtbareModule,
|
||
} from '@/lib/validierung/freigaben'
|
||
|
||
import { darf } from '@/lib/auth/rollen'
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Beispieldaten - ein Jahr im Leben einer betreuten Person
|
||
// -----------------------------------------------------------------------------
|
||
|
||
const tag = (iso: string) => new Date(`${iso}T00:00:00.000Z`)
|
||
|
||
function beispiel(aenderungen: Partial<JahresberichtQuellen> = {}): JahresberichtQuellen {
|
||
const quellen: JahresberichtQuellen = {
|
||
betreute: {
|
||
anrede: 'Frau',
|
||
vorname: 'Maria',
|
||
nachname: 'Musterfrau',
|
||
geburtsdatum: tag('1943-04-17'),
|
||
heimName: 'Seniorenzentrum St. Anna',
|
||
heimStrasse: 'Klinikweg 4',
|
||
heimPlz: '95444',
|
||
heimOrt: 'Bayreuth',
|
||
heimZimmer: '212',
|
||
heimEinzugAm: tag('2025-11-03'),
|
||
fruehereStrasse: 'Bergstraße 12',
|
||
frueherePlz: '95463',
|
||
fruehererOrt: 'Bindlach',
|
||
pflegegrad: 4,
|
||
pflegegradSeit: tag('2025-12-01'),
|
||
schwerbehinderungGdB: 80,
|
||
schwerbehinderungMerkzeichen: 'G, B',
|
||
diagnosen: 'COPD GOLD III mit Sauerstoffpflicht, Herzinsuffizienz',
|
||
ermittelterWille: 'Sie möchte nicht wiederbelebt werden und keine künstliche Ernährung.',
|
||
patientenverfuegungVorhanden: true,
|
||
vorsorgevollmachtVorhanden: false,
|
||
aufgabenkreise: ['VERMOEGENSSORGE', 'GESUNDHEITSSORGE'],
|
||
aufgabenkreiseFreitext: null,
|
||
betreuungsgericht: 'Amtsgericht Bayreuth',
|
||
aktenzeichenGericht: 'XVII 123/25',
|
||
bestellungAm: tag('2025-10-01'),
|
||
bestellungBis: null,
|
||
},
|
||
zeitraumVon: tag('2025-10-01'),
|
||
zeitraumBis: tag('2026-09-30'),
|
||
kontakte: [
|
||
{
|
||
datum: tag('2025-11-10'),
|
||
art: 'BESUCH',
|
||
mitBetreuter: true,
|
||
dauerMinuten: 90,
|
||
gespraechspartner: null,
|
||
wuensche: 'Sie möchte ihre Nähmaschine im Zimmer haben.',
|
||
},
|
||
{
|
||
datum: tag('2026-02-14'),
|
||
art: 'BESUCH',
|
||
mitBetreuter: true,
|
||
dauerMinuten: 60,
|
||
gespraechspartner: null,
|
||
wuensche: null,
|
||
},
|
||
{
|
||
datum: tag('2026-06-20'),
|
||
art: 'TELEFONAT',
|
||
mitBetreuter: true,
|
||
dauerMinuten: 15,
|
||
gespraechspartner: null,
|
||
wuensche: 'Das Haus in Bindlach soll nicht verkauft werden.',
|
||
},
|
||
{
|
||
datum: tag('2026-03-02'),
|
||
art: 'TELEFONAT',
|
||
mitBetreuter: false,
|
||
dauerMinuten: 20,
|
||
gespraechspartner: 'Bezirk Oberfranken',
|
||
wuensche: null,
|
||
},
|
||
],
|
||
gesundheit: [
|
||
{
|
||
datum: tag('2026-01-08'),
|
||
bisDatum: tag('2026-01-19'),
|
||
art: 'KRANKENHAUS',
|
||
titel: 'Exazerbation der COPD',
|
||
einrichtung: 'Klinikum Bayreuth',
|
||
abwesenheitstage: 11,
|
||
},
|
||
{
|
||
datum: tag('2026-04-03'),
|
||
bisDatum: null,
|
||
art: 'ARZTBESUCH',
|
||
titel: 'Kontrolle Lungenfacharzt',
|
||
einrichtung: null,
|
||
abwesenheitstage: null,
|
||
},
|
||
],
|
||
vorgaenge: [
|
||
{
|
||
titel: 'Antrag auf Hilfe zur Pflege',
|
||
kategorie: 'SOZIALHILFE',
|
||
status: 'WARTET_AUF_DRITTE',
|
||
naechsterSchritt: 'Sachstandsanfrage beim Bezirk',
|
||
gegenstelle: 'Bezirk Oberfranken',
|
||
genehmigungspflichtig: false,
|
||
erledigtAm: null,
|
||
},
|
||
{
|
||
titel: 'Wohnung Bindlach auflösen',
|
||
kategorie: 'IMMOBILIE',
|
||
status: 'ERLEDIGT',
|
||
naechsterSchritt: null,
|
||
gegenstelle: null,
|
||
genehmigungspflichtig: true,
|
||
erledigtAm: tag('2026-05-12'),
|
||
},
|
||
],
|
||
}
|
||
return { ...quellen, ...aenderungen }
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Jahresbericht
|
||
// -----------------------------------------------------------------------------
|
||
|
||
describe('Jahresbericht nach § 1863 BGB', () => {
|
||
it('führt alle fünf Punkte auf, die das Gericht sehen will', () => {
|
||
const abschnitte = jahresberichtEntwurfBauen(beispiel())
|
||
|
||
expect(abschnitte).toHaveLength(5)
|
||
expect(abschnitte.map((a) => a.nummer)).toEqual([1, 2, 3, 4, 5])
|
||
expect(abschnitte.map((a) => a.titel)).toEqual(GLIEDERUNG.map((g) => g.titel))
|
||
for (const abschnitt of abschnitte) {
|
||
expect(abschnitt.absaetze.length).toBeGreaterThan(0)
|
||
expect(abschnitt.rechtsgrundlage).toMatch(/§/)
|
||
}
|
||
})
|
||
|
||
it('beschreibt den Aufenthalt, den Pflegegrad und den Krankenhausaufenthalt', () => {
|
||
const [persoenlich] = jahresberichtEntwurfBauen(beispiel())
|
||
const text = persoenlich!.absaetze.join('\n')
|
||
|
||
expect(text).toContain('Seniorenzentrum St. Anna')
|
||
expect(text).toContain('Zimmer 212')
|
||
expect(text).toContain('Pflegegrad 4')
|
||
expect(text).toContain('COPD GOLD III')
|
||
expect(text).toContain('Klinikum Bayreuth')
|
||
expect(text).toContain('11 Abwesenheitstage')
|
||
expect(text).toContain('Patientenverfügung liegt vor')
|
||
})
|
||
|
||
it('zählt die persönlichen Kontakte und schreibt sie in ganzen Sätzen aus', () => {
|
||
const [, kontakt] = jahresberichtEntwurfBauen(beispiel())
|
||
const text = kontakt!.absaetze.join('\n')
|
||
|
||
// Drei Kontakte mit der betreuten Person, davon zwei Besuche.
|
||
expect(text).toContain('dreimal persönlichen Kontakt')
|
||
expect(text).toContain('2 Besuche')
|
||
expect(text).toContain('ein Telefonat')
|
||
// Der Kontakt mit dem Bezirk zählt nicht als persönlicher Kontakt.
|
||
expect(text).toContain('1 Kontakte mit Dritten')
|
||
expect(text).toContain('Bezirk Oberfranken')
|
||
expect(text).toContain('2 Stunden und 45 Minuten')
|
||
})
|
||
|
||
it('warnt ausdrücklich, wenn kein persönlicher Kontakt protokolliert ist', () => {
|
||
const ohne = beispiel({ kontakte: [] })
|
||
const [, kontakt] = jahresberichtEntwurfBauen(ohne)
|
||
const text = kontakt!.absaetze.join('\n')
|
||
|
||
expect(text).toContain('kein persönlicher Kontakt')
|
||
expect(text).toContain('§ 1863 Abs. 2 BGB')
|
||
})
|
||
|
||
it('übernimmt die Wünsche aus den Protokollen und nennt § 1821 BGB', () => {
|
||
const [, , wuensche] = jahresberichtEntwurfBauen(beispiel())
|
||
const text = wuensche!.absaetze.join('\n')
|
||
|
||
expect(text).toContain('§ 1821 BGB')
|
||
expect(text).toContain('Nähmaschine')
|
||
expect(text).toContain('Das Haus in Bindlach soll nicht verkauft werden.')
|
||
expect(text).toContain('nicht wiederbelebt werden')
|
||
// Der Betreuer muss selbst ergänzen, wie er den Wünschen entsprochen hat.
|
||
expect(text).toContain(ERGAENZEN)
|
||
})
|
||
|
||
it('trennt erledigte und laufende Angelegenheiten nach Kategorien', () => {
|
||
const [, , , angelegenheiten] = jahresberichtEntwurfBauen(beispiel())
|
||
const text = angelegenheiten!.absaetze.join('\n')
|
||
|
||
expect(text).toContain('Sozialhilfe und Hilfe zur Pflege')
|
||
expect(text).toContain('Immobilie und Wohnraum')
|
||
expect(text).toContain('Abgeschlossen wurde Wohnung Bindlach auflösen')
|
||
expect(text).toContain('Noch offen ist Antrag auf Hilfe zur Pflege')
|
||
expect(text).toContain('Sachstandsanfrage beim Bezirk')
|
||
expect(text).toContain('Genehmigungspflichtig waren dabei')
|
||
})
|
||
|
||
it('macht zur Fortführung einen Vorschlag und verlangt eine eigene Entscheidung', () => {
|
||
const [, , , , einschaetzung] = jahresberichtEntwurfBauen(beispiel())
|
||
const text = einschaetzung!.absaetze.join('\n')
|
||
|
||
expect(text).toContain('Vermögenssorge')
|
||
expect(text).toContain('Gesundheitssorge')
|
||
expect(text).toContain('fortzuführen')
|
||
expect(text).toContain('zu erweitern')
|
||
expect(text).toContain('einzuschränken')
|
||
expect(text).toContain(ERGAENZEN)
|
||
})
|
||
|
||
it('bleibt ein Entwurf: offene Stellen sind gezählt und wiederauffindbar', () => {
|
||
const text = jahresberichtEntwurfText(beispiel())
|
||
expect(offeneErgaenzungen(text)).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('lässt sich als Text ablegen und wieder in Abschnitte zerlegen', () => {
|
||
const abschnitte = jahresberichtEntwurfBauen(beispiel())
|
||
const text = entwurfAlsText(abschnitte)
|
||
|
||
expect(text.startsWith('1. Persönliche Verhältnisse')).toBe(true)
|
||
|
||
const zurueck = textZuAbschnitten(text)
|
||
expect(zurueck).toHaveLength(5)
|
||
expect(zurueck[1]!.titel).toBe('2. Persönlicher Kontakt zur betreuten Person')
|
||
expect(zurueck[4]!.absaetze.length).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('verkraftet einen leeren Bestand, ohne den Bericht zu verstümmeln', () => {
|
||
const leer = beispiel({ kontakte: [], gesundheit: [], vorgaenge: [] })
|
||
const abschnitte = jahresberichtEntwurfBauen(leer)
|
||
|
||
expect(abschnitte).toHaveLength(5)
|
||
for (const abschnitt of abschnitte) {
|
||
expect(abschnitt.absaetze.join('').trim()).not.toBe('')
|
||
}
|
||
})
|
||
|
||
it('formuliert Zahl- und Zeitangaben so, wie man sie schreiben würde', () => {
|
||
expect(malAngabe(0)).toBe('keinmal')
|
||
expect(malAngabe(1)).toBe('einmal')
|
||
expect(malAngabe(3)).toBe('dreimal')
|
||
expect(malAngabe(17)).toBe('17-mal')
|
||
expect(dauerAusgeschrieben(95)).toBe('1 Stunde und 35 Minuten')
|
||
expect(dauerAusgeschrieben(45)).toBe('45 Minuten')
|
||
expect(aufzaehlung(['a', 'b', 'c'])).toBe('a, b und c')
|
||
expect(aufzaehlung(['a'])).toBe('a')
|
||
expect(aufzaehlung([])).toBe('')
|
||
})
|
||
})
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Dateinamen im Export
|
||
// -----------------------------------------------------------------------------
|
||
|
||
describe('Dateinamen im Vollexport', () => {
|
||
it('schreibt Umlaute um und lässt nur harmlose Zeichen stehen', () => {
|
||
expect(sichererDateiname('Bescheid Hilfe zur Pflege')).toBe('Bescheid-Hilfe-zur-Pflege')
|
||
expect(sichererDateiname('Vergütung & Rückkauf')).toBe('Verguetung-Rueckkauf')
|
||
expect(sichererDateiname('Maßnahme')).toBe('Massnahme')
|
||
expect(sichererDateiname(' ')).toBe('ohne-titel')
|
||
expect(sichererDateiname('...')).toBe('ohne-titel')
|
||
})
|
||
|
||
it('lässt keinen Pfadausbruch zu', () => {
|
||
for (const boesartig of [
|
||
'../../etc/passwd',
|
||
'..\\..\\windows\\system32',
|
||
'C:\\Users\\Public\\x',
|
||
'/etc/shadow',
|
||
'beleg\u0000.pdf',
|
||
'auszug:stream',
|
||
]) {
|
||
const sauber = sichererDateiname(boesartig)
|
||
expect(sauber).not.toContain('..')
|
||
expect(sauber).not.toContain('/')
|
||
expect(sauber).not.toContain('\\')
|
||
expect(sauber).not.toContain(':')
|
||
expect(sauber).not.toContain('\u0000')
|
||
}
|
||
})
|
||
|
||
it('umgeht Namen, die Windows nie akzeptiert', () => {
|
||
expect(sichererDateiname('CON')).toBe('_CON')
|
||
expect(sichererDateiname('lpt1')).toBe('_lpt1')
|
||
})
|
||
|
||
it('baut den Dokumentpfad nach dem verabredeten Muster', () => {
|
||
const pfad = exportDokumentPfad({
|
||
datum: tag('2026-03-14'),
|
||
absender: 'Bezirk Oberfranken',
|
||
titel: 'Bescheid über Hilfe zur Pflege',
|
||
dateiname: 'scan.PDF',
|
||
mimeTyp: 'application/pdf',
|
||
})
|
||
|
||
expect(pfad).toBe(
|
||
'dokumente/2026/2026-03-14_Bezirk-Oberfranken_Bescheid-ueber-Hilfe-zur-Pflege.pdf',
|
||
)
|
||
expect(pfadIstSicher(pfad)).toBe(true)
|
||
})
|
||
|
||
it('legt Dokumente ohne Datum in einen eigenen Ordner statt in ein erfundenes Jahr', () => {
|
||
const pfad = exportDokumentPfad({
|
||
datum: null,
|
||
absender: null,
|
||
titel: '../../geheim',
|
||
dateiname: null,
|
||
mimeTyp: 'image/jpeg',
|
||
})
|
||
|
||
expect(pfad.startsWith('dokumente/ohne-jahr/')).toBe(true)
|
||
expect(pfad.endsWith('.jpg')).toBe(true)
|
||
expect(pfad.split('/')).toHaveLength(3)
|
||
expect(pfadIstSicher(pfad)).toBe(true)
|
||
})
|
||
|
||
it('erkennt unzulässige Archivpfade', () => {
|
||
expect(pfadIstSicher('daten/frist.json')).toBe(true)
|
||
expect(pfadIstSicher('dokumente/../../x')).toBe(false)
|
||
expect(pfadIstSicher('/absolut')).toBe(false)
|
||
expect(pfadIstSicher('C:/windows')).toBe(false)
|
||
expect(pfadIstSicher('a\\b')).toBe(false)
|
||
expect(pfadIstSicher('')).toBe(false)
|
||
expect(pfadIstSicher('a//b')).toBe(false)
|
||
})
|
||
|
||
it('normalisiert Dateiendungen', () => {
|
||
expect(sichereEndung('scan.PDF')).toBe('pdf')
|
||
expect(sichereEndung('pdf')).toBe('pdf')
|
||
expect(sichereEndung('ohnepunkt')).toBe('bin')
|
||
expect(sichereEndung('bild.jpeg')).toBe('jpeg')
|
||
expect(sichereEndung('x.verylongextension')).toBe('bin')
|
||
expect(sichereEndung(null)).toBe('bin')
|
||
})
|
||
|
||
it('benennt erzeugte Berichte sortierbar', () => {
|
||
expect(berichtDateiname('jahresbericht', 'Musterfrau', tag('2026-09-30'), 'pdf')).toBe(
|
||
'2026-09-30_jahresbericht_Musterfrau.pdf',
|
||
)
|
||
})
|
||
})
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Eingabepruefung
|
||
// -----------------------------------------------------------------------------
|
||
|
||
describe('Eingabeprüfung der Berichtsformulare', () => {
|
||
it('weist einen Berichtszeitraum zurück, der rückwärts läuft', () => {
|
||
const ergebnis = jahresberichtErzeugenSchema.safeParse({
|
||
betreuteId: 'abc123',
|
||
zeitraumVon: '2026-09-30',
|
||
zeitraumBis: '2025-10-01',
|
||
})
|
||
expect(ergebnis.success).toBe(false)
|
||
})
|
||
|
||
it('verlangt für die Belegmappe entweder einen Zeitraum oder einen Vorgang', () => {
|
||
expect(
|
||
belegmappeSchema.safeParse({ betreuteId: 'abc', quelle: 'zeitraum', zeitraumVon: '', zeitraumBis: '' })
|
||
.success,
|
||
).toBe(false)
|
||
expect(
|
||
belegmappeSchema.safeParse({
|
||
betreuteId: 'abc',
|
||
quelle: 'vorgang',
|
||
zeitraumVon: '',
|
||
zeitraumBis: '',
|
||
vorgangId: 'vg1',
|
||
}).success,
|
||
).toBe(true)
|
||
})
|
||
|
||
it('lässt ein Unterlagenpaket ohne ausgewählte Unterlage nicht zu', () => {
|
||
expect(
|
||
unterlagenpaketSchema.safeParse({ betreuteId: 'a', antragId: 'b', nachforderungIds: [] })
|
||
.success,
|
||
).toBe(false)
|
||
})
|
||
})
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Hinweis-Wissensbasis
|
||
// -----------------------------------------------------------------------------
|
||
|
||
const WISSENSBASIS = path.join(process.cwd(), 'daten', 'hinweise.json')
|
||
|
||
describe('Hinweis-Wissensbasis', () => {
|
||
const roh = readFileSync(WISSENSBASIS, 'utf8')
|
||
const geladen = hinweiseAusText(roh)
|
||
|
||
it('ist gültig gegen das Schema', () => {
|
||
expect(geladen.fehler).toEqual([])
|
||
expect(HinweisdateiSchema.safeParse(JSON.parse(roh)).success).toBe(true)
|
||
})
|
||
|
||
it('enthält mindestens 25 Karten mit eindeutigen Schlüsseln', () => {
|
||
expect(geladen.karten.length).toBeGreaterThanOrEqual(25)
|
||
const schluessel = geladen.karten.map((k) => k.schluessel)
|
||
expect(new Set(schluessel).size).toBe(schluessel.length)
|
||
})
|
||
|
||
it('nennt zu jeder Karte eine Rechtsgrundlage und einen sicheren Link', () => {
|
||
for (const karte of geladen.karten) {
|
||
expect(karte.rechtsgrundlage.length).toBeGreaterThan(1)
|
||
expect(karte.link.startsWith('https://')).toBe(true)
|
||
expect(karte.kontext.length).toBeGreaterThan(0)
|
||
}
|
||
})
|
||
|
||
it('deckt die fachlich unverzichtbaren Themen ab', () => {
|
||
const schluessel = new Set(geladen.karten.map((k) => k.schluessel))
|
||
for (const pflicht of [
|
||
'wohnraum-aufgeben',
|
||
'grundstueck-genehmigung',
|
||
'genehmigung-vor-geschaeft',
|
||
'aufwandspauschale',
|
||
'vermoegensverzeichnis-frist',
|
||
'jahresbericht',
|
||
'getrennte-konten',
|
||
'schonvermoegen',
|
||
'hausgrundstueck-geschuetzt',
|
||
'bestattungsvorsorge',
|
||
'darlehen-statt-verwertung',
|
||
'widerspruchsfrist',
|
||
'fehlende-rechtsbehelfsbelehrung',
|
||
'wille-leitschnur',
|
||
'zwangsmassnahme',
|
||
'unterbringung',
|
||
'leistungszuschlag',
|
||
'zuzahlungsbefreiung',
|
||
'landespflegegeld',
|
||
'rundfunkbeitragsbefreiung',
|
||
'pflegegrad-hoeherstufung',
|
||
'leerstand-versicherung',
|
||
'verkehrssicherungspflicht',
|
||
'schenkungsrueckforderung',
|
||
'betreuerausweis',
|
||
'kontoauszuege-aufbewahren',
|
||
'rechtspfleger-fragen',
|
||
]) {
|
||
expect(schluessel.has(pflicht), `Karte "${pflicht}" fehlt`).toBe(true)
|
||
}
|
||
})
|
||
|
||
it('markiert die Ausschlussfrist der Aufwandspauschale als solche', () => {
|
||
const karte = geladen.karten.find((k) => k.schluessel === 'aufwandspauschale')!
|
||
expect(karte.text).toContain('Ausschlussfrist')
|
||
expect(karte.rechtsgrundlage).toContain('1878')
|
||
})
|
||
|
||
it('meldet kaputtes JSON, statt abzustürzen', () => {
|
||
const ergebnis = hinweiseAusText('{ das ist kein JSON')
|
||
expect(ergebnis.karten).toEqual([])
|
||
expect(ergebnis.fehler.length).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('lehnt Karten ohne Pflichtangaben ab', () => {
|
||
const ergebnis = hinweiseAusDaten({
|
||
version: 1,
|
||
karten: [{ schluessel: 'x', titel: 'zu kurz' }],
|
||
})
|
||
expect(ergebnis.karten).toEqual([])
|
||
expect(ergebnis.fehler.length).toBeGreaterThan(0)
|
||
})
|
||
|
||
it('erkennt doppelte Schlüssel', () => {
|
||
const eine = geladen.karten[0]!
|
||
const ergebnis = hinweiseAusDaten({ version: 1, karten: [eine, { ...eine }] })
|
||
expect(ergebnis.karten).toEqual([])
|
||
expect(ergebnis.fehler[0]).toContain('Doppelte Schlüssel')
|
||
})
|
||
})
|
||
|
||
describe('Zuordnung der Hinweiskarten zum Kontext', () => {
|
||
const karten: Hinweiskarte[] = [
|
||
{
|
||
schluessel: 'a',
|
||
titel: 'Karte A für die Immobilie',
|
||
text: 'x'.repeat(60),
|
||
rechtsgrundlage: '§ 1833 BGB',
|
||
link: 'https://www.gesetze-im-internet.de/bgb/__1833.html',
|
||
kontext: ['IMMOBILIE', 'VORLAGE:wohnungsaufloesung'],
|
||
gewicht: 5,
|
||
},
|
||
{
|
||
schluessel: 'b',
|
||
titel: 'Karte B zur Jahresberichtsfrist',
|
||
text: 'y'.repeat(60),
|
||
rechtsgrundlage: '§ 1863 BGB',
|
||
link: 'https://www.gesetze-im-internet.de/bgb/__1863.html',
|
||
kontext: ['FRIST:jahresbericht'],
|
||
gewicht: 9,
|
||
},
|
||
{
|
||
schluessel: 'c',
|
||
titel: 'Karte C zur Sozialhilfe',
|
||
text: 'z'.repeat(60),
|
||
rechtsgrundlage: '§ 90 SGB XII',
|
||
link: 'https://www.gesetze-im-internet.de/sgb_12/__90.html',
|
||
kontext: ['SOZIALHILFE', 'IMMOBILIE'],
|
||
gewicht: 9,
|
||
},
|
||
]
|
||
|
||
it('findet Karten über Modul, Vorlage und Frist', () => {
|
||
expect(passendeHinweise(karten, ['IMMOBILIE']).map((k) => k.schluessel)).toEqual(['c', 'a'])
|
||
expect(passendeHinweise(karten, ['VORLAGE:wohnungsaufloesung']).map((k) => k.schluessel)).toEqual([
|
||
'a',
|
||
])
|
||
expect(passendeHinweise(karten, ['FRISTEN'])).toEqual([])
|
||
})
|
||
|
||
it('lässt einen Auslöser auch auf die Frist mit Jahreszahl passen', () => {
|
||
expect(ausloeserPasst('FRIST:jahresbericht', 'FRIST:jahresbericht-2027')).toBe(true)
|
||
expect(ausloeserPasst('FRIST:jahresbericht', 'FRIST:jahresberichtXYZ')).toBe(false)
|
||
expect(ausloeserPasst('IMMOBILIE', 'immobilie')).toBe(true)
|
||
expect(passendeHinweise(karten, ['FRIST:jahresbericht-2027']).map((k) => k.schluessel)).toEqual([
|
||
'b',
|
||
])
|
||
})
|
||
|
||
it('sortiert nach Gewicht und bleibt bei Gleichstand stabil', () => {
|
||
const treffer = passendeHinweise(karten, ['IMMOBILIE', 'FRIST:jahresbericht'])
|
||
expect(treffer.map((k) => k.schluessel)).toEqual(['b', 'c', 'a'])
|
||
expect(passendeHinweise(karten, ['IMMOBILIE'], 1).map((k) => k.schluessel)).toEqual(['c'])
|
||
})
|
||
|
||
it('sucht über Titel, Text und Rechtsgrundlage', () => {
|
||
expect(hinweiseSuchen(karten, '1833').map((k) => k.schluessel)).toEqual(['a'])
|
||
expect(hinweiseSuchen(karten, 'sozialhilfe').map((k) => k.schluessel)).toEqual(['c'])
|
||
expect(hinweiseSuchen(karten, '').map((k) => k.schluessel)).toEqual(['b', 'c', 'a'])
|
||
})
|
||
})
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Rollen und Freigaben
|
||
// -----------------------------------------------------------------------------
|
||
|
||
describe('Mitleser-Rolle und Modulfreigaben', () => {
|
||
const mitleser = {
|
||
rolle: 'MITLESER' as const,
|
||
freigegebeneModule: ['STAMMDATEN', 'FRISTEN'] as const,
|
||
}
|
||
|
||
it('lässt einen Mitleser niemals schreiben – auch nicht im freigegebenen Modul', () => {
|
||
expect(darf({ ...mitleser, freigegebeneModule: [...mitleser.freigegebeneModule] }, 'FRISTEN', 'lesen')).toBe(true)
|
||
expect(darf({ ...mitleser, freigegebeneModule: [...mitleser.freigegebeneModule] }, 'FRISTEN', 'schreiben')).toBe(false)
|
||
expect(
|
||
beschreibbareModule({ rolle: 'MITLESER', freigegebeneModule: ['STAMMDATEN', 'FRISTEN'] }),
|
||
).toEqual([])
|
||
})
|
||
|
||
it('zeigt einem Mitleser nur die freigegebenen Bereiche', () => {
|
||
expect(sichtbareModule({ rolle: 'MITLESER', freigegebeneModule: ['STAMMDATEN', 'FRISTEN'] })).toEqual([
|
||
'STAMMDATEN',
|
||
'FRISTEN',
|
||
])
|
||
expect(darf({ rolle: 'MITLESER', freigegebeneModule: ['STAMMDATEN'] }, 'GESUNDHEIT', 'lesen')).toBe(
|
||
false,
|
||
)
|
||
})
|
||
|
||
it('gibt dem Betreuer und dem Administrator alles', () => {
|
||
expect(sichtbareModule({ rolle: 'BETREUER', freigegebeneModule: [] }).length).toBeGreaterThan(10)
|
||
expect(beschreibbareModule({ rolle: 'ADMIN', freigegebeneModule: [] }).length).toBeGreaterThan(10)
|
||
})
|
||
|
||
it('sperrt jeden Zugriff ohne Zugang', () => {
|
||
expect(darf(null, 'STAMMDATEN', 'lesen')).toBe(false)
|
||
expect(sichtbareModule(null)).toEqual([])
|
||
})
|
||
|
||
it('räumt die Modulliste auf, sobald jemand kein Mitleser mehr ist', () => {
|
||
expect(freigabenNormalisieren('BETREUER', ['GESUNDHEIT'])).toEqual([])
|
||
expect(freigabenNormalisieren('ADMIN', ['GESUNDHEIT'])).toEqual([])
|
||
expect(freigabenNormalisieren('MITLESER', ['FRISTEN', 'STAMMDATEN'])).toEqual([
|
||
'STAMMDATEN',
|
||
'FRISTEN',
|
||
])
|
||
})
|
||
|
||
it('benennt, was sich an einer Freigabe ändert – und was davon heikel ist', () => {
|
||
const aenderung = freigabeAenderung(
|
||
{ rolle: 'MITLESER', freigegebeneModule: ['STAMMDATEN', 'FRISTEN'] },
|
||
{ rolle: 'MITLESER', freigegebeneModule: ['STAMMDATEN', 'GESUNDHEIT', 'FINANZEN'] },
|
||
)
|
||
|
||
expect(aenderung.hinzugefuegt).toEqual(['FINANZEN', 'GESUNDHEIT'])
|
||
expect(aenderung.entfernt).toEqual(['FRISTEN'])
|
||
expect(aenderung.sensibelHinzugefuegt).toEqual(['FINANZEN', 'GESUNDHEIT'])
|
||
expect(aenderung.rolleGeaendert).toBe(false)
|
||
})
|
||
|
||
it('schützt den letzten Administrator vor sich selbst', () => {
|
||
expect(
|
||
letzterAdminGeschuetzt({
|
||
zielRolleVorher: 'ADMIN',
|
||
zielRolleNachher: 'BETREUER',
|
||
zielBleibtAktiv: true,
|
||
anzahlAktiverAdmins: 1,
|
||
}),
|
||
).toMatch(/letzte aktive Administrator/)
|
||
|
||
expect(
|
||
letzterAdminGeschuetzt({
|
||
zielRolleVorher: 'ADMIN',
|
||
zielRolleNachher: 'BETREUER',
|
||
zielBleibtAktiv: true,
|
||
anzahlAktiverAdmins: 2,
|
||
}),
|
||
).toBeNull()
|
||
|
||
expect(
|
||
letzterAdminGeschuetzt({
|
||
zielRolleVorher: 'BETREUER',
|
||
zielRolleNachher: 'MITLESER',
|
||
zielBleibtAktiv: true,
|
||
anzahlAktiverAdmins: 1,
|
||
}),
|
||
).toBeNull()
|
||
})
|
||
})
|