import { describe, expect, it, vi } from "vitest"; const poolConstructor = vi.hoisted(() => vi.fn()); vi.mock("pg", () => ({ Pool: poolConstructor })); import { parseEkklesiaUserId, parsePermissionId, SYSTEM_ACTOR } from "@ekklesia/contracts"; import { createAuthorizationDrizzleDatabase } from "../src/authorization/database.js"; import { AUTHORIZATION_GRANT_LIST_MAX_LIMIT, AuthorizationStoreError, createAuthorizationStore, } from "../src/authorization/store.js"; describe("Core authorization store — import and construction safety", () => { it("does not construct a pool or open a connection merely because the module is imported", () => { expect(poolConstructor).not.toHaveBeenCalled(); }); it("does not perform any query merely because the store is constructed", () => { poolConstructor.mockImplementation(function fakePool() { return { end: async () => undefined, on: vi.fn(), query: vi.fn() }; }); const { db } = createAuthorizationDrizzleDatabase("postgresql://example.test/ekklesia"); const store = createAuthorizationStore(db); expect(store).toBeDefined(); expect(typeof store.createGrant).toBe("function"); expect(typeof store.revokeGrant).toBe("function"); expect(typeof store.hasActiveGrant).toBe("function"); expect(typeof store.grantPlatformOwner).toBe("function"); expect(typeof store.revokePlatformOwner).toBe("function"); expect(typeof store.isPlatformOwner).toBe("function"); expect(Object.isFrozen(store)).toBe(true); }); }); describe("AuthorizationStoreError", () => { it.each([ "AUTHORIZATION_GRANT_ALREADY_EXISTS", "AUTHORIZATION_GRANT_CREATE_FAILED", "AUTHORIZATION_GRANT_EXPIRATION_INVALID", "AUTHORIZATION_GRANT_LIMIT_INVALID", "AUTHORIZATION_GRANT_LIST_FAILED", "AUTHORIZATION_GRANT_LOOKUP_FAILED", "AUTHORIZATION_GRANT_NOT_FOUND", "AUTHORIZATION_GRANT_REVOKE_FAILED", "AUTHORIZATION_PLATFORM_OWNER_LOOKUP_FAILED", "AUTHORIZATION_PLATFORM_OWNER_MUTATION_FAILED", "AUTHORIZATION_PLATFORM_OWNER_NOT_FOUND", ] as const)("exposes a stable code and a fixed, generic message for %s", (code) => { const error = new AuthorizationStoreError(code); expect(error.code).toBe(code); expect(error.name).toBe("AuthorizationStoreError"); expect(Object.isFrozen(error)).toBe(true); expect(error.message.length).toBeGreaterThan(0); }); it("never carries a cause, raw driver error, SQL, or stack detail beyond the fixed message", () => { const error = new AuthorizationStoreError("AUTHORIZATION_GRANT_CREATE_FAILED"); const publicText = `${String(error)}\n${error.stack ?? ""}`; expect(publicText).toBe( `AuthorizationStoreError: ${error.message}\nAuthorizationStoreError: ${error.message}`, ); }); }); /** * Input validation the store performs *before* any transaction opens, so a rejected value performs * no database work at all. The fake database below records every call it receives; asserting it * stayed empty is what proves "no grant and no audit row" rather than merely "an error was * thrown". `createAuthorizationStore`'s injectable clock keeps this deterministic without * arbitrary sleeps or real-time flakiness. */ describe("Core authorization store — pre-database input validation", () => { const fixedNow = new Date("2026-07-26T12:00:00.000Z"); function recordingStore() { const calls: string[] = []; poolConstructor.mockImplementation(function fakePool() { return { end: async () => undefined, on: vi.fn(), query: vi.fn() }; }); const { db } = createAuthorizationDrizzleDatabase("postgresql://example.test/ekklesia"); const instrumented = new Proxy(db, { get(target, property, receiver) { if (property === "transaction" || property === "select" || property === "execute") { calls.push(String(property)); } return Reflect.get(target, property, receiver) as unknown; }, }); return { calls, store: createAuthorizationStore(instrumented, { now: () => fixedNow }) }; } it.each([ ["a past instant", new Date("2020-01-01T00:00:00.000Z")], ["exactly the acceptance clock", new Date("2026-07-26T12:00:00.000Z")], ["an invalid Date", new Date("nonsense")], ])("rejects %s expiration with no database work at all", async (_label, expiresAt) => { const { calls, store } = recordingStore(); await expect( store.createGrant({ actor: SYSTEM_ACTOR, expiresAt, permission: parsePermissionId("calendar.events.read"), sourceKind: "explicit", userId: parseEkklesiaUserId("subject_1"), }), ).rejects.toMatchObject({ code: "AUTHORIZATION_GRANT_EXPIRATION_INVALID" }); expect(calls).toEqual([]); }); it.each([ 0, -1, 101, 1.5, Number.NaN, Number.POSITIVE_INFINITY, Number.MAX_SAFE_INTEGER + 2, ])("rejects the unusable page size %s with no database work at all", async (limit) => { const { calls, store } = recordingStore(); await expect( store.listGrantsForUser(parseEkklesiaUserId("subject_1"), { limit }), ).rejects.toMatchObject({ code: "AUTHORIZATION_GRANT_LIMIT_INVALID" }); expect(calls).toEqual([]); }); it("exposes one shared maximum page size rather than two that could drift", () => { expect(AUTHORIZATION_GRANT_LIST_MAX_LIMIT).toBe(100); }); });