rspace-online/shared/membrane.test.ts

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");
});
});