import { afterEach, describe, expect, test } from "bun:test"; import { v } from "@wrnexus/validation"; import { defineService, procedure } from "../src/contract.ts"; import { RPC_ERROR_CODES } from "../src/errors.ts"; import { exportSubjectContext } from "../src/identity.ts"; import { implement } from "../src/server.ts"; const originalRpcSecret = process.env.WRNEXUS_RPC_SECRET; const originalAppName = process.env.WRNEXUS_APP_NAME; afterEach(() => { if (originalRpcSecret === undefined) delete process.env.WRNEXUS_RPC_SECRET; else process.env.WRNEXUS_RPC_SECRET = originalRpcSecret; if (originalAppName === undefined) delete process.env.WRNEXUS_APP_NAME; else process.env.WRNEXUS_APP_NAME = originalAppName; }); function configure(appName = "web") { process.env.WRNEXUS_RPC_SECRET = "test-rpc-secret-at-least-32-chars-long"; process.env.WRNEXUS_APP_NAME = appName; } const openContract = defineService({ name: "demo", procedures: { add: procedure .input(v.object({ a: v.number() })) .output<{ a: number }>() .build(), }, }); const guardedContract = defineService({ name: "billing", procedures: { createInvoice: procedure .input(v.object({ amountCents: v.number() })) .output<{ invoiceId: string }>() .permission("invoice:create") .build(), }, }); describe("implement()", () => { test("invokes with validated input", async () => { const service = implement( openContract, { add: async ({ a }) => ({ a: a + 1 }) }, { selfApp: "demo" }, ); const result = await service.invoke("add", { a: 1 }); expect(result).toEqual({ ok: true, value: { a: 2 } }); }); test("coerces through the schema", async () => { let received: unknown; const coercing = defineService({ name: "demo2", procedures: { add: procedure .input(v.object({ a: v.number() })) .output<{ a: number }>() .build(), }, }); const service = implement( coercing, { add: async (input) => { received = input; return { a: (input as { a: number }).a }; }, }, { selfApp: "demo2" }, ); await service.invoke("add", { a: "3" }); expect(received).toEqual({ a: 3 }); }); test("rejects schema-invalid input without invoking the handler", async () => { let called = false; const service = implement( openContract, { add: async ({ a }) => { called = true; return { a }; }, }, { selfApp: "demo" }, ); const result = await service.invoke("add", { a: "not-a-number" }); expect(called).toBe(false); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.invalid }); }); test("unknown procedure refused", async () => { const service = implement(openContract, { add: async ({ a }) => ({ a }) }, { selfApp: "demo" }); const result = await service.invoke("subtract", {}); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.unknown }); }); test("handler throw becomes opaque; a secret in the message does not survive", async () => { const secret = "sk-super-secret-db-password"; const service = implement( openContract, { add: async () => { throw new Error(`db error using ${secret}`); }, }, { selfApp: "demo" }, ); const result = await service.invoke("add", { a: 1 }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.code).toBe(RPC_ERROR_CODES.handler); expect(result.message).not.toContain(secret); } }); test("a declared permission is enforced before the handler runs", async () => { let called = false; const service = implement( guardedContract, { createInvoice: async ({ amountCents }) => { called = true; return { invoiceId: `inv_${amountCents}` }; }, }, { selfApp: "billing", checkPermission: async () => false }, ); const result = await service.invoke("createInvoice", { amountCents: 5 }); expect(called).toBe(false); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.denied }); }); test("a permission check that throws denies", async () => { let called = false; const service = implement( guardedContract, { createInvoice: async ({ amountCents }) => { called = true; return { invoiceId: `inv_${amountCents}` }; }, }, { selfApp: "billing", checkPermission: async () => { throw new Error("permission store unavailable"); }, }, ); const result = await service.invoke("createInvoice", { amountCents: 5 }); expect(called).toBe(false); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.denied }); }); test("a declared permission with no checkPermission configured denies (fail closed)", async () => { let called = false; const service = implement( guardedContract, { createInvoice: async ({ amountCents }) => { called = true; return { invoiceId: `inv_${amountCents}` }; }, }, { selfApp: "billing" }, ); const result = await service.invoke("createInvoice", { amountCents: 5 }); expect(called).toBe(false); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.denied }); }); test("a valid identity token reaches the handler as a subject", async () => { configure("web"); const token = await exportSubjectContext( { user: { id: "u1" }, locals: {} } as never, "billing", ); const service = implement( guardedContract, { createInvoice: async ({ amountCents }, ctx) => ({ invoiceId: `${ctx.subject?.subjectId}_${amountCents}`, }), }, { selfApp: "billing", checkPermission: async () => true }, ); const result = await service.invoke("createInvoice", { amountCents: 5 }, token); expect(result).toEqual({ ok: true, value: { invoiceId: "u1_5" } }); }); test("a bad identity token is refused rather than downgraded to anonymous", async () => { configure("web"); let called = false; const service = implement( openContract, { add: async ({ a }, ctx) => { called = true; expect(ctx.subject).toBeUndefined(); return { a }; }, }, { selfApp: "demo" }, ); const result = await service.invoke("add", { a: 1 }, "garbage-token"); expect(called).toBe(false); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.identity }); }); describe("C1: prototype-chain procedure names cannot bypass the permission gate", () => { const PROTO_NAMES = [ "constructor", "toString", "valueOf", "hasOwnProperty", "__proto__", "isPrototypeOf", ]; for (const name of PROTO_NAMES) { test(`"${name}" resolves as unknown, not as a handler`, async () => { let permissionChecked = false; const service = implement( guardedContract, { createInvoice: async ({ amountCents }) => ({ invoiceId: `inv_${amountCents}` }), }, { selfApp: "billing", checkPermission: async () => { permissionChecked = true; return false; }, }, ); const result = await service.invoke(name, { amountCents: 1 }); expect(result).toMatchObject({ ok: false, code: RPC_ERROR_CODES.unknown }); expect(permissionChecked).toBe(false); }); } }); });