Workstream 6: Admin-Panel — Taxonomie & Bereitstellung (Phase 4)
Platform-Admin-only Oberflächen und Domänenlogik: - codes.ts erweitert um allradCode/normalizeCode/codesMatch (Allrad-Infix kanonisch; Suche importiert weiterhin expandNameQuery). Pure-Unit-Tests. - slug.ts (Idempotenz-Key-Erzeugung) + Tests. - audit.ts: writeAudit mit EINER Signatur und optionalem typisierten tx. - provisioning.ts: createBrigadeWithFirstAdmin (Geocoding inline, argon2id, Audit brigade.create/user.create) + resetUserPassword (Audit user.reset). - Zod-Validierung: merkmal/template/equipment-category/brigade (+ Tests). - Server Actions (jede mit Guard als erster Anweisung, default-deny): merkmale (CRUD, Delete blockiert bei Referenz), proposals (promote/merge mit Typ-Kompatibilität), templates (Merkmale/Vorgabewerte/Aliasse), equipment- categories, brigades (Bereitstellung/Reset). Audit in allen Schreib-Actions. - (admin)-Route-Group: Layout mit requirePlatformAdmin als erster Zeile, AdminNav, DataTable, loading/error; Seiten für Merkmale (+Editor), Vorschläge (Merge), Vorlagen (+Detail mit Merkmal-/Alias-Editor und Allrad-Hinweis), Geräte-Kategorien (+Detail), Wehren (Liste/neu/Detail mit Passwort-Reset), paginierter Audit-Viewer mit Filter. Jede Seite ruft zusätzlich den Guard. - i18n: admin-Strings in zentraler de.ts. - Playwright-Specs (deferred, nicht ausgeführt): admin-gating, admin-merkmal-proposal, admin-brigade-provision. Schema NICHT neu definiert — nur importiert. codes.ts ist hier Eigentümer. Offline-Verifikation: tsc --noEmit grün; eslint grün; vitest run grün (119 passed, 7 DB-roundtrip skipped); next build Exit 0; drizzle-kit check ok. DB-/Server-/Browser-abhängige Schritte deferred (kein Postgres/Server im Sandbox). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
73
tests/e2e/admin-gating.spec.ts
Normal file
73
tests/e2e/admin-gating.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Admin-Gating (Workstream 6, Querschnittsstandard 1–3, default-deny dreifach).
|
||||
*
|
||||
* NICHT in der Sandbox ausführbar (kein Server/DB) — deferred. Wird über einen
|
||||
* laufenden Server ausgeführt. Erwartet:
|
||||
* - anonym: jede /admin/*-Seite -> Redirect auf /login.
|
||||
* - wehr_admin / wehr_read: jede /admin/*-Seite -> 403 (forbidden()).
|
||||
* - platform_admin: /admin erreichbar.
|
||||
*
|
||||
* Negativ-Probe: Entfernen von `requirePlatformAdmin()` aus (admin)/layout.tsx
|
||||
* muss diese Suite rot machen.
|
||||
*/
|
||||
|
||||
const ADMIN_PAGES = [
|
||||
"/admin",
|
||||
"/admin/merkmale",
|
||||
"/admin/merkmale/proposals",
|
||||
"/admin/vorlagen",
|
||||
"/admin/geraete-kategorien",
|
||||
"/admin/wehren",
|
||||
"/admin/wehren/neu",
|
||||
"/admin/audit",
|
||||
];
|
||||
|
||||
test.describe("Admin: anonym -> Redirect auf /login", () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
for (const path of ADMIN_PAGES) {
|
||||
test(`anonymer Aufruf von ${path} leitet auf /login um`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(path);
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Diese Projekte setzen einen storageState eines wehr_admin/wehr_read-Kontos
|
||||
// voraus (Test-Workstream stellt die Fixtures bereit). Hier dokumentiert als
|
||||
// erwartetes Verhalten; das tatsächliche Konto wird über --project gewählt.
|
||||
test.describe("Admin: falsche Rolle -> 403", () => {
|
||||
test.skip(
|
||||
!process.env.E2E_WEHR_ADMIN_STATE,
|
||||
"benötigt wehr_admin-Fixture (Test-Workstream)",
|
||||
);
|
||||
test.use({
|
||||
storageState: process.env.E2E_WEHR_ADMIN_STATE ?? { cookies: [], origins: [] },
|
||||
});
|
||||
for (const path of ADMIN_PAGES) {
|
||||
test(`wehr_admin-Aufruf von ${path} -> 403`, async ({ page }) => {
|
||||
const res = await page.goto(path);
|
||||
expect(res?.status()).toBe(403);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("Admin: platform_admin -> erreichbar", () => {
|
||||
test.skip(
|
||||
!process.env.E2E_PLATFORM_ADMIN_STATE,
|
||||
"benötigt platform_admin-Fixture (Test-Workstream)",
|
||||
);
|
||||
test.use({
|
||||
storageState:
|
||||
process.env.E2E_PLATFORM_ADMIN_STATE ?? { cookies: [], origins: [] },
|
||||
});
|
||||
test("/admin ist als platform_admin erreichbar", async ({ page }) => {
|
||||
await page.goto("/admin");
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Administration" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user