153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
/**
|
|
* Unit tests for the Object Visibility Membrane.
|
|
*
|
|
* Run: bun test shared/membrane.test.ts
|
|
*/
|
|
|
|
import { describe, expect, it } from "bun:test";
|
|
import {
|
|
isVisibleTo,
|
|
filterByVisibility,
|
|
filterArrayByVisibility,
|
|
DEFAULT_VISIBILITY,
|
|
type ObjectVisibility,
|
|
} from "./membrane";
|
|
|
|
// ── isVisibleTo ──
|
|
|
|
describe("isVisibleTo", () => {
|
|
it("treats undefined/null as viewer (open default)", () => {
|
|
expect(isVisibleTo(undefined, "viewer")).toBe(true);
|
|
expect(isVisibleTo(null, "viewer")).toBe(true);
|
|
expect(isVisibleTo(undefined, "member")).toBe(true);
|
|
expect(isVisibleTo(null, "admin")).toBe(true);
|
|
});
|
|
|
|
it("viewer visibility is visible to all roles", () => {
|
|
expect(isVisibleTo("viewer", "viewer")).toBe(true);
|
|
expect(isVisibleTo("viewer", "member")).toBe(true);
|
|
expect(isVisibleTo("viewer", "moderator")).toBe(true);
|
|
expect(isVisibleTo("viewer", "admin")).toBe(true);
|
|
});
|
|
|
|
it("member visibility is hidden from viewers", () => {
|
|
expect(isVisibleTo("member", "viewer")).toBe(false);
|
|
expect(isVisibleTo("member", "member")).toBe(true);
|
|
expect(isVisibleTo("member", "moderator")).toBe(true);
|
|
expect(isVisibleTo("member", "admin")).toBe(true);
|
|
});
|
|
|
|
it("moderator visibility is hidden from viewers and members", () => {
|
|
expect(isVisibleTo("moderator", "viewer")).toBe(false);
|
|
expect(isVisibleTo("moderator", "member")).toBe(false);
|
|
expect(isVisibleTo("moderator", "moderator")).toBe(true);
|
|
expect(isVisibleTo("moderator", "admin")).toBe(true);
|
|
});
|
|
|
|
it("admin visibility is only visible to admins", () => {
|
|
expect(isVisibleTo("admin", "viewer")).toBe(false);
|
|
expect(isVisibleTo("admin", "member")).toBe(false);
|
|
expect(isVisibleTo("admin", "moderator")).toBe(false);
|
|
expect(isVisibleTo("admin", "admin")).toBe(true);
|
|
});
|
|
|
|
it("unknown roles default to level 0", () => {
|
|
expect(isVisibleTo("viewer", "unknown")).toBe(true); // level 0 >= 0
|
|
expect(isVisibleTo("member", "unknown")).toBe(false); // level 0 < 1
|
|
});
|
|
});
|
|
|
|
// ── filterByVisibility ──
|
|
|
|
describe("filterByVisibility", () => {
|
|
const items: Record<string, { id: string; visibility?: ObjectVisibility }> = {
|
|
a: { id: "a" }, // undefined → viewer
|
|
b: { id: "b", visibility: "viewer" },
|
|
c: { id: "c", visibility: "member" },
|
|
d: { id: "d", visibility: "moderator" },
|
|
e: { id: "e", visibility: "admin" },
|
|
};
|
|
|
|
it("viewer sees only viewer-level items", () => {
|
|
const result = filterByVisibility(items, "viewer");
|
|
expect(Object.keys(result).sort()).toEqual(["a", "b"]);
|
|
});
|
|
|
|
it("member sees viewer + member items", () => {
|
|
const result = filterByVisibility(items, "member");
|
|
expect(Object.keys(result).sort()).toEqual(["a", "b", "c"]);
|
|
});
|
|
|
|
it("moderator sees viewer + member + moderator items", () => {
|
|
const result = filterByVisibility(items, "moderator");
|
|
expect(Object.keys(result).sort()).toEqual(["a", "b", "c", "d"]);
|
|
});
|
|
|
|
it("admin sees all items", () => {
|
|
const result = filterByVisibility(items, "admin");
|
|
expect(Object.keys(result).sort()).toEqual(["a", "b", "c", "d", "e"]);
|
|
});
|
|
|
|
it("handles empty record", () => {
|
|
const result = filterByVisibility({}, "admin");
|
|
expect(Object.keys(result)).toEqual([]);
|
|
});
|
|
|
|
it("supports custom getVis accessor", () => {
|
|
const calEvents: Record<string, { id: string; accessVisibility?: string }> = {
|
|
x: { id: "x", accessVisibility: "admin" },
|
|
y: { id: "y" },
|
|
};
|
|
const result = filterByVisibility(calEvents, "viewer", (e) => e.accessVisibility as ObjectVisibility | undefined);
|
|
expect(Object.keys(result)).toEqual(["y"]);
|
|
});
|
|
});
|
|
|
|
// ── filterArrayByVisibility ──
|
|
|
|
describe("filterArrayByVisibility", () => {
|
|
const items = [
|
|
{ id: "1", visibility: undefined as ObjectVisibility | undefined },
|
|
{ id: "2", visibility: "viewer" as ObjectVisibility },
|
|
{ id: "3", visibility: "member" as ObjectVisibility },
|
|
{ id: "4", visibility: "admin" as ObjectVisibility },
|
|
];
|
|
|
|
it("filters array correctly for viewer", () => {
|
|
const result = filterArrayByVisibility(items, "viewer");
|
|
expect(result.map((r) => r.id)).toEqual(["1", "2"]);
|
|
});
|
|
|
|
it("filters array correctly for member", () => {
|
|
const result = filterArrayByVisibility(items, "member");
|
|
expect(result.map((r) => r.id)).toEqual(["1", "2", "3"]);
|
|
});
|
|
|
|
it("admin sees all", () => {
|
|
const result = filterArrayByVisibility(items, "admin");
|
|
expect(result.map((r) => r.id)).toEqual(["1", "2", "3", "4"]);
|
|
});
|
|
|
|
it("handles empty array", () => {
|
|
const result = filterArrayByVisibility([], "admin");
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("supports custom accessor for non-standard field names", () => {
|
|
const events = [
|
|
{ id: "e1", accessVisibility: "moderator" as ObjectVisibility },
|
|
{ id: "e2" },
|
|
];
|
|
const result = filterArrayByVisibility(events, "viewer", (e) => (e as any).accessVisibility);
|
|
expect(result.map((r) => r.id)).toEqual(["e2"]);
|
|
});
|
|
});
|
|
|
|
// ── DEFAULT_VISIBILITY ──
|
|
|
|
describe("DEFAULT_VISIBILITY", () => {
|
|
it("is viewer", () => {
|
|
expect(DEFAULT_VISIBILITY).toBe("viewer");
|
|
});
|
|
});
|