import { describe, expect, test } from "bun:test"; import { isCapabilityAllowed } from "./context"; // Scope gating for OAuth-token consumers (hub MCP, future desktop MCP). The // contract: a capability with a requiredScope is only allowed when that scope // was granted; one with no requiredScope is open to any authenticated caller. describe("isCapabilityAllowed", () => { test("no requiredScope → allowed regardless of granted scopes", () => { expect(isCapabilityAllowed({}, [])).toBe(true); expect(isCapabilityAllowed({ requiredScope: undefined }, [])).toBe(true); }); test("requiredScope present in granted scopes → allowed", () => { expect( isCapabilityAllowed({ requiredScope: "timeline:read" }, [ "timeline:read", "projects:read", ]), ).toBe(true); }); test("requiredScope absent from granted scopes → denied", () => { expect( isCapabilityAllowed({ requiredScope: "timeline:write" }, [ "timeline:read", ]), ).toBe(false); }); test("empty granted scopes denies any scoped capability", () => { expect(isCapabilityAllowed({ requiredScope: "invoices:read" }, [])).toBe( false, ); }); test("does not substring-match scope names", () => { // "timeline:read" must not satisfy a requirement for "timeline:read:all" expect( isCapabilityAllowed({ requiredScope: "timeline:read:all" }, [ "timeline:read", ]), ).toBe(false); }); });