Files
craftvia/scripts/test-tenant-switch.ts
T
msolarczekandClaude Opus 5 1701db0a62 Fundament: Doku, Testanpassungen, Restbereinigung
- AGENTS.md und README.md auf Craftvia umgeschrieben (Regeln, Andockpunkte,
  Stack mit Garage, tsx-Tests, Gate, Demo-Logins)
- ISMS-Dokumente entfernt (SPEC, Prototypen, Lane-Prompts, Übergaben, Konzepte
  Incidents/Framework); Fundament-Doku (Deploy, Sicherheit, Backup, Identity) bleibt
- Fundament-Tests an Craftvia-Rollen/Branding angepasst, Resttreffer
  certvia/isms in Skripten und Kommentaren bereinigt

Gate: prisma generate, migrate status, tsc, lint, build, 22/22 Testskripte grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:44:58 +02:00

72 lines
3.1 KiB
TypeScript

// WS2-Abnahmetest (Option C) — Mandantenwechsel: server-autoritative Auflösung.
//
// `resolveActiveMembership(identityId, membershipId)` ist der Kern von setActiveTenant
// (und des jwt-„update"-Triggers). Geprüft:
// 1. Eine Mitgliedschaft der eigenen Identity wird aufgelöst (Mandant + Rechte).
// 2. Rechte werden je Mandant NEU aufgelöst (demo user ≠ demo2 tenant-admin) —
// Nachweis der Wechsel-Isolation (keine token-eingefrorenen Fremdrechte).
// 3. Eine FREMDE Mitgliedschaft (andere Identity) wird abgelehnt (null).
// 4. Eine unbekannte Membership-ID wird abgelehnt (null).
//
// Lauf: npx tsx scripts/test-tenant-switch.ts (setzt den Demo-Seed voraus)
import "dotenv/config";
import { prisma } from "../src/server/db";
import { resolveActiveMembership } from "../src/server/auth";
let failures = 0;
const ok = (cond: boolean, msg: string) => {
console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`);
if (!cond) failures++;
};
async function main() {
const multi = await prisma.identity.findUniqueOrThrow({
where: { email: "multi@demo.example" },
include: { memberships: { include: { tenant: true } } },
});
const inDemo = multi.memberships.find((m) => m.tenant.slug === "demo")!;
const inDemo2 = multi.memberships.find((m) => m.tenant.slug === "demo2")!;
console.log("\n— 1/2) Auflösung + Rechte je Mandant (Wechsel-Isolation) —");
const demo = await resolveActiveMembership(multi.id, inDemo.id);
const demo2 = await resolveActiveMembership(multi.id, inDemo2.id);
ok(demo?.tenantSlug === "demo", `demo-Mitgliedschaft → Mandant demo (${demo?.tenantSlug})`);
ok(demo2?.tenantSlug === "demo2", `demo2-Mitgliedschaft → Mandant demo2 (${demo2?.tenantSlug})`);
ok(demo?.roles.includes("technician") ?? false, "in demo: Rolle technician");
ok(demo2?.roles.includes("tenant-admin") ?? false, "in demo2: Rolle tenant-admin");
ok(
JSON.stringify([...(demo?.permissions ?? [])].sort()) !== JSON.stringify([...(demo2?.permissions ?? [])].sort()),
"Rechte je Mandant verschieden (neu aufgelöst, nicht token-eingefroren)"
);
ok(demo?.activeMembershipId === inDemo.id, "activeMembershipId = gewählte Mitgliedschaft");
console.log("\n— 3) Fremde Mitgliedschaft wird abgelehnt —");
// admin@demo ist eine ANDERE Identity; ihre Mitgliedschaft darf multi@ nicht aktivieren.
const admin = await prisma.identity.findUniqueOrThrow({
where: { email: "admin@demo.example" },
include: { memberships: { select: { id: true } } },
});
const foreign = await resolveActiveMembership(multi.id, admin.memberships[0]!.id);
ok(foreign === null, "Mitgliedschaft einer fremden Identity ⇒ null");
console.log("\n— 4) Unbekannte Membership-ID —");
const missing = await resolveActiveMembership(multi.id, "does-not-exist");
ok(missing === null, "unbekannte Membership-ID ⇒ null");
}
main()
.then(async () => {
await prisma.$disconnect();
if (failures > 0) {
console.error(`\n✗ ${failures} Testfall/-fälle fehlgeschlagen.`);
process.exit(1);
}
console.log("\nOK");
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});