Verwaltungsbereich fuer Projektanfragen
Masterplan S.13. Anfragen ansehen, filtern, durchsuchen, Status aendern, interne Notizen, archivieren. 9/9 Sicherheitstests. Bewusste Entscheidungen: * NUR AUF DEUTSCH. Der oeffentliche Teil laeuft in fuenf Sprachen, weil dort Kunden landen. Hier landen ausschliesslich Dogfather und VanVan -- fuenf Sprachen waeren fuenffache Pflege und fuenffache Fehlerflaeche ohne einen einzigen Nutzer, der sie braucht. Genau wie der bestehende interne Bereich des Universe. * Anmeldung ueber die BESTEHENDE Team-Anmeldung (/auth/login) statt einer zweiten eigenen. Zwei Anmeldungen fuer dieselben zwei Personen waeren doppelte Pflege und ein zweiter Ort, an dem ein Zugang vergessen werden kann. Der Code der Zugangswand gilt hier ausdruecklich NICHT -- die Verwaltung steckt hinter zwei getrennten Tueren. * Sitzungstoken im sessionStorage, nicht localStorage: es verschwindet beim Schliessen, dieselbe Regel wie an der Zugangswand. Der Test prueft das ausdruecklich. * Bei abgelaufener Sitzung geht es zurueck zur Anmeldung statt zu einer leeren Liste. Eine leere Liste sieht aus wie "keine Anfragen" und ist damit eine stille Falschaussage. * Gold nur beim Status "neu" -- also genau dort, wo wirklich etwas zu tun ist. Wuerde alles leuchten, leuchtet nichts. * Archivieren statt Loeschen, und die Oberflaeche sagt das auch dazu. Pruefskript: kennt jetzt bewusst einsprachige Seiten (verwaltung, portal) und prueft dort nur die Struktur, statt ein fehlendes Woerterbuch zu melden. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
/* =====================================================================
|
||||
test-webdesign-verwaltung.mjs — Verwaltungsbereich im echten Browser
|
||||
|
||||
Prüft vor allem das, was hier wirklich zählt:
|
||||
- ohne Anmeldung sieht man NICHTS
|
||||
- ein erfundenes Token wird abgewiesen
|
||||
- das Sitzungstoken überlebt das Schließen NICHT
|
||||
- mit Anmeldung: Liste, Detail, Status ändern, Notiz, Archivieren
|
||||
|
||||
Der Verwaltungscode kommt aus der Umgebung, nie aus dieser Datei:
|
||||
WD_ADMIN_CODE=... node test-webdesign-verwaltung.mjs
|
||||
Ohne Code laufen nur die Sicherheitsprüfungen — die sind die wichtigeren.
|
||||
===================================================================== */
|
||||
import { chromium } from "playwright";
|
||||
|
||||
const BASIS = process.env.WD_BASIS || "http://127.0.0.1:4199";
|
||||
const ZUGANG = process.env.WD_CODE || "DOGI-TEST-1";
|
||||
const ADMIN_CODE = process.env.WD_ADMIN_CODE || null;
|
||||
|
||||
let ok = 0, schlecht = 0;
|
||||
const pruefe = (name, b, zusatz = "") => {
|
||||
if (b) { ok++; console.log(" ok " + name); }
|
||||
else { schlecht++; console.log(" FEHLT " + name + " " + zusatz); }
|
||||
};
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const kontext = await browser.newContext();
|
||||
const seite = await kontext.newPage();
|
||||
const konsole = [];
|
||||
seite.on("console", (m) => {
|
||||
if (m.type() !== "error") return;
|
||||
/* Ein abgewiesener Anmeldeversuch erzeugt zwangslaeufig eine
|
||||
Browser-Meldung ueber die 401-Antwort. Das ist das GEWUENSCHTE
|
||||
Verhalten -- wuerde man es als Fehler zaehlen, koennte der Test
|
||||
"falscher Code wird abgewiesen" nie bestehen. */
|
||||
if (/status of 40[13]/.test(m.text())) return;
|
||||
konsole.push(m.text());
|
||||
});
|
||||
seite.on("pageerror", (e) => konsole.push("pageerror: " + e.message));
|
||||
|
||||
console.log("\n=== Verwaltungsbereich ===\n");
|
||||
|
||||
/* ---------- 1. Die Seite selbst ist hinter der Zugangswand ---------- */
|
||||
console.log("1. Doppelter Schutz");
|
||||
{
|
||||
const frisch = await browser.newContext();
|
||||
const p = await frisch.newPage();
|
||||
await p.goto(BASIS + "/webdesign/verwaltung.html", { waitUntil: "networkidle" });
|
||||
pruefe("ohne Zugangscode gar nicht erreichbar", p.url().includes("zugang.html"),
|
||||
"-> die Verwaltung steckt hinter ZWEI Türen");
|
||||
await frisch.close();
|
||||
}
|
||||
|
||||
// Ab hier: durch die Zugangswand, aber noch nicht in der Verwaltung angemeldet.
|
||||
await seite.goto(BASIS + "/webdesign/zugang.html", { waitUntil: "networkidle" });
|
||||
await seite.fill("#zg-code-dogfather", ZUGANG);
|
||||
await Promise.all([
|
||||
seite.waitForURL((u) => !u.pathname.endsWith("zugang.html"), { timeout: 15000 }),
|
||||
seite.click("#zg-form-dogfather button[type=submit]"),
|
||||
]);
|
||||
|
||||
console.log("\n2. Anmeldung der Verwaltung");
|
||||
await seite.goto(BASIS + "/webdesign/verwaltung.html", { waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(500);
|
||||
pruefe("Anmeldemaske erscheint", await seite.locator("#vw-anmeldung").isVisible());
|
||||
pruefe("Arbeitsbereich bleibt verborgen", !(await seite.locator("#vw-bereich").isVisible()));
|
||||
pruefe("keine Anfragedaten im Quelltext",
|
||||
!(await seite.content()).includes("A-2608"),
|
||||
"-> ohne Anmeldung darf nichts durchsickern");
|
||||
|
||||
await seite.click("#vw-login-form button[type=submit]");
|
||||
await seite.waitForTimeout(400);
|
||||
pruefe("leerer Code wird bemaengelt",
|
||||
(await seite.locator("#vw-feld-code.wd-feld--fehler").count()) > 0);
|
||||
|
||||
await seite.fill("#vw-code", "garantiert-falscher-code");
|
||||
await seite.click("#vw-login-form button[type=submit]");
|
||||
/* Auf die Fehlermarkierung WARTEN statt fest zu schlafen: der Aufruf geht
|
||||
an den echten Server, und eine feste Wartezeit ist entweder zu kurz
|
||||
(Fehlalarm) oder unnoetig lang. */
|
||||
const abgewiesen = await seite.locator("#vw-feld-code.wd-feld--fehler")
|
||||
.waitFor({ state: "attached", timeout: 15000 }).then(() => true).catch(() => false);
|
||||
pruefe("falscher Code wird abgewiesen", abgewiesen,
|
||||
abgewiesen ? "" : "Meldung: " + (await seite.locator("#vw-feld-code .wd-feld-fehler").textContent().catch(() => "")));
|
||||
pruefe("bleibt auf der Anmeldemaske", await seite.locator("#vw-anmeldung").isVisible());
|
||||
|
||||
/* ---------- 3. Erfundenes Token ---------- */
|
||||
console.log("\n3. Erfundenes Sitzungstoken");
|
||||
await seite.evaluate(() => sessionStorage.setItem("wd-vw-token", "voellig-erfunden-1234"));
|
||||
await seite.reload({ waitUntil: "networkidle" });
|
||||
await seite.waitForTimeout(2500);
|
||||
pruefe("erfundenes Token fuehrt zurueck zur Anmeldung",
|
||||
await seite.locator("#vw-anmeldung").isVisible(),
|
||||
"-> und nicht zu einer leeren Liste, die wie 'keine Anfragen' aussieht");
|
||||
|
||||
/* ---------- 4. Token verschwindet beim Schliessen ---------- */
|
||||
console.log("\n4. Sitzung endet beim Schliessen");
|
||||
{
|
||||
const speicherArt = await seite.evaluate(() => {
|
||||
sessionStorage.setItem("wd-vw-token", "test");
|
||||
const inSession = sessionStorage.getItem("wd-vw-token") !== null;
|
||||
const inLocal = localStorage.getItem("wd-vw-token") !== null;
|
||||
sessionStorage.removeItem("wd-vw-token");
|
||||
return { inSession, inLocal };
|
||||
});
|
||||
pruefe("Token liegt im Sitzungsspeicher, nicht dauerhaft",
|
||||
speicherArt.inSession && !speicherArt.inLocal,
|
||||
"-> sonst bliebe die Verwaltung nach dem Schliessen offen");
|
||||
}
|
||||
|
||||
/* ---------- 5. Mit echtem Zugang ---------- */
|
||||
if (!ADMIN_CODE) {
|
||||
console.log("\n5. Angemeldeter Durchlauf");
|
||||
console.log(" (uebersprungen -- WD_ADMIN_CODE nicht gesetzt)");
|
||||
} else {
|
||||
console.log("\n5. Angemeldeter Durchlauf");
|
||||
await seite.evaluate(() => sessionStorage.removeItem("wd-vw-token"));
|
||||
await seite.reload({ waitUntil: "networkidle" });
|
||||
await seite.fill("#vw-code", ADMIN_CODE);
|
||||
await seite.click("#vw-login-form button[type=submit]");
|
||||
await seite.waitForTimeout(3500);
|
||||
|
||||
const drin = await seite.locator("#vw-bereich").isVisible();
|
||||
pruefe("Anmeldung erfolgreich", drin,
|
||||
drin ? "" : (await seite.locator("#vw-feld-code .wd-feld-fehler").textContent().catch(() => "")));
|
||||
|
||||
if (drin) {
|
||||
pruefe("Kennzahlen werden angezeigt",
|
||||
(await seite.locator("#vw-zahlen .vw-zahl").count()) === 5);
|
||||
pruefe("Filterleiste ist da",
|
||||
(await seite.locator("#vw-filter .vw-filter-knopf").count()) >= 8);
|
||||
|
||||
const anzahl = await seite.locator(".vw-karte").count();
|
||||
console.log(" -> " + anzahl + " Anfragen in der Liste");
|
||||
pruefe("Anfragen werden gelistet", anzahl > 0);
|
||||
|
||||
if (anzahl > 0) {
|
||||
await seite.locator(".vw-karte").first().click();
|
||||
await seite.waitForTimeout(2000);
|
||||
pruefe("Detailansicht oeffnet", await seite.locator("#vw-detail.vw-auf").isVisible());
|
||||
const text = await seite.locator("#vw-detail-blatt").textContent();
|
||||
pruefe("zeigt die Anfragenummer", /A-\d{4}-\d{4}/.test(text));
|
||||
pruefe("zeigt Kontakt und Vorhaben", text.includes("Kontakt") && text.includes("Vorhaben"));
|
||||
pruefe("bietet Statuswechsel an",
|
||||
(await seite.locator("[data-neuer-status]").count()) === 7);
|
||||
pruefe("hat ein Notizfeld", await seite.locator("#vw-neue-notiz").isVisible());
|
||||
pruefe("Archivieren erklaert, dass nichts geloescht wird",
|
||||
text.includes("löscht nichts") || text.includes("loescht nichts"));
|
||||
|
||||
await seite.keyboard.press("Escape");
|
||||
await seite.waitForTimeout(400);
|
||||
pruefe("Escape schliesst die Detailansicht",
|
||||
!(await seite.locator("#vw-detail.vw-auf").isVisible()));
|
||||
}
|
||||
|
||||
// Suche
|
||||
await seite.fill("#vw-suche", "zzz-gibt-es-nicht");
|
||||
await seite.waitForTimeout(400);
|
||||
pruefe("Suche ohne Treffer meldet das verstaendlich",
|
||||
(await seite.locator("#vw-liste").textContent()).includes("Filter"));
|
||||
await seite.fill("#vw-suche", "");
|
||||
await seite.waitForTimeout(300);
|
||||
|
||||
// Abmelden
|
||||
await seite.click("#vw-abmelden");
|
||||
await seite.waitForTimeout(2500);
|
||||
pruefe("Abmelden fuehrt zurueck zur Anmeldung",
|
||||
await seite.locator("#vw-anmeldung").isVisible());
|
||||
pruefe("Token ist nach dem Abmelden weg",
|
||||
(await seite.evaluate(() => sessionStorage.getItem("wd-vw-token"))) === null);
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(konsole.length ? "\nKONSOLENFEHLER:\n" + [...new Set(konsole)].join("\n") : "\nKeine Konsolenfehler.");
|
||||
console.log(`\n=== ${ok} bestanden, ${schlecht} gescheitert ===\n`);
|
||||
process.exit(schlecht === 0 && konsole.length === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user