Files
Laufzettel/tests/passwort.test.ts
T
florian.wilde eec5de404a
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(platzhalter): Aufloesung, Namensregeln und Shell-Maskierung (#26)
2026-09-25 13:30:31 +00:00

139 lines
5.4 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 } from "vitest";
import {
PASSWORT_GROSS,
PASSWORT_KLEIN,
PASSWORT_LAENGE,
PASSWORT_SONDER,
PASSWORT_ZIFFERN,
passwortErzeugen,
} from "@/lib/passwort";
/**
* Das generierte Passwort (#012).
*
* Die Zusage lautet: bei jeder Anzeige neu und nirgends gespeichert. Hier steht
* die eine Haelfte davon — dass es wirklich jedes Mal ein anderes ist und dass
* es die Anforderungen erfuellt, an denen ein `New-ADUser` sonst scheitert.
*
* Die andere Haelfte — dass es in keiner Tabelle landet — steht in
* `tests/befehle.test.ts` („schreibt beim Fuellen nichts in die Datenbank“). Sie
* prueft, was heute pruefbar ist: dass der Weg vom Lauf zum fertigen Befehl
* nichts schreibt. `Schrittergebnis` und `Kommentar` gibt es erst mit #013; dort
* gehoert derselbe Beweis noch einmal hin, wenn es sie gibt.
*/
const ALLE = PASSWORT_GROSS + PASSWORT_KLEIN + PASSWORT_ZIFFERN + PASSWORT_SONDER;
describe("Erzeugtes Passwort", () => {
it("ist 20 Zeichen lang", () => {
expect(passwortErzeugen()).toHaveLength(20);
expect(PASSWORT_LAENGE).toBe(20);
});
it("benutzt nur Zeichen aus dem vereinbarten Vorrat", () => {
for (let versuch = 0; versuch < 200; versuch += 1) {
for (const zeichen of passwortErzeugen()) {
expect(ALLE.includes(zeichen), `„${zeichen}“ gehört nicht zum Vorrat`).toBe(true);
}
}
});
it("enthält aus jeder Gruppe mindestens ein Zeichen", () => {
// Die Passwortrichtlinie eines Active Directory verlangt ueblicherweise drei
// von vier Gruppen. Ein abgewiesenes New-ADUser laesst den Techniker vor dem
// Kunden stehen.
for (let versuch = 0; versuch < 200; versuch += 1) {
const passwort = passwortErzeugen();
for (const [name, gruppe] of [
["Großbuchstaben", PASSWORT_GROSS],
["Kleinbuchstaben", PASSWORT_KLEIN],
["Ziffern", PASSWORT_ZIFFERN],
["Sonderzeichen", PASSWORT_SONDER],
] as const) {
expect(
[...passwort].some((z) => gruppe.includes(z)),
`${name} fehlen in „${passwort}“`,
).toBe(true);
}
}
});
it("enthält keine verwechselbaren Zeichen", () => {
/*
* Das Passwort wird vom Bildschirm abgelesen und getippt – oft von der
* Person, der das Konto gehoert. Ein falsch gelesenes Zeichen kostet einen
* Anruf.
*
* Verwechselbar ist immer ein **Paar**: aus jedem Paar fehlt genau einer,
* dann ist der andere eindeutig. `B` ist draussen, also darf `8` bleiben;
* `O` ist draussen, also darf nichts mit `0` verwechselt werden — `0` ist
* trotzdem weg, weil es mit `o` zusammenfaellt.
*/
for (let versuch = 0; versuch < 200; versuch += 1) {
expect(passwortErzeugen()).not.toMatch(/[IlB1|O0]/u);
}
});
it("lässt aus jedem verwechselbaren Paar genau einen übrig", () => {
// Haelt die Begruendung oben fest: `8` ohne `B` ist eindeutig, `1` ohne `l`
// und `I` waere es auch — es ist trotzdem draussen, weil `|` in manchen
// Schriften gleich aussieht.
const vorrat = PASSWORT_GROSS + PASSWORT_KLEIN + PASSWORT_ZIFFERN;
for (const paar of ["I1", "l1", "O0", "o0", "B8", "Il"]) {
const drin = [...paar].filter((zeichen) => vorrat.includes(zeichen));
expect(drin.length, `„${paar}“ ist beides im Vorrat`).toBeLessThanOrEqual(1);
}
});
it("enthält nichts, was einen Befehl oder eine CSV-Zelle zerbricht", () => {
// Die Maskierung faengt das ab – aber ein Passwort, das nur wegen korrekter
// Maskierung funktioniert, ist eine Wette auf jeden kuenftigen Aufrufer.
for (let versuch = 0; versuch < 200; versuch += 1) {
expect(passwortErzeugen()).not.toMatch(/["'$`\\;&|<>%,\n\r]/u);
}
});
it("liefert bei zwei Aufrufen verschiedene Passwörter", () => {
// Genau das Akzeptanzkriterium: ein zweiter Aufruf ist ein **anderes**
// Passwort, nicht dasselbe noch einmal.
const gesehen = new Set<string>();
for (let versuch = 0; versuch < 500; versuch += 1) {
gesehen.add(passwortErzeugen());
}
expect(gesehen.size).toBe(500);
});
it("verteilt die Pflichtzeichen über die ganze Länge", () => {
// Ohne Mischen staenden Gross, Klein, Ziffer und Sonderzeichen immer an den
// ersten vier Stellen — erkennbar, und damit vier Zeichen weniger Zufall.
const stellen = new Set<number>();
for (let versuch = 0; versuch < 300; versuch += 1) {
const passwort = passwortErzeugen();
const stelle = [...passwort].findIndex((z) => PASSWORT_SONDER.includes(z));
stellen.add(stelle);
}
expect(stellen.size).toBeGreaterThan(10);
});
it("weist eine Länge ab, die nicht alle vier Gruppen fassen kann", () => {
expect(() => passwortErzeugen(3)).toThrow();
expect(passwortErzeugen(4)).toHaveLength(4);
});
it("fängt nie mit einem Sonderzeichen an", () => {
// `= + - @` am Anfang einer Zelle liest eine Tabellenkalkulation als Formel,
// und ein Passwort geht seinen Weg oft ueber Zwischenablage und Tabelle.
for (let versuch = 0; versuch < 500; versuch += 1) {
expect(PASSWORT_SONDER.includes(passwortErzeugen().charAt(0))).toBe(false);
}
});
it("enthält kein Ausrufezeichen", () => {
// Bei eingeschalteter verzoegerter Ersetzung frisst cmd.exe es still – das
// Konto bekaeme ein anderes Passwort als angezeigt.
for (let versuch = 0; versuch < 200; versuch += 1) {
expect(passwortErzeugen()).not.toContain("!");
}
});
});