This commit is contained in:
98
scripts/verify-team.mjs
Normal file
98
scripts/verify-team.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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);
|
||||
Reference in New Issue
Block a user