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 { 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() }) })