// 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); });