feat(datenmodell): Schema, tagCodes und Geheimnisse (#20)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen,
eine Migration, dazu die framework-freien Helfer für tagCodes und Geheimnisse.

Regeln stehen zusätzlich in der Datenbank, nicht nur im Service: 12 CHECKs,
2 partielle Eindeutigkeitsindizes, 4 zusammengesetzte Fremdschlüssel. Die
Mandantengrenze ist damit strukturell erzwungen. Schrittergebnisse, Kommentare
und Protokolleinträge kaskadieren bewusst nicht — das Archiv ist das Gedächtnis
der Anwendung.

106 Tests, davon 42 gegen die echten Datenbankregeln.

Closes #6

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #20.
This commit is contained in:
2026-09-24 23:11:58 +00:00
co-authored by Claude Opus 5
parent 49ce06f206
commit 7a0f875f55
19 changed files with 2820 additions and 40 deletions
+666
View File
@@ -0,0 +1,666 @@
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. */
async function veroeffentlichteVersionAnlegen() {
const vorlage = await testPrisma.vorlage.create({ data: { name: "Probe" } });
return testPrisma.vorlagenVersion.create({
// veroeffentlichtAm ist Pflicht, sobald der Status VEROEFFENTLICHT ist –
// eine Bedingung in der Datenbank haelt das fest.
data: {
vorlageId: vorlage.id,
version: 1,
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 },
});
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("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" },
});
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" },
});
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" },
});
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();
});
});
});