Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
3.1 KiB
TypeScript
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("user") ?? false, "in demo: Rolle user");
|
|
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);
|
|
});
|