// Standalone JS port of `lib/team.ts::isValidInviteRole` and the // org-membership decision used by POST /api/tenants/[name]/assignments. function isValidInviteRole(role) { return role === "owner" || role === "user"; } // Mirrors the assignment-time check: target user must exist in the // org's member list. Returns true if assign should proceed. function canAssign(targetUserId, orgMembers) { return orgMembers.some((m) => m.userId === targetUserId); } // Mirrors the dropdown candidate-filter on the AssignedUsersPanel: // only `user`-role members who aren't already assigned, excluding // owners (who have implicit access). function pickCandidates(orgMembers, alreadyAssigned) { const assigned = new Set(alreadyAssigned); return orgMembers.filter( (m) => !assigned.has(m.userId) && m.roles.includes("user") && !m.roles.includes("owner") ); } // --------------------------------------------------------------------------- // Test fixtures // --------------------------------------------------------------------------- const orgMembers = [ { userId: "u-1", roles: ["owner"] }, { userId: "u-2", roles: ["user"] }, { userId: "u-3", roles: ["user"] }, { userId: "u-4", roles: [] }, // member with no role yet { userId: "u-5", roles: ["owner", "user"] }, // dual-role ]; let pass = 0, fail = 0; console.log("--- isValidInviteRole ---"); const inviteCases = [ ["owner", true, "owner is valid"], ["user", true, "user is valid"], ["viewer", false, "viewer rejected (dropped in Slice 5)"], ["platform_admin", false, "platform_admin not invitable"], ["platform_operator", false, "platform_operator not invitable"], ["", false, "empty rejected"], ["OWNER", false, "case-sensitive"], ]; for (const [role, expected, note] of inviteCases) { const got = isValidInviteRole(role); const ok = got === expected; console.log(`${ok ? "PASS" : "FAIL"} got=${got} want=${expected} [${note}]`); if (ok) pass++; else fail++; } console.log("\n--- canAssign (membership check) ---"); const assignCases = [ ["u-1", true, "owner can be assigned (idempotent for owners)"], ["u-2", true, "user-role member can be assigned"], ["u-99", false, "non-member rejected"], ["", false, "empty userId rejected"], ]; for (const [targetId, expected, note] of assignCases) { const got = canAssign(targetId, orgMembers); const ok = got === expected; console.log(`${ok ? "PASS" : "FAIL"} got=${got} want=${expected} [${note}]`); if (ok) pass++; else fail++; } console.log("\n--- pickCandidates (assign dropdown) ---"); const candidateCases = [ { assigned: [], expected: ["u-2", "u-3"], note: "user-role members minus owners (u-5 is owner+user, excluded)", }, { assigned: ["u-2"], expected: ["u-3"], note: "u-2 already assigned, only u-3 remains", }, { assigned: ["u-2", "u-3"], expected: [], note: "everyone assigned", }, ]; for (const c of candidateCases) { const got = pickCandidates(orgMembers, c.assigned).map((m) => m.userId); const ok = JSON.stringify(got) === JSON.stringify(c.expected); console.log(`${ok ? "PASS" : "FAIL"} got=${JSON.stringify(got)} want=${JSON.stringify(c.expected)} [${c.note}]`); if (ok) pass++; else fail++; } console.log(`\n${pass} pass, ${fail} fail`); process.exit(fail === 0 ? 0 : 1);