import { test } from "node:test"; import assert from "node:assert/strict"; import { BusinessError } from "../src/errors.mjs"; import * as access from "../src/access.mjs"; // Row 193a (#1691): the viewer level. R1 (federation design r4.5, section 7) owns access.mjs and its // tests; this file pins only what row 193 adds: a grant's level is collaborator or viewer, and covers() // tells the two apart with a required least level. Every call into R1 goes through check() below, so a change to R1's loader // interface edits one place. const CONTEXT = { providers: ["authentik"], peers: ["factory"], businesses: { "creative-hub": { human: "melanie", instances: ["researcher", "writer"] } }, ssoSubjects: [], }; const check = (doc) => access.checkAccess(doc, CONTEXT); const PROJECT = "0b6f4a52-6f1e-4c7a-9a0e-3f1d2c4b5a69"; const NOW = new Date("2026-10-20T19:30:00Z"); function file(level) { const grant = { id: "g-1", user: "jason", business: "creative-hub", projects: ["*"], instances: ["researcher"] }; if (level !== undefined) grant.level = level; return { accessVersion: 1, revision: 1, users: [{ id: "jason", home: "local", subjects: [{ provider: "authentik", sub: "s-1" }] }], grants: [grant], }; } function refuses(fn, pattern) { assert.throws(fn, (error) => { assert.ok(error instanceof BusinessError, `expected BusinessError, got ${error}`); assert.equal(error.exitCode, 2); assert.match(error.message, pattern); return true; }); } test("a grant's level is collaborator or viewer", () => { for (const level of ["collaborator", "viewer"]) { assert.doesNotThrow(() => check(file(level)), level); } }); test("any other level refuses with the key path named", () => { for (const level of ["owner", "admin", "member", "Viewer", "viewer ", "", null, 1, ["viewer"], undefined]) { refuses(() => check(file(level)), /grants\[0\]\.level/); } }); test("covers tells a viewer grant from a collaborator grant", () => { const target = { business: "creative-hub", project: PROJECT, instance: "researcher" }; const viewer = file("viewer").grants[0]; const collaborator = file("collaborator").grants[0]; // The level is the least one the action needs: share reads ask viewer; create, open, send and share ask // collaborator (design 9). assert.equal(access.covers(viewer, target, NOW, { level: "viewer" }), true); assert.equal(access.covers(viewer, target, NOW, { level: "collaborator" }), false); assert.equal(access.covers(collaborator, target, NOW, { level: "viewer" }), true); assert.equal(access.covers(collaborator, target, NOW, { level: "collaborator" }), true); }); test("covers refuses a call with no level, so a viewer grant can't slip past a collaborator check", () => { const target = { business: "creative-hub", project: PROJECT, instance: "researcher" }; const viewer = file("viewer").grants[0]; assert.throws(() => access.covers(viewer, target, NOW), TypeError); assert.throws(() => access.covers(viewer, target, NOW, {}), TypeError); }); test("a viewer grant still needs its business, project, instance and expiry to cover", () => { const viewer = { ...file("viewer").grants[0], projects: [PROJECT], expires: "2026-10-21T00:00:00Z" }; const target = { business: "creative-hub", project: PROJECT, instance: "researcher" }; const read = { level: "viewer" }; assert.equal(access.covers(viewer, target, NOW, read), true); assert.equal(access.covers(viewer, { ...target, business: "other" }, NOW, read), false); assert.equal(access.covers(viewer, { ...target, project: "7c1e2d3f-0a9b-4c8d-8e7f-6a5b4c3d2e1f" }, NOW, read), false); assert.equal(access.covers(viewer, { ...target, instance: "writer" }, NOW, read), false); assert.equal(access.covers(viewer, target, new Date("2026-10-21T00:00:01Z"), read), false); });