Files
curriculum-project-hub/hub/test/unit/filelib-permission.test.ts
T

160 lines
6.0 KiB
TypeScript

/**
* 纯权限 reducer 单测(契约 P6 / D11 / 2.3)。
* 矩阵覆盖:个人/Group/祖先继承/max 取最高/不降权/空权限/toggle 冻结;
* 外加确定性随机化不变量(单调性:任何可用 grant 都不超过 effective)。
*/
import { describe, expect, it } from "vitest";
import { checkAccess, effectiveRole, type EffectiveRoleInput, type FileLibGrantFact } from "../../src/database/filelib/permission.js";
const base: EffectiveRoleInput = {
nodeId: "N",
nodeKind: "FOLDER",
ancestorIds: ["A", "R"], // N ⊂ A ⊂ R
independentPermissionsEnabled: false,
userId: "u1",
groupIds: ["g1"],
grants: [],
};
function grant(partial: Partial<FileLibGrantFact>): FileLibGrantFact {
return {
nodeId: "N",
principalType: "USER",
principalId: "u1",
role: "VIEW",
isCreatorGrant: false,
...partial,
};
}
describe("effectiveRole · 契约 P6 矩阵", () => {
it("无任何 grant → null(无权限)", () => {
expect(effectiveRole(base)).toBeNull();
});
it("个人直接 grant 在 self 上 → 生效", () => {
expect(effectiveRole({ ...base, grants: [grant({ role: "EDIT" })] })).toBe("EDIT");
});
it("Group grant 且用户在组内 → 生效", () => {
expect(effectiveRole({ ...base, grants: [grant({ principalType: "GROUP", principalId: "g1", role: "MANAGE" })] })).toBe("MANAGE");
});
it("Group grant 但用户不在组内 → null", () => {
expect(effectiveRole({ ...base, grants: [grant({ principalType: "GROUP", principalId: "g9" })] })).toBeNull();
});
it("祖先链上的 grant 向下继承", () => {
expect(effectiveRole({ ...base, grants: [grant({ nodeId: "A", role: "EDIT" })] })).toBe("EDIT");
expect(effectiveRole({ ...base, grants: [grant({ nodeId: "R", role: "VIEW" })] })).toBe("VIEW");
});
it("取最高:个人低权限 + Group 高权限 → 高权限(个人不能降权)", () => {
const grants = [
grant({ role: "VIEW" }),
grant({ principalType: "GROUP", principalId: "g1", role: "MANAGE" }),
];
expect(effectiveRole({ ...base, grants })).toBe("MANAGE");
});
it("取最高:链上多处 grant,最高者胜", () => {
const grants = [
grant({ nodeId: "R", role: "MANAGE" }),
grant({ nodeId: "A", role: "VIEW" }),
grant({ nodeId: "N", role: "EDIT" }),
];
expect(effectiveRole({ ...base, grants })).toBe("MANAGE");
});
it("链外节点的 grant 不参与", () => {
expect(effectiveRole({ ...base, grants: [grant({ nodeId: "X", role: "MANAGE" })] })).toBeNull();
});
it("他人的个人 grant 不参与", () => {
expect(effectiveRole({ ...base, grants: [grant({ principalId: "u2", role: "MANAGE" })] })).toBeNull();
});
});
describe("effectiveRole · D11 独立权限开关", () => {
const project: EffectiveRoleInput = { ...base, nodeKind: "PROJECT", nodeId: "P" };
it("开关关闭:项目级非创建者 grant 冻结", () => {
const grants = [grant({ nodeId: "P", role: "EDIT" })];
expect(effectiveRole({ ...project, grants })).toBeNull();
});
it("开关关闭:创建者 grant 仍生效", () => {
const grants = [grant({ nodeId: "P", role: "MANAGE", isCreatorGrant: true })];
expect(effectiveRole({ ...project, grants })).toBe("MANAGE");
});
it("开关关闭:祖先链 grant 不受影响", () => {
const grants = [
grant({ nodeId: "P", role: "MANAGE" }), // 冻结
grant({ nodeId: "A", role: "VIEW" }), // 生效
];
expect(effectiveRole({ ...project, grants })).toBe("VIEW");
});
it("开关开启:项目级 grant 恢复参与", () => {
const grants = [grant({ nodeId: "P", role: "EDIT" })];
expect(effectiveRole({ ...project, independentPermissionsEnabled: true, grants })).toBe("EDIT");
});
it("文件夹忽略开关(self grant 照常参与)", () => {
const grants = [grant({ nodeId: "N", role: "EDIT" })];
expect(effectiveRole({ ...base, grants })).toBe("EDIT");
});
});
describe("effectiveRole · 随机化不变量", () => {
// 确定性 LCG,替代 property-based 框架(不新增依赖)。
function lcg(seed: number): () => number {
let s = seed;
return () => {
s = (s * 1103515245 + 12345) % 2147483648;
return s / 2147483648;
};
}
const roles = ["VIEW", "EDIT", "MANAGE"] as const;
it("effective 恰为所有可用 grant 的最高秩(单调、顺序无关)", () => {
const rand = lcg(42);
for (let round = 0; round < 200; round += 1) {
const nodes = ["N", "A", "R", "X"];
const principals = ["u1", "u2", "g1", "g9"];
const grants: FileLibGrantFact[] = Array.from({ length: Math.floor(rand() * 12) }, () => {
const principalId = principals[Math.floor(rand() * principals.length)]!;
return grant({
nodeId: nodes[Math.floor(rand() * nodes.length)]!,
principalType: principalId.startsWith("g") ? "GROUP" : "USER",
principalId,
role: roles[Math.floor(rand() * roles.length)]!,
});
});
const input: EffectiveRoleInput = { ...base, grants };
const applicableRanks = grants
.filter((g) => ["N", "A", "R"].includes(g.nodeId))
.filter((g) => (g.principalType === "USER" ? g.principalId === "u1" : g.principalId === "g1"))
.map((g) => ({ VIEW: 1, EDIT: 2, MANAGE: 3 })[g.role]);
const expected = applicableRanks.length === 0 ? 0 : Math.max(...applicableRanks);
const actual = effectiveRole(input);
expect(actual === null ? 0 : { VIEW: 1, EDIT: 2, MANAGE: 3 }[actual]).toBe(expected);
// 顺序无关
expect(effectiveRole({ ...input, grants: [...grants].reverse() })).toBe(actual);
}
});
});
describe("checkAccess · D8 语义", () => {
it("无权限 → not_found(404 不泄露)", () => {
expect(checkAccess(null, "VIEW")).toEqual({ allowed: false, reason: "not_found" });
});
it("权限不足 → forbidden(403)", () => {
expect(checkAccess("VIEW", "EDIT")).toEqual({ allowed: false, reason: "forbidden" });
});
it("权限足够 → allowed 并带回实际角色", () => {
expect(checkAccess("MANAGE", "EDIT")).toEqual({ allowed: true, role: "MANAGE" });
});
});