Files
Laufzettel/tests/namensregeln.test.ts
T
Florian WildeandClaude Opus 5 b59fbb4645
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(platzhalter): Aufloesung, Namensregeln und Shell-Maskierung
Aus einer Vorlage wird hier der PowerShell-Befehl, den ein Techniker kopiert und
auf einem Domaenencontroller ausfuehrt. Entsprechend dicht ist die Abdeckung:
810 Tests, und die Maskierung wird **ausgefuehrt** geprueft, nicht gelesen.

- Platzhalter aus Geraet, Lauf, Mitarbeiter und Abteilung, dazu die Variablen
  der Vorlage; ein unbekannter bleibt sichtbar stehen
- `{{ou}}` ist der Eltern-Container des DN — genau der Pfad fuer New-ADUser
- Namensregeln fuer upn, sam, mail und hostname mit Transliteration, `{v}`,
  `{nnn}` und `{kundenkuerzel}`; Kundenregeln ueberlagern die globalen feldweise
- Maskierung je Shell, Skript-Kopie je Abschnitt mit Kommentarzeilen
- `{{generiertes_passwort}}`: 20 Zeichen, bei jeder Anzeige neu, nirgends
  gespeichert; im Druck `[Passwort – nicht gespeichert]`

Der unabhaengige Review fand drei Blocker. Der schwerste war eine
Codeausfuehrung: PowerShell liest auch `“`, `”` und `„` als Begrenzer einer
Zeichenkette, die Maskierung kannte nur das ASCII-Zeichen. Ein Abteilungsname
aus Word — Word wandelt `"` von selbst um — reichte. Gefunden hat es der Review
nicht durch Lesen, sondern indem er sich PowerShell in einen Container geholt
und die Markierungsdatei hat entstehen lassen. Der Test dagegen hatte sich still
uebersprungen; er nimmt jetzt selbst das Abbild und bricht in CI ab.

Dazu: zwei verschiedene Passwoerter in einer Antwort (Anzeige und Skript wurden
getrennt gefuellt), eine Vorlagenvariable, die `{{techniker}}` und `{{kunde}}`
ueberschreiben konnte, und die tragende Annahme der Maskierung — dass der
Platzhalter in Anfuehrungszeichen steht —, die niemand geprueft hat.

Schliesst #012

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-25 15:29:43 +02:00

554 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from "vitest";
import {
HOSTNAME_HOECHSTLAENGE,
NamensregelFehler,
NamensregelnSchema,
SAM_HOECHSTLAENGE,
UNTERSCHEIDUNG_HOECHSTWERT,
UNTERSCHEIDUNG_STELLEN,
ZIELFORMATE,
hatZaehler,
kleinGefaltet,
namenBilden,
namenEindeutigBilden,
transliterieren,
} from "@/lib/namensregeln";
/**
* Namensregeln, Transliteration und Zaehler (#012).
*
* Aus diesen Werten werden Befehle auf einem Domaenencontroller. Die Tests sind
* deshalb nicht nur auf den guten Fall gerichtet, sondern vor allem darauf, dass
* bei fehlenden Angaben **nichts** herauskommt und dass die Grenzen des Ziel-
* systems eingehalten werden, auch wenn gekuerzt werden muss.
*/
/** Der Fall aus dem Akzeptanzkriterium — er kommt in mehreren Tests wieder. */
const JOERG = { vorname: "Jörg", nachname: "Müller-Lüdenscheid" } as const;
const MIT_KUNDE = { ...JOERG, kundenkuerzel: "KND" } as const;
describe("transliterieren", () => {
it("faltet „Jörg Müller-Lüdenscheid“ und laesst den Bindestrich stehen", () => {
expect(transliterieren("Jörg Müller-Lüdenscheid")).toBe("Joerg Mueller-Luedenscheid");
expect(kleinGefaltet("Jörg Müller-Lüdenscheid")).toBe("joerg mueller-luedenscheid");
});
it.each([
["ä", "ae"],
["ö", "oe"],
["ü", "ue"],
["Ä", "Ae"],
["Ö", "Oe"],
["Ü", "Ue"],
["ß", "ss"],
["Straße", "Strasse"],
])("macht aus %s zwei Zeichen: %s", (ein, aus) => {
expect(transliterieren(ein)).toBe(aus);
});
it.each([
["é", "e"],
["è", "e"],
["ç", "c"],
["ñ", "n"],
["å", "a"],
["ě", "e"],
["ą", "a"],
["Ş", "S"],
["René Ćosić", "Rene Cosic"],
])("entfernt den Akzent von %s", (ein, aus) => {
expect(transliterieren(ein)).toBe(aus);
});
it.each([
["ł", "l"],
["Łukasz", "Lukasz"],
["ø", "o"],
["Øyvind", "Oyvind"],
["æ", "ae"],
["œ", "oe"],
["đ", "d"],
["þ", "th"],
["ı", "i"],
])("faltet %s, das Unicode nicht zerlegt, zu %s", (ein, aus) => {
// Ohne eigene Tabelle fiele dieser Buchstabe als Nicht-ASCII ganz weg, und
// aus „Kowalski“ wuerde „Kowaski“ — ein Anmeldename, der nie funktioniert.
expect(transliterieren(ein)).toBe(aus);
});
it("wirft alles uebrige Nicht-ASCII weg", () => {
expect(transliterieren("Александр")).toBe("");
expect(transliterieren("李雷")).toBe("");
expect(transliterieren("Max 🎉 Mustermann")).toBe("Max Mustermann");
});
it("wirft Steuerzeichen weg, statt sie zu Leerzeichen zu machen", () => {
// Ein Zeilenumbruch in einem Namen hat in einem Befehl nichts zu suchen.
expect(transliterieren("Max\nMustermann")).toBe("MaxMustermann");
expect(transliterieren("Max\tMustermann")).toBe("MaxMustermann");
});
it("laesst reines ASCII unveraendert", () => {
expect(transliterieren("Max Mustermann-Meier 1970")).toBe("Max Mustermann-Meier 1970");
});
it("kommt mit einer leeren Eingabe zurecht", () => {
expect(transliterieren("")).toBe("");
expect(kleinGefaltet("")).toBe("");
});
it("faltet auch die zerlegte Schreibweise eines Umlauts zu zwei Zeichen", () => {
// „ü“ kann als ein Zeichen oder als u + Trema ankommen — aus einer CSV-Datei
// eines Mac kommt regelmaessig die zweite Form. Beide muessen `mueller`
// ergeben, nicht einmal `mueller` und einmal `muller`.
// `normalize("NFD")` macht aus dem einen Zeichen die zerlegte Form. Sie hier
// als Fluchtfolge hinzuschreiben geht nicht: Prettier schreibt sie in das
// unsichtbare Zeichen zurueck, und dann steht im Test etwas, was niemand sieht.
expect(kleinGefaltet("Müller".normalize("NFD"))).toBe("mueller");
expect(kleinGefaltet("Müller")).toBe("mueller");
});
});
describe("namenBilden – die Akzeptanzkriterien aus #012", () => {
it("bildet joerg.mueller-luedenscheid@firma.de", () => {
expect(namenBilden("upn", "{vorname}.{nachname}@firma.de", JOERG)).toBe(
"joerg.mueller-luedenscheid@firma.de",
);
expect(namenBilden("mail", "{vorname}.{nachname}@firma.de", JOERG)).toBe(
"joerg.mueller-luedenscheid@firma.de",
);
});
it("bildet den sam jmuellerluedensch", () => {
const sam = namenBilden("sam", "{v}{nachname}", JOERG);
expect(sam).toBe("jmuellerluedensch");
// 17 Zeichen: 20 erlaubt das Active Directory, drei bleiben fuer die
// Unterscheidung frei, damit ein zweiter Traeger desselben Namens den ersten
// nicht umbenennt.
expect(sam).toHaveLength(SAM_HOECHSTLAENGE - UNTERSCHEIDUNG_STELLEN);
});
});
describe("namenBilden – Platzhalter", () => {
it("setzt {v} auf den ersten Buchstaben des Vornamens", () => {
expect(namenBilden("sam", "{v}", JOERG)).toBe("j");
expect(namenBilden("sam", "{v}.{nachname}", { vorname: "Ed", nachname: "Ng" })).toBe("e.ng");
});
it("nimmt fuer {v} den ersten Buchstaben, nicht das erste Zeichen", () => {
expect(namenBilden("sam", "{v}{nachname}", { vorname: "'Abd al-Rahman", nachname: "Farouk" })).toBe(
"afarouk",
);
});
it("entfernt Leerzeichen aus zusammengesetzten Vornamen", () => {
expect(namenBilden("sam", "{vorname}.{nachname}", { vorname: "Anna Lena", nachname: "Meier" })).toBe(
"annalena.meier",
);
});
it("setzt {kundenkuerzel} ein", () => {
expect(namenBilden("hostname", "{kundenkuerzel}-PC-{nnn}", MIT_KUNDE, 7)).toBe("KND-PC-007");
});
it("richtet die Stellen des Zaehlers nach der Zahl der n", () => {
expect(namenBilden("hostname", "PC{n}", MIT_KUNDE, 7)).toBe("PC7");
expect(namenBilden("hostname", "PC{nn}", MIT_KUNDE, 7)).toBe("PC07");
expect(namenBilden("hostname", "PC{nnnn}", MIT_KUNDE, 7)).toBe("PC0007");
expect(namenBilden("hostname", "PC{nnnn}", MIT_KUNDE, 1234)).toBe("PC1234");
});
it("liefert nichts, wenn die Zahl nicht in ihre Stellen passt", () => {
// `{nn}` kann keine 100 tragen; ein Hostname mit einer Stelle mehr waere
// ausserhalb der Regel, die der Admin aufgeschrieben hat.
expect(namenBilden("hostname", "PC{nn}", MIT_KUNDE, 100)).toBeUndefined();
expect(namenBilden("hostname", "PC{nn}", MIT_KUNDE, -1)).toBeUndefined();
expect(namenBilden("hostname", "PC{nn}", MIT_KUNDE, 1.5)).toBeUndefined();
});
it("liefert nichts, wenn die Regel einen Zaehler braucht, aber keiner mitkommt", () => {
// Die naechste freie Nummer kennt nur der Aufrufer. `001` zu raten hiesse
// einen Hostnamen zu liefern, der schon an einem anderen Geraet haengt.
expect(namenBilden("hostname", "{kundenkuerzel}-PC-{nnn}", MIT_KUNDE)).toBeUndefined();
});
it("liefert nichts bei zwei Zaehlern in einer Regel", () => {
expect(namenBilden("hostname", "PC{nn}-{nn}", MIT_KUNDE, 7)).toBeUndefined();
});
});
describe("namenBilden – wenn etwas fehlt, kommt nichts heraus", () => {
it.each([
["keine Regel", undefined],
["null", null],
["leere Regel", ""],
["nur Leerzeichen", " "],
])("liefert bei %s nichts", (_fall, regel) => {
expect(namenBilden("upn", regel, JOERG)).toBeUndefined();
});
it.each([
["fehlender Nachname", { vorname: "Jörg" }],
["leerer Nachname", { vorname: "Jörg", nachname: "" }],
["Nachname nur aus Leerzeichen", { vorname: "Jörg", nachname: " " }],
["fehlender Vorname", { nachname: "Müller" }],
["null als Wert", { vorname: null, nachname: "Müller" }],
])("liefert bei %s nichts", (_fall, werte) => {
expect(namenBilden("upn", "{vorname}.{nachname}@firma.de", werte)).toBeUndefined();
});
it("liefert nichts, wenn das Kundenkuerzel fehlt", () => {
// Im Modus INTERN gibt es kein Kuerzel. Ein Hostname `-PC-001` waere
// schlimmer als keiner: er sieht richtig aus.
expect(namenBilden("hostname", "{kundenkuerzel}-PC-{nnn}", JOERG, 1)).toBeUndefined();
});
it("liefert nichts bei einem Platzhalter, den es nicht gibt", () => {
expect(namenBilden("upn", "{vorname}.{abteilung}@firma.de", JOERG)).toBeUndefined();
expect(namenBilden("sam", "{titel}", JOERG)).toBeUndefined();
});
it("liefert nichts bei einem verunglueckten Platzhalter", () => {
// Sonst faellt die Klammer beim Normalisieren weg und der Text bleibt: aus
// `{Nachname}@firma.de` wuerde die Adresse `nachname@firma.de`, und die
// sieht echt aus.
expect(namenBilden("upn", "{Nachname}@firma.de", JOERG)).toBeUndefined();
expect(namenBilden("upn", "{NACHNAME}@firma.de", JOERG)).toBeUndefined();
expect(namenBilden("upn", "{vorname@firma.de", JOERG)).toBeUndefined();
expect(namenBilden("sam", "{vorname}}", JOERG)).toBeUndefined();
});
it("liefert nichts, wenn vom Namen nach der Faltung nichts bleibt", () => {
expect(namenBilden("sam", "{v}{nachname}", { vorname: "###", nachname: "###" })).toBeUndefined();
expect(namenBilden("upn", "{nachname}@firma.de", { nachname: "###" })).toBeUndefined();
expect(namenBilden("sam", "{nachname}", { nachname: "李雷" })).toBeUndefined();
expect(namenBilden("hostname", "{nachname}", { nachname: "___" })).toBeUndefined();
});
it("liefert bei jedem Zielformat nichts, wenn die Regel fehlt", () => {
for (const zielformat of ZIELFORMATE) {
expect(namenBilden(zielformat, undefined, JOERG)).toBeUndefined();
}
});
});
describe("namenBilden – Adressform fuer upn und mail", () => {
it("schreibt klein", () => {
expect(namenBilden("upn", "{vorname}@FIRMA.DE", JOERG)).toBe("joerg@firma.de");
expect(namenBilden("mail", "{NACHNAME}@firma.de", JOERG)).toBeUndefined();
});
it("laesst keine doppelten Punkte stehen", () => {
expect(namenBilden("upn", "{vorname}..{nachname}@firma.de", JOERG)).toBe(
"joerg.mueller-luedenscheid@firma.de",
);
expect(namenBilden("upn", "{vorname}@firma..de", JOERG)).toBe("joerg@firma.de");
});
it("laesst keinen Punkt vor dem @ stehen", () => {
expect(namenBilden("upn", "{vorname}.@firma.de", JOERG)).toBe("joerg@firma.de");
expect(namenBilden("upn", ".{vorname}@firma.de", JOERG)).toBe("joerg@firma.de");
});
it("verlangt genau ein @ und beide Haelften", () => {
expect(namenBilden("upn", "{vorname}.{nachname}", JOERG)).toBeUndefined();
expect(namenBilden("upn", "{vorname}@{nachname}@firma.de", JOERG)).toBeUndefined();
expect(namenBilden("upn", "{vorname}@", JOERG)).toBeUndefined();
expect(namenBilden("upn", "@firma.de", JOERG)).toBeUndefined();
});
it("weist einen Namensbereich mit unbrauchbarer Stufe ab", () => {
expect(namenBilden("upn", "{vorname}@firma.-de", JOERG)).toBeUndefined();
expect(namenBilden("upn", "{vorname}@firma.de-.x", JOERG)).toBeUndefined();
});
it("raeumt Bindestriche am Rand des Namensbereichs weg", () => {
expect(namenBilden("upn", "{vorname}@-firma.de", JOERG)).toBe("joerg@firma.de");
expect(namenBilden("upn", "{vorname}@firma.de-", JOERG)).toBe("joerg@firma.de");
});
it("wirft Zeichen weg, die in einer Adresse nicht vorkommen duerfen", () => {
// „Müller, Jörg“ aus einer CSV-Spalte, in der Vor- und Nachname zusammen
// stehen: das Komma gehoert in keine Adresse.
expect(namenBilden("mail", "{nachname}@firma.de", { nachname: "Mül,ler" })).toBe("mueller@firma.de");
});
it("kuerzt eine Adresse nicht", () => {
// Eine gekuerzte Mail-Adresse ist keine Adresse, sie ist eine falsche.
const lang = namenBilden("mail", "{vorname}.{nachname}@firma.de", {
vorname: "Jörg",
nachname: "Lüdenscheid".repeat(10),
});
expect(lang?.length).toBeGreaterThan(100);
expect(lang?.endsWith("@firma.de")).toBe(true);
});
});
describe("namenBilden – sam", () => {
it("laesst den Punkt stehen und den Bindestrich weg", () => {
expect(namenBilden("sam", "{vorname}.{nachname}", JOERG)).toBe("joerg.muellerlued");
});
it("kuerzt am Ende auf die nutzbare Laenge", () => {
const sam = namenBilden("sam", "{v}{nachname}", { vorname: "Jörg", nachname: "Lüdenscheid".repeat(20) });
expect(sam).toHaveLength(SAM_HOECHSTLAENGE - UNTERSCHEIDUNG_STELLEN);
expect(sam?.startsWith("jluedenscheid")).toBe(true);
});
it("laesst keinen Punkt am Ende stehen, auch nicht nach dem Kuerzen", () => {
// Ein `sAMAccountName` mit Punkt am Ende ist unzulaessig; beim Kuerzen kann
// er genau dort landen.
const sam = namenBilden("sam", "{nachname}.{vorname}", {
vorname: "Jörg",
nachname: "Mueller-Luedensch",
});
expect(sam).toBe("muellerluedensch");
expect(sam?.endsWith(".")).toBe(false);
});
it("nimmt einen Namen aus einem Zeichen", () => {
expect(namenBilden("sam", "{v}{nachname}", { vorname: "Ed", nachname: "Ng" })).toBe("eng");
expect(namenBilden("sam", "{vorname}", { vorname: "Ü" })).toBe("ue");
});
});
describe("namenBilden – hostname", () => {
it("schreibt gross", () => {
// Windows fuehrt den NetBIOS-Namen gross; stuende auf dem Aufkleber etwas
// anderes als das Geraet meldet, sucht der naechste Techniker einen Fehler,
// den es nicht gibt.
expect(namenBilden("hostname", "{kundenkuerzel}-nb-{nnn}", MIT_KUNDE, 3)).toBe("KND-NB-003");
expect(namenBilden("hostname", "{vorname}", JOERG)).toBe("JOERG");
});
it("laesst Unterstrich und Punkt weg", () => {
expect(namenBilden("hostname", "{kundenkuerzel}_PC_{nn}", MIT_KUNDE, 7)).toBe("KNDPC07");
expect(namenBilden("hostname", "{kundenkuerzel}.pc.{nn}", MIT_KUNDE, 7)).toBe("KNDPC07");
});
it("laesst keinen fuehrenden oder abschliessenden Bindestrich stehen", () => {
expect(namenBilden("hostname", "-{kundenkuerzel}-", MIT_KUNDE)).toBe("KND");
expect(namenBilden("hostname", "{kundenkuerzel}--", MIT_KUNDE)).toBe("KND");
});
it("haelt die 15 Zeichen des NetBIOS-Namens ein und kuerzt nie in der Nummer", () => {
// Das ist die gefaehrliche Stelle: wuerde am Ende gekuerzt, verschwaende die
// Nummer, und zwei Geraete bekaemen denselben Namen.
const hostname = namenBilden("hostname", "{nachname}-{nnn}", JOERG, 1);
expect(hostname).toBe("MUELLER-LUED001");
expect(hostname).toHaveLength(HOSTNAME_HOECHSTLAENGE);
expect(hostname?.endsWith("001")).toBe(true);
});
it("behaelt die Nummer auch bei einem sehr langen Namen", () => {
const hostname = namenBilden("hostname", "{nachname}-{nnnn}", { nachname: "Lüdenscheid".repeat(30) }, 42);
expect(hostname).toHaveLength(HOSTNAME_HOECHSTLAENGE);
expect(hostname?.endsWith("0042")).toBe(true);
});
it("behaelt die Nummer auch, wenn sie nicht am Ende der Regel steht", () => {
const hostname = namenBilden("hostname", "{nnn}-{nachname}", { nachname: "Lüdenscheid".repeat(30) }, 42);
expect(hostname).toHaveLength(HOSTNAME_HOECHSTLAENGE);
expect(hostname?.startsWith("042-")).toBe(true);
});
it("haelt ohne Zaehler in der Regel den Vorrat fuer die Unterscheidung frei", () => {
const hostname = namenBilden("hostname", "{nachname}", { nachname: "Lüdenscheid".repeat(5) });
expect(hostname).toHaveLength(HOSTNAME_HOECHSTLAENGE - UNTERSCHEIDUNG_STELLEN);
});
it("liefert nichts, wenn die Nummer allein schon zu lang ist", () => {
expect(namenBilden("hostname", "{nachname}-{nnnnnnnnnnnnnnnn}", JOERG, 1)).toBeUndefined();
});
});
describe("hatZaehler", () => {
it.each([
["{nnn}", true],
["{kundenkuerzel}-PC-{nnn}", true],
["PC{n}", true],
["{vorname}.{nachname}@firma.de", false],
["{nachname}", false],
["", false],
])("erkennt %s", (regel, erwartet) => {
expect(hatZaehler(regel)).toBe(erwartet);
});
});
describe("namenEindeutigBilden – mit Zaehler in der Regel", () => {
it("nimmt die erste Nummer, wenn sie frei ist", async () => {
const istVergeben = vi.fn(async () => false);
await expect(
namenEindeutigBilden("hostname", "{kundenkuerzel}-PC-{nnn}", MIT_KUNDE, istVergeben),
).resolves.toBe("KND-PC-001");
expect(istVergeben).toHaveBeenCalledTimes(1);
expect(istVergeben).toHaveBeenCalledWith("KND-PC-001");
});
it("zaehlt weiter, solange Nummern vergeben sind", async () => {
const vergeben = new Set(["KND-PC-001", "KND-PC-002"]);
const istVergeben = vi.fn(async (kandidat: string) => vergeben.has(kandidat));
await expect(
namenEindeutigBilden("hostname", "{kundenkuerzel}-PC-{nnn}", MIT_KUNDE, istVergeben),
).resolves.toBe("KND-PC-003");
expect(istVergeben).toHaveBeenCalledTimes(3);
});
it("wirft, wenn der Vorrat der Regel erschoepft ist", async () => {
const istVergeben = vi.fn(async () => true);
// `{n}` gibt neun Nummern her. Danach ist die Regel zu eng — das ist eine
// Einstellung, die geaendert werden muss, und keine Nummer, die man
// erfinden darf.
await expect(namenEindeutigBilden("hostname", "PC{n}", MIT_KUNDE, istVergeben)).rejects.toThrow(
NamensregelFehler,
);
expect(istVergeben).toHaveBeenCalledTimes(9);
});
it("fragt nicht nach, wenn die Regel keinen Namen ergibt", async () => {
const istVergeben = vi.fn(async () => false);
await expect(
namenEindeutigBilden("hostname", "{kundenkuerzel}-PC-{nnn}", JOERG, istVergeben),
).resolves.toBeUndefined();
expect(istVergeben).not.toHaveBeenCalled();
});
});
describe("namenEindeutigBilden – ohne Zaehler in der Regel", () => {
it("liefert den Namen unveraendert, wenn er frei ist", async () => {
const istVergeben = vi.fn(async () => false);
await expect(namenEindeutigBilden("sam", "{v}{nachname}", JOERG, istVergeben)).resolves.toBe(
"jmuellerluedensch",
);
expect(istVergeben).toHaveBeenCalledTimes(1);
});
it("haengt bei einer Kollision eine Unterscheidung an und bleibt in der Grenze", async () => {
const vergeben = new Set(["jmuellerluedensch"]);
const istVergeben = vi.fn(async (kandidat: string) => vergeben.has(kandidat));
const sam = await namenEindeutigBilden("sam", "{v}{nachname}", JOERG, istVergeben);
expect(sam).toBe("jmuellerluedensch2");
expect(sam?.length).toBeLessThanOrEqual(SAM_HOECHSTLAENGE);
});
it("bleibt auch bei dreistelliger Unterscheidung in den 20 Zeichen", async () => {
// Die drei zurueckgehaltenen Stellen sind genau dafuer da.
const istVergeben = vi.fn(async (kandidat: string) => kandidat !== "jmuellerluedensch999");
const sam = await namenEindeutigBilden("sam", "{v}{nachname}", JOERG, istVergeben);
expect(sam).toBe("jmuellerluedensch999");
expect(sam).toHaveLength(SAM_HOECHSTLAENGE);
});
it("setzt die Unterscheidung bei Adressen vor das @", async () => {
const vergeben = new Set(["joerg.mueller-luedenscheid@firma.de"]);
const istVergeben = vi.fn(async (kandidat: string) => vergeben.has(kandidat));
await expect(
namenEindeutigBilden("upn", "{vorname}.{nachname}@firma.de", JOERG, istVergeben),
).resolves.toBe("joerg.mueller-luedenscheid2@firma.de");
});
it("bleibt beim Hostnamen in den 15 Zeichen", async () => {
const istVergeben = vi.fn(async (kandidat: string) => kandidat.length < HOSTNAME_HOECHSTLAENGE);
const hostname = await namenEindeutigBilden(
"hostname",
"{nachname}",
{ nachname: "Lüdenscheid".repeat(5) },
istVergeben,
);
expect(hostname).toHaveLength(HOSTNAME_HOECHSTLAENGE);
});
it("wirft, wenn auch alle Unterscheidungen vergeben sind", async () => {
const istVergeben = vi.fn(async () => true);
await expect(namenEindeutigBilden("sam", "{v}{nachname}", JOERG, istVergeben)).rejects.toThrow(
NamensregelFehler,
);
// Der Name selbst und die Zusaetze 2 bis 999.
expect(istVergeben).toHaveBeenCalledTimes(UNTERSCHEIDUNG_HOECHSTWERT);
});
it("fragt ohne Regel nicht nach", async () => {
const istVergeben = vi.fn(async () => false);
await expect(namenEindeutigBilden("sam", undefined, JOERG, istVergeben)).resolves.toBeUndefined();
await expect(namenEindeutigBilden("sam", " ", JOERG, istVergeben)).resolves.toBeUndefined();
expect(istVergeben).not.toHaveBeenCalled();
});
it("gibt einen Fehler der Vergabepruefung unveraendert weiter", async () => {
const istVergeben = vi.fn(async () => {
throw new Error("Datenbank nicht erreichbar");
});
await expect(namenEindeutigBilden("sam", "{v}{nachname}", JOERG, istVergeben)).rejects.toThrow(
"Datenbank nicht erreichbar",
);
});
});
describe("NamensregelnSchema", () => {
it("nimmt die vier Zielformate und trimmt", () => {
const gelesen = NamensregelnSchema.parse({
upn: " {vorname}.{nachname}@firma.de ",
sam: "{v}{nachname}",
mail: "{vorname}.{nachname}@firma.de",
hostname: "{kundenkuerzel}-PC-{nnn}",
});
expect(gelesen.upn).toBe("{vorname}.{nachname}@firma.de");
expect(gelesen.sam).toBe("{v}{nachname}");
});
it("nimmt ein leeres Objekt", () => {
expect(NamensregelnSchema.parse({})).toEqual({});
});
it("weist eine zu lange Regel ab", () => {
expect(NamensregelnSchema.safeParse({ upn: "x".repeat(201) }).success).toBe(false);
});
});
describe("Hostnamen, die Windows ablehnt", () => {
it("schlägt keinen reservierten Gerätenamen vor", () => {
/*
* `CON`, `PRN`, `NUL` und Geschwister sind die alten Geraetenamen von
* MS-DOS. Ein Nachname „Con“ reicht, damit die Regel sie vorschlaegt — und
* Windows nimmt den Computernamen dann nicht an. Gefunden im Review zu #012.
*/
for (const name of ["con", "prn", "aux", "nul", "com1", "lpt9"]) {
expect(namenBilden("hostname", "{nachname}", { nachname: name }), name).toBeUndefined();
}
// Mit etwas davor oder dahinter ist der Name in Ordnung.
expect(namenBilden("hostname", "PC-{nachname}", { nachname: "con" })).toBe("PC-CON");
});
it("schlägt keinen rein numerischen Hostnamen vor", () => {
// Er waere von einer IP-Adresse nicht zu unterscheiden; Windows lehnt ihn ab.
expect(namenBilden("hostname", "{nachname}", { nachname: "12345" })).toBeUndefined();
expect(namenBilden("hostname", "{nnn}", {}, 1)).toBeUndefined();
expect(namenBilden("hostname", "PC-{nnn}", {}, 1)).toBe("PC-001");
});
});