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:
50
src/lib/validation/brigade.test.ts
Normal file
50
src/lib/validation/brigade.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { brigadeProvisionSchema } from "./brigade";
|
||||
|
||||
const valid = {
|
||||
name: "FF Musterdorf",
|
||||
strasse: "Hauptstraße 1",
|
||||
plz: "3100",
|
||||
ort: "St. Pölten",
|
||||
telefon: "+43 2742 12345",
|
||||
adminEmail: "admin@ff-musterdorf.at",
|
||||
adminName: "Max Muster",
|
||||
};
|
||||
|
||||
describe("brigadeProvisionSchema", () => {
|
||||
it("akzeptiert gültige Bereitstellungsdaten", () => {
|
||||
expect(brigadeProvisionSchema.safeParse(valid).success).toBe(true);
|
||||
});
|
||||
|
||||
it("verlangt einen Wehr-Namen", () => {
|
||||
expect(
|
||||
brigadeProvisionSchema.safeParse({ ...valid, name: "" }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("normalisiert die Admin-E-Mail auf Kleinbuchstaben", () => {
|
||||
const r = brigadeProvisionSchema.parse({
|
||||
...valid,
|
||||
adminEmail: "ADMIN@FF.AT",
|
||||
});
|
||||
expect(r.adminEmail).toBe("admin@ff.at");
|
||||
});
|
||||
|
||||
it("lehnt ungültige E-Mail ab", () => {
|
||||
expect(
|
||||
brigadeProvisionSchema.safeParse({ ...valid, adminEmail: "keine-mail" })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("optionale Felder dürfen fehlen", () => {
|
||||
const { email, wehrfuehrer, ...rest } = {
|
||||
...valid,
|
||||
email: undefined,
|
||||
wehrfuehrer: undefined,
|
||||
};
|
||||
void email;
|
||||
void wehrfuehrer;
|
||||
expect(brigadeProvisionSchema.safeParse(rest).success).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user