Verwaltung
++ Dein persönlicher Zugangscode aus dem internen Bereich — derselbe + wie für das Postfach. Der Code der Zugangswand gilt hier nicht. +
+ +diff --git a/server/pruefe-webdesign-i18n.mjs b/server/pruefe-webdesign-i18n.mjs index d8b2c238..58abe0dc 100644 --- a/server/pruefe-webdesign-i18n.mjs +++ b/server/pruefe-webdesign-i18n.mjs @@ -35,11 +35,30 @@ for (const datei of seiten) { console.log(`\n--- ${datei} ---`); - if (!existsSync(i18nDatei)) { + /* Bewusst einsprachige Seiten. + Der öffentliche Teil läuft in fünf Sprachen, weil dort Kunden landen. + In der Verwaltung landen ausschließlich Dogfather und VanVan — fünf + Sprachen wären dort fünffache Pflege und fünffache Fehlerfläche ohne + einen einzigen Nutzer, der sie braucht. Genau wie der bestehende + interne Bereich des Universe, der ebenfalls nur Deutsch spricht. + Die Tag-Balance wird trotzdem geprüft (siehe unten). */ + const NUR_DEUTSCH = ["verwaltung", "portal"]; + const einsprachig = NUR_DEUTSCH.includes(name); + + if (!existsSync(i18nDatei) && !einsprachig) { console.log(` FEHLER: ${i18nDatei} fehlt`); fehler++; continue; } + if (einsprachig) { + console.log(" bewusst nur Deutsch (interner Bereich) — nur Struktur geprueft"); + for (const tag of ["div", "section", "main", "article", "details", "form", "button"]) { + const auf = (html.match(new RegExp(`<${tag}\\b`, "g")) || []).length; + const zu = (html.match(new RegExp(`${tag}>`, "g")) || []).length; + if (auf !== zu) { console.log(` TAG-UNGLEICHGEWICHT <${tag}>: ${auf} offen, ${zu} geschlossen`); fehler++; } + } + continue; + } // Wörterbuch laden (setzt window.I18N_WD) global.window = {}; diff --git a/server/test-webdesign-verwaltung.mjs b/server/test-webdesign-verwaltung.mjs new file mode 100644 index 00000000..93e82a3f --- /dev/null +++ b/server/test-webdesign-verwaltung.mjs @@ -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); diff --git a/webdesign/verwaltung.html b/webdesign/verwaltung.html new file mode 100644 index 00000000..9352f1bd --- /dev/null +++ b/webdesign/verwaltung.html @@ -0,0 +1,585 @@ + + +
+ + ++ Dein persönlicher Zugangscode aus dem internen Bereich — derselbe + wie für das Postfach. Der Code der Zugangswand gilt hier nicht. +
+ +