/** * Pipeline acceptance: * - `.gate(member)` → typed Unauthorized for anonymous callers * - RateLimited / Forbidden typed denials (never thrown) * - evaluated gate chain lands on the run (not only declared names) * - forged / expired Bearer → Unauthorized (cryptographic verifyAccess) * - extras.principal injection gated to test env / trustedInvoke only */ import { describe, expect, test } from "bun:test"; import { z } from "zod"; import { createSessionStore, issueSessionWithScopes } from "../auth/sessions.ts"; import { createClockRuntime } from "../elements/clock.ts"; import { gate } from "../elements/gate.ts"; import { createRunsRuntime, memoryRunsDriver } from "../runs/index.ts"; import { oke } from "./app.ts"; import { HTTP_FRAME_REDACTED } from "./http-frame.ts"; import { flow, resetFlowSeq } from "./flow.ts"; import { on, resetBindings } from "./on.ts"; import { http } from "./triggers.ts"; import { gateDenialFailure, recordGateEvaluations } from "./pipeline.ts"; import { createRunTelemetry } from "./run-telemetry.ts"; const member = gate.policy("member", ({ auth }) => !!auth.verified); const canOrder = gate.policy("order:create", ({ auth }) => auth.scopes.has("order:create")); describe("gateDenialFailure", () => { test("anonymous → Unauthorized; authed → Forbidden; rate → RateLimited", () => { const anon = gateDenialFailure( { name: "member", kind: "policy", allowed: false, reason: "policy denied" }, { auth: { userId: null, scopes: new Set() }, operator: { id: null }, }, ); expect(anon.error.code).toBe("Unauthorized"); expect(anon.error.message).toBe("Authentication required."); const forbid = gateDenialFailure( { name: "order:create", kind: "policy", allowed: false }, { auth: { userId: "u1", scopes: new Set(), verified: true }, operator: { id: null }, }, ); expect(forbid.error.code).toBe("Forbidden"); expect(forbid.error.data).toEqual({ gate: "order:create", reason: "policy_denied" }); expect(forbid.error.message).toBe("Policy denied this request."); const rate = gateDenialFailure( { name: "rate:x", kind: "rate", allowed: false, retryAfterMs: 1500, reason: "rate limited", }, { auth: { userId: "u1", scopes: new Set(), verified: true }, operator: { id: null }, }, ); expect(rate.error.code).toBe("RateLimited"); expect(rate.error.data).toEqual({ retryAfterMs: 1500 }); expect(rate.error.message).toBe("Too many requests. Try again later."); }); }); describe("http trigger — .public()", () => { test("attaches the public sentinel without calling gate()", () => { const trigger = http.get("/health").public(); expect(trigger.gates.map((g) => (typeof g === "string" ? g : g.name))).toEqual(["public"]); expect(trigger.liveSignal).toBeUndefined(); }); }); describe("pipeline — Unauthorized for anonymous", () => { test("flow with .gate(member) returns typed Unauthorized", async () => { resetBindings(); resetFlowSeq(); on( http .post("/orders", { in: z.object({ sku: z.string() }), out: z.object({ id: z.string() }), }) .gate(member), flow("orders.create", { do: (_input, fx) => ({ id: fx.id() }), }), ); const runs = createRunsRuntime({ driver: memoryRunsDriver }); const app = oke({ name: "gates-pipeline", gate: { policies: [member] }, runs, env: "test", }); await app.boot({ env: "test", gates: [member], runs }); const res = await app.fetch( new Request("http://localhost/orders", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ sku: "COFFEE" }), }), ); expect(res.status).toBe(401); const body = (await res.json()) as { data: null; error: { code: string }; }; expect(body.error.code).toBe("Unauthorized"); await app.bootResult?.close(); }); test("authenticated member passes; missing scope → Forbidden", async () => { resetBindings(); resetFlowSeq(); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(member, canOrder), flow("orders.create2", { do: () => ({ ok: true }), }), ); const app = oke({ name: "gates-forbid", gate: { policies: [member, canOrder] }, env: "test", }); await app.boot({ env: "test", gates: [member, canOrder] }); const res = await app.execute( app.flow("orders.create2")!, { sku: "X" }, http.post("/orders").gate(member, canOrder), { principal: { plane: "user", userId: "u1", scopes: new Set(), // no order:create verified: true, }, }, ); expect(res.failure?.error.code).toBe("Forbidden"); await app.bootResult?.close(); }); test("flow with .gate(write) flattens gate.all in registration order", async () => { resetBindings(); resetFlowSeq(); const write = gate.all(member, canOrder); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(write), flow("orders.createAll", { do: () => ({ ok: true }), }), ); const app = oke({ name: "gates-all", gate: { policies: [write] }, env: "test", }); await app.boot({ env: "test" }); const anon = await app.execute( app.flow("orders.createAll")!, { sku: "X" }, http.post("/orders").gate(write), ); expect((anon as { failure?: { error: { code: string } } }).failure?.error.code).toBe( "Unauthorized", ); const forbid = await app.execute( app.flow("orders.createAll")!, { sku: "X" }, http.post("/orders").gate(write), { principal: { plane: "user", userId: "u1", scopes: new Set(), verified: true, }, }, ); expect(forbid.failure?.error.code).toBe("Forbidden"); await app.bootResult?.close(); }); }); describe("pipeline — HTTP wire frame", () => { test("fetch stores query, headers, and status beside the body", async () => { resetBindings(); resetFlowSeq(); on( http.get("/tasks", { out: z.object({ ok: z.literal(true) }) }).public(), flow("tasks.list", { do: () => ({ ok: true as const }), }), ); const runs = createRunsRuntime({ driver: memoryRunsDriver }); await runs.open(); const app = oke({ name: "http-frame", gate: { policies: [gate.public] }, runs, env: "test", }); await app.boot({ env: "test", gates: [gate.public], runs }); const res = await app.fetch( new Request("http://localhost/tasks?limit=20", { headers: { accept: "application/json", cookie: "sid=secret" }, }), ); expect(res.status).toBe(200); const event = (await runs.all()).find((row) => row.flow === "tasks.list"); expect(event?.input).toBeUndefined(); expect(event?.http).toMatchObject({ request: { method: "GET", path: "/tasks", query: { limit: "20" }, headers: { cookie: HTTP_FRAME_REDACTED, accept: "application/json" }, }, response: { status: 200 }, }); await app.bootResult?.close(); }); }); describe("pipeline — evaluated gates on the run", () => { test("recorded run contains evaluated chain, not just declared names", async () => { resetBindings(); resetFlowSeq(); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(member, canOrder), flow("orders.create3", { do: () => ({ ok: true }), }), ); const runs = createRunsRuntime({ driver: memoryRunsDriver }); await runs.open(); const app = oke({ name: "gates-eval", gate: { policies: [member, canOrder] }, runs, env: "test", }); await app.boot({ env: "test", gates: [member, canOrder], runs }); // Anonymous — stops at member; canOrder never evaluated. await app.execute( app.flow("orders.create3")!, { sku: "X" }, http.post("/orders").gate(member, canOrder), ); const events = await runs.all(); expect(events.length).toBeGreaterThanOrEqual(1); const last = events[events.length - 1]!; expect(last.gates).toEqual(["member"]); expect(last.gates).not.toContain("order:create"); expect(last.dimensions["gate:member"]).toBe("deny"); await app.bootResult?.close(); }); test("recordGateEvaluations appends pass and deny", () => { const telemetry = createRunTelemetry(); recordGateEvaluations(telemetry, [ { name: "member", kind: "policy", allowed: true }, { name: "order:create", kind: "policy", allowed: false }, ]); expect(telemetry.gates).toEqual(["member", "order:create"]); expect(telemetry.dimensions["gate:member"]).toBe("pass"); expect(telemetry.dimensions["gate:order:create"]).toBe("deny"); }); }); describe("pipeline — Bearer cryptographic verification", () => { test("forged Bearer token → Unauthorized (not a principal)", async () => { resetBindings(); resetFlowSeq(); on( http.get("/secure").gate(member), flow("secure.get", { do: () => ({ ok: true }), }), ); const sessions = createSessionStore(); const app = oke({ name: "auth-forge", gate: { auth: { secret: "hmac-secret-for-tests", sessions, http: false }, policies: [member], }, env: "test", startScheduler: false, }); await app.boot({ env: "test", gates: [member], startScheduler: false }); const res = await app.fetch( new Request("http://localhost/secure", { method: "GET", headers: { authorization: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJoYWNrZXIifQ.forgedsignature", }, }), ); expect(res.status).toBe(401); const body = (await res.json()) as { error: { code: string } }; expect(body.error.code).toBe("Unauthorized"); await app.stop(); }); test("expired Bearer token → Unauthorized", async () => { resetBindings(); resetFlowSeq(); on( http.get("/secure").gate(member), flow("secure.get2", { do: () => ({ ok: true }), }), ); let nowMs = 1_000_000; const sessions = createSessionStore(); const secret = "hmac-secret-for-tests"; const issued = await issueSessionWithScopes( sessions, { secret, now: () => nowMs, accessTtlMs: 60_000 }, { id: "user-1", plane: "user", scopes: ["order:create"], }, ); // Shared clock so boot's auth rebinding still reads `nowMs`. const clockRt = createClockRuntime({ now: () => nowMs, instanceId: "auth-expired", }); const app = oke({ name: "auth-expired", gate: { auth: { secret, sessions, now: () => nowMs, http: false }, policies: [member], }, env: "test", elements: { clock: clockRt }, startScheduler: false, }); await app.boot({ env: "test", gates: [member], startScheduler: false, elements: { clock: clockRt }, }); // Still valid at issue time. const okRes = await app.fetch( new Request("http://localhost/secure", { method: "GET", headers: { authorization: `Bearer ${issued.accessToken}` }, }), ); expect(okRes.status).toBe(200); // Advance past access TTL — same token must fail. nowMs += 120_000; const expiredRes = await app.fetch( new Request("http://localhost/secure", { method: "GET", headers: { authorization: `Bearer ${issued.accessToken}` }, }), ); expect(expiredRes.status).toBe(401); const body = (await expiredRes.json()) as { error: { code: string } }; expect(body.error.code).toBe("Unauthorized"); await app.stop(); }); test("extras.principal injection is ignored outside test env", async () => { resetBindings(); resetFlowSeq(); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(member), flow("orders.inject", { do: () => ({ ok: true }), }), ); const app = oke({ name: "no-inject", gate: { policies: [member] }, env: "dev", config: { drivers: { store: { kv: { dev: "memory", test: "memory", prod: "memory" } }, signal: { dev: "memory", test: "memory", prod: "memory" }, clock: { dev: "memory", test: "frozen", prod: "memory" }, journal: { dev: "memory", test: "memory", prod: "memory" }, channel: { email: { dev: "console", test: "console", prod: "console" } }, }, }, startScheduler: false, }); await app.boot({ env: "dev", gates: [member], startScheduler: false, config: app.$options.config, }); const result = await app.execute( app.bindings.find((b) => b.flow.name === "orders.inject")!.flow, { sku: "X" }, http.post("/orders").gate(member), { principal: { plane: "user", userId: "attacker", scopes: new Set(["*"]), verified: true, }, }, ); expect(result.failure?.error.code).toBe("Unauthorized"); await app.stop(); }); test("extras.trustedInvoke allows principal injection outside test env", async () => { resetBindings(); resetFlowSeq(); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(member), flow("orders.trusted", { do: (_input, fx) => ({ userId: fx.auth.userId }), }), ); const app = oke({ name: "trusted-inject", gate: { policies: [member] }, env: "dev", config: { drivers: { store: { kv: { dev: "memory", test: "memory", prod: "memory" } }, signal: { dev: "memory", test: "memory", prod: "memory" }, clock: { dev: "memory", test: "frozen", prod: "memory" }, journal: { dev: "memory", test: "memory", prod: "memory" }, channel: { email: { dev: "console", test: "console", prod: "console" } }, }, }, startScheduler: false, }); await app.boot({ env: "dev", gates: [member], startScheduler: false, config: app.$options.config, }); const result = await app.execute( app.bindings.find((b) => b.flow.name === "orders.trusted")!.flow, { sku: "X" }, http.post("/orders").gate(member), { trustedInvoke: true, principal: { plane: "user", userId: "console-assumed", scopes: new Set(["member"]), verified: true, }, }, ); expect(result.failure).toBeUndefined(); expect(result.output).toEqual({ userId: "console-assumed" }); await app.stop(); }); test("extras.bypassGates skips the gate chain only with trustedInvoke", async () => { resetBindings(); resetFlowSeq(); on( http.post("/orders", { in: z.object({ sku: z.string() }) }).gate(member), flow("orders.bypass", { do: (_input, fx) => ({ userId: fx.auth.userId }), }), ); const app = oke({ name: "trusted-bypass", gate: { policies: [member] }, env: "dev", config: { drivers: { store: { kv: { dev: "memory", test: "memory", prod: "memory" } }, signal: { dev: "memory", test: "memory", prod: "memory" }, clock: { dev: "memory", test: "frozen", prod: "memory" }, journal: { dev: "memory", test: "memory", prod: "memory" }, channel: { email: { dev: "console", test: "console", prod: "console" } }, }, }, startScheduler: false, }); await app.boot({ env: "dev", gates: [member], startScheduler: false, config: app.$options.config, }); const flowDef = app.bindings.find((b) => b.flow.name === "orders.bypass")!.flow; const trigger = http.post("/orders").gate(member); const denied = await app.execute(flowDef, { sku: "X" }, trigger, { trustedInvoke: true, principal: { plane: "user", userId: null, verified: false }, }); expect(denied.failure?.error.code).toBe("Unauthorized"); const bypassed = await app.execute(flowDef, { sku: "X" }, trigger, { trustedInvoke: true, bypassGates: true, principal: { plane: "user", userId: "console:operator", verified: true }, }); expect(bypassed.failure).toBeUndefined(); expect(bypassed.output).toEqual({ userId: "console:operator" }); await app.stop(); }); });