import { beforeEach, describe, expect, it } from "vitest"; import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db"; /** * Haelt die Regeln fest, die in der Datenbank selbst stehen (#006). * * Sie stehen dort und nicht nur in der Service-Schicht, damit sie auch dann * greifen, wenn jemand an ihr vorbei schreibt – per Migration, per psql oder aus * einem spaeteren Connector. Die Service-Schicht prueft dieselben Regeln noch * einmal, aber mit verstaendlicheren Meldungen. */ async function kundeAnlegen(kuerzel = "TST", eigene = false) { return testPrisma.kunde.create({ data: { name: `Kunde ${kuerzel}`, kuerzel, istEigeneOrganisation: eigene }, }); } async function benutzerAnlegen(email = "techniker@firma.example") { return testPrisma.benutzer.create({ data: { email, name: "Techniker", rolle: "TECHNIKER" }, }); } /** * Kleinste Vorlage, die ausreicht, um einen Lauf daran zu haengen. * * Erst als Entwurf anlegen, dann veroeffentlichen – an einer veroeffentlichten * Version laesst sich nichts mehr anhaengen, dafuer sorgen Trigger in der * Datenbank. Wer die Fixture umdreht, baut genau den Zustand, den die Anwendung * nie herstellen darf. */ async function veroeffentlichteVersionAnlegen() { const vorlage = await testPrisma.vorlage.create({ data: { name: "Probe" } }); return testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1, status: "ENTWURF" }, }); } /** Schliesst eine Fixture-Version ab, nachdem ihr Aufbau steht. */ async function versionVeroeffentlichen(versionId: string) { return testPrisma.vorlagenVersion.update({ where: { id: versionId }, // veroeffentlichtAm ist Pflicht, sobald der Status VEROEFFENTLICHT ist – // eine Bedingung in der Datenbank haelt das fest. data: { status: "VEROEFFENTLICHT", veroeffentlichtAm: new Date() }, }); } describe.runIf(datenbankVerfuegbar)("Datenmodell: Regeln in der Datenbank", () => { beforeEach(async () => { await datenbankLeeren(); }); describe("Kunde", () => { it("laesst nur eine eigene Organisation zu", async () => { await kundeAnlegen("EIGEN", true); // Gemeint ist der partielle Index, nicht irgendeine Ablehnung. await expect(kundeAnlegen("ZWEIT", true)).rejects.toThrow(/Kunde_eigene_organisation_einmalig/); }); it("laesst beliebig viele gewoehnliche Kunden zu", async () => { await kundeAnlegen("EIGEN", true); await kundeAnlegen("MUS"); await kundeAnlegen("REI"); expect(await testPrisma.kunde.count()).toBe(3); }); it("weist ein Kuerzel ausserhalb von 2 bis 6 Zeichen zurueck", async () => { await expect(kundeAnlegen("X")).rejects.toThrow(/Kunde_kuerzel_form/); await expect(kundeAnlegen("ZULANG7")).rejects.toThrow(/Kunde_kuerzel_form/); }); it("weist ein Kuerzel mit Kleinbuchstaben oder Sonderzeichen zurueck", async () => { await expect(kundeAnlegen("mus")).rejects.toThrow(); await expect(kundeAnlegen("MU-S")).rejects.toThrow(); }); it("haelt das Kuerzel eindeutig", async () => { await kundeAnlegen("MUS"); await expect(kundeAnlegen("MUS")).rejects.toThrow(); }); }); describe("Einstellung", () => { it("laesst nur die eine Zeile mit der Kennung global zu", async () => { await testPrisma.einstellung.create({ data: { id: "global" } }); await expect(testPrisma.einstellung.create({ data: { id: "zweite" } })).rejects.toThrow(); }); }); describe("Geraet", () => { it("erlaubt einen vorgedruckten Tag ohne Hostname und ohne Bezeichnung", async () => { const kunde = await kundeAnlegen(); const tag = await testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-7K3F9Q" }, }); expect(tag.registriertAm).toBeNull(); expect(tag.hostname).toBeNull(); }); it("verlangt beim Registrieren einen Hostname oder eine Bezeichnung", async () => { const kunde = await kundeAnlegen(); await expect( testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-7K3F9Q", registriertAm: new Date() }, }), ).rejects.toThrow(/Geraet_registriert_braucht_namen/); }); it("wertet reine Leerzeichen nicht als Bezeichnung", async () => { const kunde = await kundeAnlegen(); await expect( testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-7K3F9Q", bezeichnung: " ", registriertAm: new Date(), }, }), ).rejects.toThrow(); }); it("nimmt ein registriertes Geraet mit Bezeichnung statt Hostname an", async () => { const kunde = await kundeAnlegen(); const raum = await testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-M2XH4T", bezeichnung: "Serverschrank Keller", typ: "RAUM", registriertAm: new Date(), }, }); expect(raum.bezeichnung).toBe("Serverschrank Keller"); }); it("weist einen tagCode in falscher Form zurueck", async () => { const kunde = await kundeAnlegen(); for (const falsch of ["7K3F9Q", "LZ-7K3F9", "lz-7k3f9q", "LZ-7K3F9O", "LZ-7K3F91"]) { await expect( testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: falsch } }), ).rejects.toThrow(); } }); it("haelt den tagCode ueber alle Kunden hinweg eindeutig", async () => { const einer = await kundeAnlegen("MUS"); const anderer = await kundeAnlegen("REI"); await testPrisma.geraet.create({ data: { kundeId: einer.id, tagCode: "LZ-7K3F9Q" } }); await expect( testPrisma.geraet.create({ data: { kundeId: anderer.id, tagCode: "LZ-7K3F9Q" } }), ).rejects.toThrow(); }); }); describe("Mitarbeiter", () => { it("haelt die externe Kennung je Quelle eindeutig", async () => { const kunde = await kundeAnlegen(); const quelle = await testPrisma.verzeichnisquelle.create({ data: { kundeId: kunde.id, name: "Firmen-AD", url: "ldaps://dc01.firma.local", bindDn: "CN=Dienst,DC=firma,DC=local", bindPasswortVerschluesselt: "v1.probe", basisDn: "DC=firma,DC=local", filter: "(objectClass=user)", attributZuordnung: {}, }, }); const stammdaten = { kundeId: kunde.id, quelleId: quelle.id, externeId: "guid-1", vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber", herkunft: "ABGLEICH" as const, }; await testPrisma.mitarbeiter.create({ data: stammdaten }); await expect(testPrisma.mitarbeiter.create({ data: stammdaten })).rejects.toThrow(); }); it("laesst beliebig viele manuell angelegte Personen ohne Quelle zu", async () => { const kunde = await kundeAnlegen(); // Postgres behandelt NULL als verschieden, deshalb greift der eindeutige // Index hier nicht – genau so ist es gewollt. for (const name of ["Ada", "Grace", "Alan"]) { await testPrisma.mitarbeiter.create({ data: { kundeId: kunde.id, vorname: name, nachname: "Test", anzeigename: name }, }); } expect(await testPrisma.mitarbeiter.count()).toBe(3); }); }); describe("Abteilung", () => { it("haelt den Namen je Kunde eindeutig, aber nicht ueber Kunden hinweg", async () => { const einer = await kundeAnlegen("MUS"); const anderer = await kundeAnlegen("REI"); await testPrisma.abteilung.create({ data: { kundeId: einer.id, name: "Buchhaltung" } }); await expect( testPrisma.abteilung.create({ data: { kundeId: einer.id, name: "Buchhaltung" } }), ).rejects.toThrow(); const beimAnderen = await testPrisma.abteilung.create({ data: { kundeId: anderer.id, name: "Buchhaltung" }, }); expect(beimAnderen.name).toBe("Buchhaltung"); }); }); describe("Lauf", () => { it("laesst einen Lauf ohne Mitarbeiter und ohne Geraet zu", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const lauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Patchday 2026-10", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); expect(lauf.status).toBe("OFFEN"); expect(lauf.gestartetAm).toBeNull(); }); it("weist Mitarbeiter und Freitextname zugleich zurueck", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const person = await testPrisma.mitarbeiter.create({ data: { kundeId: kunde.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" }, }); await expect( testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Onboarding", kundeId: kunde.id, erstelltVonId: benutzer.id, mitarbeiterId: person.id, mitarbeiterName: "Maria Huber", }, }), ).rejects.toThrow(); }); it("verhindert das Loeschen eines Geraets, an dem ein Lauf haengt", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const geraet = await testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-7K3F9Q", hostname: "PC-001", typ: "PC", registriertAm: new Date(), }, }); await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "PC Neuinstallation", kundeId: kunde.id, erstelltVonId: benutzer.id, geraetId: geraet.id, }, }); await expect(testPrisma.geraet.delete({ where: { id: geraet.id } })).rejects.toThrow(); }); it("verhindert das Loeschen einer Version, auf die ein Lauf verweist", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); await expect(testPrisma.vorlagenVersion.delete({ where: { id: version.id } })).rejects.toThrow(); }); }); describe("Schrittergebnis", () => { it("laesst je Lauf und Schritt nur ein Ergebnis zu", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const abschnitt = await testPrisma.vorlagenAbschnitt.create({ data: { versionId: version.id, titel: "Vorbereitung" }, }); const schritt = await testPrisma.vorlagenSchritt.create({ data: { abschnittId: abschnitt.id, titel: "Ticket erfasst", pflicht: true }, }); await versionVeroeffentlichen(version.id); const lauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); await testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id }, }); await expect( testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id }, }), ).rejects.toThrow(); }); }); describe("Vorlage", () => { it("haelt den Seed-Schluessel eindeutig, wo er gesetzt ist", async () => { await testPrisma.vorlage.create({ data: { name: "PC Neuinstallation", seedSchluessel: "pc-neuinstallation" }, }); await expect( testPrisma.vorlage.create({ data: { name: "Anders benannt", seedSchluessel: "pc-neuinstallation" }, }), ).rejects.toThrow(/Vorlage_seedSchluessel_einmalig/); }); it("laesst niemanden sein eigener Vorgesetzter sein", async () => { // Die kuerzeste Schleife, und die einzige, die sich ohne Rekursion // abfangen laesst. Laengere haelt der Dienst auf (#020). const kunde = await kundeAnlegen("VORG"); const person = await testPrisma.mitarbeiter.create({ data: { kundeId: kunde.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" }, }); await expect( testPrisma.mitarbeiter.update({ where: { id: person.id }, data: { vorgesetzterId: person.id }, }), ).rejects.toThrow(/Mitarbeiter_vorgesetzter_nicht_selbst/); }); it("laesst beliebig viele Vorlagen ohne Seed-Schluessel zu", async () => { // Der Index ist partiell, sonst waere die zweite eigene Vorlage eine // Dublette von null (#021). await testPrisma.vorlage.create({ data: { name: "Eigene A" } }); await testPrisma.vorlage.create({ data: { name: "Eigene B" } }); expect(await testPrisma.vorlage.count({ where: { seedSchluessel: null } })).toBe(2); }); }); describe("Vorlagen-Version", () => { it("haelt die Versionsnummer je Vorlage eindeutig", async () => { const vorlage = await testPrisma.vorlage.create({ data: { name: "PC Neuinstallation" } }); await testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1 } }); await expect( testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1 } }), ).rejects.toThrow(); }); it("raeumt Abschnitte, Schritte und Variablen mit der Version ab", async () => { // Ein Entwurf darf als Ganzes verworfen werden; nur deshalb steht hier // Cascade und nicht Restrict. const version = await veroeffentlichteVersionAnlegen(); const abschnitt = await testPrisma.vorlagenAbschnitt.create({ data: { versionId: version.id, titel: "Vorbereitung" }, }); await testPrisma.vorlagenSchritt.create({ data: { abschnittId: abschnitt.id, titel: "Ticket erfasst" }, }); await testPrisma.vorlagenVariable.create({ data: { versionId: version.id, schluessel: "os_version", beschriftung: "Betriebssystem" }, }); await testPrisma.vorlagenVersion.delete({ where: { id: version.id } }); expect(await testPrisma.vorlagenAbschnitt.count()).toBe(0); expect(await testPrisma.vorlagenSchritt.count()).toBe(0); expect(await testPrisma.vorlagenVariable.count()).toBe(0); }); }); }); /** * Die Zusicherungen, die erst der unabhaengige Review sichtbar gemacht hat: * Mandantengrenze, Unversehrtheit des Archivs und die Vorleistung fuer Phase 4. */ describe.runIf(datenbankVerfuegbar)("Datenmodell: Grenzen und Zusicherungen", () => { beforeEach(async () => { await datenbankLeeren(); }); describe("Mandantengrenze", () => { it("laesst einen Lauf kein Geraet eines fremden Kunden waehlen", async () => { const einer = await kundeAnlegen("MUS"); const anderer = await kundeAnlegen("REI"); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const fremdesGeraet = await testPrisma.geraet.create({ data: { kundeId: anderer.id, tagCode: "LZ-7K3F9Q", hostname: "REI-PC-001", registriertAm: new Date(), }, }); await expect( testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Quer ueber Kunden", kundeId: einer.id, erstelltVonId: benutzer.id, geraetId: fremdesGeraet.id, }, }), ).rejects.toThrow(/Lauf_geraet_gleicher_kunde/); }); it("laesst einen Lauf keine Person eines fremden Kunden waehlen", async () => { const einer = await kundeAnlegen("MUS"); const anderer = await kundeAnlegen("REI"); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const fremdePerson = await testPrisma.mitarbeiter.create({ data: { kundeId: anderer.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" }, }); await expect( testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Quer ueber Kunden", kundeId: einer.id, erstelltVonId: benutzer.id, mitarbeiterId: fremdePerson.id, }, }), ).rejects.toThrow(/Lauf_mitarbeiter_gleicher_kunde/); }); it("laesst einem Geraet keinen Besitzer eines fremden Kunden geben", async () => { const einer = await kundeAnlegen("MUS"); const anderer = await kundeAnlegen("REI"); const fremdePerson = await testPrisma.mitarbeiter.create({ data: { kundeId: anderer.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" }, }); await expect( testPrisma.geraet.create({ data: { kundeId: einer.id, tagCode: "LZ-7K3F9Q", hostname: "MUS-PC-001", registriertAm: new Date(), mitarbeiterId: fremdePerson.id, }, }), ).rejects.toThrow(/Geraet_besitzer_gleicher_kunde/); }); }); describe("Unversehrtheit des Archivs", () => { it("verhindert, dass ein Lauf seine Schrittergebnisse mitnimmt", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const abschnitt = await testPrisma.vorlagenAbschnitt.create({ data: { versionId: version.id, titel: "Vorbereitung" }, }); const schritt = await testPrisma.vorlagenSchritt.create({ data: { abschnittId: abschnitt.id, titel: "Ticket erfasst" }, }); await versionVeroeffentlichen(version.id); const lauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); await testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id }, }); // Ohne diese Sperre verschwaende ein DELETE auf Lauf das halbe Archiv. await expect(testPrisma.lauf.delete({ where: { id: lauf.id } })).rejects.toThrow(); expect(await testPrisma.schrittergebnis.count()).toBe(1); }); it("verhindert, dass ein Protokolleintrag seinen Urheber verliert", async () => { const benutzer = await benutzerAnlegen(); await testPrisma.protokoll.create({ data: { entitaet: "Benutzer", entitaetId: benutzer.id, aktion: "angemeldet", benutzerId: benutzer.id, }, }); await expect(testPrisma.benutzer.delete({ where: { id: benutzer.id } })).rejects.toThrow(); }); }); describe("Vorlagen-Versionierung", () => { it("laesst je Vorlage nur einen Entwurf zu", async () => { const vorlage = await testPrisma.vorlage.create({ data: { name: "PC Neuinstallation" } }); await testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1 } }); await expect( testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 2 } }), ).rejects.toThrow(/VorlagenVersion_ein_entwurf/); }); it("verlangt beim Veroeffentlichen einen Zeitpunkt", async () => { const vorlage = await testPrisma.vorlage.create({ data: { name: "Probe" } }); await expect( testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1, status: "VEROEFFENTLICHT" }, }), ).rejects.toThrow(/VorlagenVersion_veroeffentlicht_mit_zeitpunkt/); }); }); describe("Status und Zeitstempel", () => { it("verlangt einen Startzeitpunkt, sobald der Lauf nicht mehr offen ist", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); await expect( testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, status: "IN_ARBEIT", }, }), ).rejects.toThrow(/Lauf_begonnen_mit_zeitpunkt/); }); it("verlangt beim Archivieren Abschluss- und Archivzeitpunkt", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); await expect( testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, status: "ARCHIVIERT", gestartetAm: new Date(), }, }), ).rejects.toThrow(/Lauf_abgeschlossen_mit_zeitpunkt/); }); }); describe("Schrittergebnis", () => { it("verlangt eine Begruendung bei nicht zutreffend und fehlgeschlagen", async () => { const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const abschnitt = await testPrisma.vorlagenAbschnitt.create({ data: { versionId: version.id, titel: "Vorbereitung" }, }); const schritt = await testPrisma.vorlagenSchritt.create({ data: { abschnittId: abschnitt.id, titel: "Ticket erfasst" }, }); await versionVeroeffentlichen(version.id); const lauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Probe", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); for (const zustand of ["NICHT_ZUTREFFEND", "FEHLGESCHLAGEN"] as const) { await expect( testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id, zustand }, }), ).rejects.toThrow(/Schrittergebnis_begruendung_noetig/); } const mitNotiz = await testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id, zustand: "NICHT_ZUTREFFEND", notiz: "Geraet hat kein TPM.", }, }); expect(mitNotiz.notiz).toBe("Geraet hat kein TPM."); }); it("nimmt ein Aktionsergebnis verlustfrei auf", async () => { // Vorleistung fuer Phase 4: der Wert muss Log, Kennungen und Zeitstempel // einer ausgefuehrten Aktion tragen koennen, nicht nur einen Haken. const kunde = await kundeAnlegen(); const benutzer = await benutzerAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const abschnitt = await testPrisma.vorlagenAbschnitt.create({ data: { versionId: version.id, titel: "Konto anlegen" }, }); const schritt = await testPrisma.vorlagenSchritt.create({ data: { abschnittId: abschnitt.id, titel: "New-ADUser", typ: "BEFEHL" }, }); await versionVeroeffentlichen(version.id); const lauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Onboarding", kundeId: kunde.id, erstelltVonId: benutzer.id, }, }); const ergebnis = { connector: "ldap", ausgefuehrtAm: "2026-09-25T08:15:00.000Z", dauerMs: 842, objekte: [{ typ: "Benutzer", dn: "CN=Maria Huber,OU=Buchhaltung,DC=firma,DC=local" }], log: ["Verbindung aufgebaut", "Konto angelegt", "Gruppen zugewiesen"], erfolgreich: true, }; const gespeichert = await testPrisma.schrittergebnis.create({ data: { laufId: lauf.id, vorlagenSchrittId: schritt.id, zustand: "ERLEDIGT", wert: ergebnis, }, }); expect(gespeichert.wert).toEqual(ergebnis); }); }); describe("Vorleistung fuer Phase 4", () => { it("laesst einen Lauf ohne erstellenden Menschen zu", async () => { // Wiederkehrende Pruefungen und der Start per Webhook legen Laeufe an, // hinter denen kein Benutzer steht. const kunde = await kundeAnlegen(); const version = await veroeffentlichteVersionAnlegen(); const systemlauf = await testPrisma.lauf.create({ data: { vorlagenVersionId: version.id, titel: "Jaehrliche Pruefung", kundeId: kunde.id }, }); expect(systemlauf.erstelltVonId).toBeNull(); }); }); describe("Vergabebuch fuer tagCodes", () => { it("haelt einen vergebenen Code fest, auch wenn das Geraet verschwindet", async () => { const kunde = await kundeAnlegen(); await testPrisma.tagCodeVergabe.create({ data: { tagCode: "LZ-7K3F9Q" } }); const geraet = await testPrisma.geraet.create({ data: { kundeId: kunde.id, tagCode: "LZ-7K3F9Q" }, }); await testPrisma.geraet.delete({ where: { id: geraet.id } }); expect(await testPrisma.geraet.count()).toBe(0); // Der Code bleibt vergeben – ein alter Aufkleber darf nie auf ein neues // Objekt zeigen. expect(await testPrisma.tagCodeVergabe.count()).toBe(1); await expect(testPrisma.tagCodeVergabe.create({ data: { tagCode: "LZ-7K3F9Q" } })).rejects.toThrow(); }); it("weist einen Code in falscher Form zurueck", async () => { await expect(testPrisma.tagCodeVergabe.create({ data: { tagCode: "LZ-7K3F9O" } })).rejects.toThrow( /TagCodeVergabe_form/, ); }); }); describe("Verzeichnisquelle", () => { it("weist ein Bind-Passwort ohne Versionspraefix zurueck", async () => { const kunde = await kundeAnlegen(); await expect( testPrisma.verzeichnisquelle.create({ data: { kundeId: kunde.id, name: "Firmen-AD", url: "ldaps://dc01.firma.local", bindDn: "CN=Dienst,DC=firma,DC=local", // So saehe ein versehentlich im Klartext gespeichertes Passwort aus. bindPasswortVerschluesselt: "GeheimesPasswort123", basisDn: "DC=firma,DC=local", filter: "(objectClass=user)", attributZuordnung: {}, }, }), ).rejects.toThrow(/Verzeichnisquelle_bindpasswort_verschluesselt/); }); }); describe("Abgleich-Sperre", () => { it("laesst dieselbe Sperre nicht zweimal halten", async () => { const laeuftAbAm = new Date(Date.now() + 60_000); await testPrisma.abgleichSperre.create({ data: { schluessel: "abgleich:quelle-1", gehaltenVon: "instanz-a", laeuftAbAm }, }); await expect( testPrisma.abgleichSperre.create({ data: { schluessel: "abgleich:quelle-1", gehaltenVon: "instanz-b", laeuftAbAm }, }), ).rejects.toThrow(); }); }); });