import { ApiError, ErrorCodes } from "@factory/shared/errors"; import { describe, expect, it } from "vitest"; import { can, check, resolvePermission, type AncestorRef, type Grant, type PermissionRequest, } from "./resolve.js"; import type { Share } from "./rules.js"; import type { WorkspaceRole } from "./levels.js"; const WS = "wsp_1"; const TS = "tms_a"; const PG = "pg_1"; const U1 = "u_1"; /** Build a request with sensible defaults; overrides win. */ function req(overrides: Partial = {}): PermissionRequest { return { userId: U1, userName: "Ada", userTeams: ["tms_1"], workspaceRole: "member", workspaceId: WS, resourceType: "page", resourceId: PG, ancestorChain: [ { resourceType: "workspace", id: WS }, { resourceType: "teamspace", id: TS }, { resourceType: "page", id: PG }, ], grants: [], shares: [], ...overrides, }; } function chainOf(pageId: string, teamspaceId: string = TS): AncestorRef[] { return [ { resourceType: "workspace", id: WS }, { resourceType: "teamspace", id: teamspaceId }, { resourceType: "page", id: pageId }, ]; } const grant = (partial: Partial = {}): Grant => ({ granteeType: "user", granteeId: U1, resourceType: "page", resourceId: PG, level: "edit", ...partial, }); describe("role baselines (no grants, no shares)", () => { const root: Partial = { resourceType: "workspace", resourceId: WS, }; it.each([ ["owner", "full_access"], ["admin", "full_access"], ["member", "edit"], ["guest", "view"], ] as [WorkspaceRole, string][])("role %s at workspace root → %s", (role, expected) => { const r = resolvePermission(req({ ...root, workspaceRole: role as WorkspaceRole })); expect(r.level).toBe(expected); }); it.each([ ["owner", "full_access"], ["admin", "full_access"], ["member", "view"], ["guest", "none"], ] as [WorkspaceRole, string][])("role %s on a page (no grants) → %s", (role, expected) => { const r = resolvePermission(req({ workspaceRole: role as WorkspaceRole })); expect(r.level).toBe(expected); }); }); describe("explicit grants on the exact resource", () => { it("user grant raises a member's level to full_access", () => { const r = resolvePermission(req({ grants: [grant({ level: "full_access" })] })); expect(r.level).toBe("full_access"); expect(r.reasons.some((reason) => reason.includes("explicit grant"))).toBe(true); }); it("explicit grant can RESTRICT below the role baseline (member edit → view)", () => { const r = resolvePermission(req({ grants: [grant({ level: "view" })] })); expect(r.level).toBe("view"); expect(r.reasons.some((reason) => reason.includes("overrides"))).toBe(true); }); it("explicit grant can RESTRICT an owner below full_access", () => { const r = resolvePermission( req({ workspaceRole: "owner", grants: [grant({ level: "comment" })] }), ); expect(r.level).toBe("comment"); }); it("takes the max when several user grants match", () => { const r = resolvePermission( req({ grants: [grant({ level: "view" }), grant({ level: "comment" }), grant({ level: "view" })], }), ); expect(r.level).toBe("comment"); }); it("role grants apply to everyone holding that role", () => { const r = resolvePermission( req({ grants: [grant({ granteeType: "role", granteeId: "member", level: "comment" })] }), ); expect(r.level).toBe("comment"); }); it("team grants apply when the user is in the team, and are ignored otherwise", () => { const inTeam = req({ grants: [grant({ granteeType: "team", granteeId: "tms_1", level: "full_access" })], }); expect(resolvePermission(inTeam).level).toBe("full_access"); const outOfTeam = req({ userTeams: ["tms_9"], grants: [grant({ granteeType: "team", granteeId: "tms_1", level: "full_access" })], }); expect(resolvePermission(outOfTeam).level).toBe("view"); // member baseline, no applicable grant }); it("grants for other users / other resources never apply", () => { const r = resolvePermission( req({ grants: [grant({ granteeId: "u_2" }), grant({ resourceId: "pg_999" })], }), ); expect(r.level).toBe("view"); }); it("grants with level none apply to no one", () => { const r = resolvePermission(req({ grants: [grant({ level: "none" })] })); expect(r.level).toBe("view"); expect(r.reasons.some((reason) => reason.includes("explicit grant"))).toBe(false); }); }); describe("inheritance (nearest ancestor wins)", () => { it("a grant on the nearest ancestor (teamspace) applies to the page", () => { const r = resolvePermission( req({ grants: [grant({ resourceType: "teamspace", resourceId: TS, level: "full_access" })] }), ); expect(r.level).toBe("full_access"); expect(r.reasons.some((reason) => reason.includes("inherited from teamspace"))).toBe(true); }); it("nearest ancestor beats a farther one (teamspace view beats workspace full_access)", () => { const r = resolvePermission( req({ grants: [ grant({ resourceType: "teamspace", resourceId: TS, level: "view" }), grant({ resourceType: "workspace", resourceId: WS, level: "full_access" }), ], }), ); expect(r.level).toBe("view"); expect(r.reasons.some((reason) => reason.includes("inherited from teamspace"))).toBe(true); expect(r.reasons.some((reason) => reason.includes("inherited from workspace"))).toBe(false); }); it("a grant on a nearer-but-unmatched ancestor does not block a farther matched one", () => { // teamspace grant is for u_2 (no match) → falls back to workspace grant for u_1 const r = resolvePermission( req({ grants: [ grant({ resourceType: "teamspace", resourceId: TS, granteeId: "u_2", level: "view" }), grant({ resourceType: "workspace", resourceId: WS, level: "comment" }), ], }), ); expect(r.level).toBe("comment"); expect(r.reasons.some((reason) => reason.includes("inherited from workspace"))).toBe(true); }); it("with a 3-level chain, an inherited level maxes with the share floor", () => { // chain: workspace → teamspace → page; workspace grant view + team share comment const r = resolvePermission( req({ grants: [grant({ resourceType: "workspace", resourceId: WS, level: "view" })], shares: [{ scope: "team", level: "comment", teamId: "tms_1" }], }), ); expect(r.level).toBe("comment"); // max(view inherited, view baseline, comment share) }); it("explicit grant on the exact resource beats any ancestor grant", () => { const r = resolvePermission( req({ grants: [ grant({ resourceType: "workspace", resourceId: WS, level: "full_access" }), grant({ level: "view" }), ], }), ); expect(r.level).toBe("view"); }); it("no ancestor grant → none from inheritance", () => { const r = resolvePermission(req({ grants: [] })); expect(r.reasons).toContain("no applicable grant on any ancestor → no inheritance"); }); it("works for database/record/file resource types too", () => { const r = resolvePermission( req({ resourceType: "record", resourceId: "rec_1", ancestorChain: [ { resourceType: "workspace", id: WS }, { resourceType: "database", id: "db_1" }, { resourceType: "record", id: "rec_1" }, ], grants: [grant({ resourceType: "database", resourceId: "db_1", level: "edit" })], }), ); expect(r.level).toBe("edit"); expect(r.reasons.some((reason) => reason.includes("inherited from database"))).toBe(true); }); }); describe("shares", () => { it("workspace share is a floor for every member", () => { const r = resolvePermission(req({ shares: [{ scope: "workspace", level: "comment" }] })); expect(r.level).toBe("comment"); }); it("team share applies only to in-team users", () => { const inTeam = resolvePermission( req({ shares: [{ scope: "team", level: "edit", teamId: "tms_1" }] }), ); expect(inTeam.level).toBe("edit"); const outOfTeam = resolvePermission( req({ userTeams: ["tms_9"], shares: [{ scope: "team", level: "edit", teamId: "tms_1" }] }), ); expect(outOfTeam.level).toBe("view"); }); it("users share applies only to listed users", () => { const listed = resolvePermission( req({ shares: [{ scope: "users", level: "full_access", userIds: [U1, "u_9"] }] }), ); expect(listed.level).toBe("full_access"); const unlisted = resolvePermission( req({ shares: [{ scope: "users", level: "full_access", userIds: ["u_9"] }] }), ); expect(unlisted.level).toBe("view"); }); it("public share is a floor even for authenticated users", () => { const r = resolvePermission( req({ shares: [{ scope: "public", level: "view", slug: "pg_1", password: "secret" }] }), ); expect(r.level).toBe("view"); expect(r.reasons.some((reason) => reason.includes("public share"))).toBe(true); }); it("a public share can raise a guest (none → view) on a page", () => { const r = resolvePermission( req({ workspaceRole: "guest", shares: [{ scope: "public", level: "view" }] }), ); expect(r.level).toBe("view"); }); it("private shares never contribute", () => { const r = resolvePermission(req({ shares: [{ scope: "private", level: "full_access" }] })); expect(r.level).toBe("view"); expect(r.reasons).toContain("no shares visible to requester → no share contribution"); }); it("takes the max over several visible shares", () => { const r = resolvePermission( req({ shares: [ { scope: "workspace", level: "view" }, { scope: "team", level: "comment", teamId: "tms_1" }, { scope: "users", level: "edit", userIds: [U1] }, ], }), ); expect(r.level).toBe("edit"); }); it("a share can raise a guest above none on the workspace root", () => { const r = resolvePermission( req({ workspaceRole: "guest", resourceType: "workspace", resourceId: WS, ancestorChain: [], shares: [{ scope: "public", level: "comment" }], }), ); expect(r.level).toBe("comment"); }); }); describe("can() / check()", () => { it("can() is true when the resolved level satisfies the requirement", () => { expect(can(req({ grants: [grant({ level: "full_access" })] }), "edit")).toBe(true); expect(can(req({ grants: [grant({ level: "full_access" })] }), "full_access")).toBe(true); expect(can(req({ grants: [grant({ level: "view" })] }), "edit")).toBe(false); expect(can(req({ workspaceRole: "guest" }), "view")).toBe(false); // guest on a page = none expect(can(req(), "view")).toBe(true); // member baseline on a page = view }); it("check() passes silently when allowed", () => { expect(() => check(req({ grants: [grant({ level: "edit" })] }), "edit")).not.toThrow(); }); it("check() throws ApiError FORBIDDEN with required/granted/reasons details", () => { try { check(req({ workspaceRole: "guest" }), "comment"); throw new Error("expected check() to throw"); } catch (err) { expect(err).toBeInstanceOf(ApiError); const apiErr = err as ApiError; expect(apiErr.code).toBe(ErrorCodes.FORBIDDEN); expect(apiErr.status).toBe(403); expect(apiErr.details).toEqual( expect.objectContaining({ required: "comment", granted: "none" }), ); const details = apiErr.details as { required: string; granted: string; reasons: string[] }; expect(details.reasons.length).toBeGreaterThan(0); expect( details.reasons.every((reason) => typeof reason === "string" && reason.length > 0), ).toBe(true); } }); }); describe("reasons (auditability)", () => { it("always returns at least one reason (baseline)", () => { for (const role of ["owner", "admin", "member", "guest"] as WorkspaceRole[]) { expect(resolvePermission(req({ workspaceRole: role })).reasons.length).toBeGreaterThan(0); } }); it("explains the full chain for an explicit override case", () => { const r = resolvePermission( req({ workspaceRole: "member", shares: [{ scope: "workspace", level: "view" }], grants: [ grant({ resourceType: "teamspace", resourceId: TS, level: "full_access" }), grant({ level: "comment" }), ], }), ); expect(r.level).toBe("comment"); expect(r.reasons.some((reason) => reason.includes("baseline"))).toBe(true); expect(r.reasons.some((reason) => reason.includes("workspace share"))).toBe(true); expect(r.reasons.some((reason) => reason.includes("inherited from teamspace"))).toBe(true); expect(r.reasons.some((reason) => reason.includes("explicit grant"))).toBe(true); expect(r.reasons).toContain( "final = explicit comment (overrides inherited full_access, baseline view, shares view)", ); }); });