/** * Tests for the IAP- and App-Attest-gated Claude BFF Worker. * * Run offline, zero installs: * node --experimental-strip-types --test worker.test.ts * * The crypto verifiers are injectable, so these tests exercise the real * decision-and-proxy logic, the part that decides who gets through and what * gets forwarded, without Apple's certificate chain or a real device. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { parseList, evaluateEntitlement } from "./entitlement.ts"; import type { JWSTransaction } from "./transaction.ts"; import type { TransactionVerifier } from "./storekit.ts"; import type { AppAttestVerifier } from "./appattest.ts"; import { handleRequest, type Handlers } from "./worker.ts"; import type { Env } from "./env.ts"; const baseCfg = () => ({ allowedBundleIds: new Set(["com.example.app"]), allowedProductIds: null as Set | null, now: 1_000_000, }); const prod = (extra: Partial = {}): JWSTransaction => ({ bundleId: "com.example.app", environment: "Production", ...extra, }); test("parseList trims and drops empties", () => { assert.deepEqual([...parseList(" a , b ,, c ")], ["a", "b", "c"]); assert.equal(parseList(undefined).size, 0); }); test("entitlement: allowed bundle + Production + future expiry passes", () => { assert.deepEqual( evaluateEntitlement(prod({ expiresDate: 2_000_000 }), baseCfg()), { ok: true }, ); }); test("entitlement: non-subscription (no expiresDate) passes", () => { assert.deepEqual(evaluateEntitlement(prod(), baseCfg()), { ok: true }); }); test("entitlement: sandbox environment is rejected (403)", () => { const txn = prod({ environment: "Sandbox", expiresDate: 2_000_000 }); assert.deepEqual(evaluateEntitlement(txn, baseCfg()), { ok: false, status: 403, reason: "environment_not_production", }); }); test("entitlement: missing environment is rejected (403)", () => { const txn: JWSTransaction = { bundleId: "com.example.app" }; assert.deepEqual(evaluateEntitlement(txn, baseCfg()), { ok: false, status: 403, reason: "environment_not_production", }); }); test("entitlement: wrong bundle is rejected (403)", () => { const txn = prod({ bundleId: "com.evil.app", expiresDate: 2_000_000 }); assert.deepEqual(evaluateEntitlement(txn, baseCfg()), { ok: false, status: 403, reason: "bundle_id_not_allowed", }); }); test("entitlement: expired subscription is rejected (403)", () => { const txn = prod({ expiresDate: 500_000 }); assert.deepEqual(evaluateEntitlement(txn, baseCfg()), { ok: false, status: 403, reason: "entitlement_expired", }); }); test("entitlement: revoked purchase is rejected (403)", () => { const txn = prod({ expiresDate: 2_000_000, revocationDate: 600_000 }); assert.deepEqual(evaluateEntitlement(txn, baseCfg()), { ok: false, status: 403, reason: "entitlement_revoked", }); }); test("entitlement: product allowlist is enforced when set", () => { const cfg = { ...baseCfg(), allowedProductIds: new Set(["pro.monthly"]) }; assert.equal( evaluateEntitlement(prod({ productId: "pro.monthly" }), cfg).ok, true, ); assert.deepEqual(evaluateEntitlement(prod({ productId: "free.tier" }), cfg), { ok: false, status: 403, reason: "product_id_not_allowed", }); }); // --- handleRequest integration --- const storeKitEnv: Env = { ANTHROPIC_API_KEY: "sk-ant-PLACEHOLDER", ALLOWED_BUNDLE_IDS: "com.example.app", APPLE_ROOT_CA_SHA256: "00", }; function messagesRequest( headers: Record, path = "/v1/messages", ): Request { return new Request(`https://bff.example.com${path}`, { method: "POST", headers: { "content-type": "application/json", ...headers }, body: JSON.stringify({ model: "claude-opus-4-8", max_tokens: 16, messages: [] }), }); } const passTxn: TransactionVerifier = async () => prod({ expiresDate: Date.now() + 60_000 }); function handlers(over: Partial = {}): Handlers { return { verifyTransaction: passTxn, verifyAppAttest: async () => {}, ...over, }; } test("handleRequest: no gate configured → 500, no upstream call", async () => { const res = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok" }), { ANTHROPIC_API_KEY: "sk-ant-PLACEHOLDER" }, handlers(), ); assert.equal(res.status, 500); }); test("handleRequest: disallowed upstream path → 403", async () => { const res = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok" }, "/v1/models"), storeKitEnv, handlers(), ); assert.equal(res.status, 403); }); test("handleRequest: missing transaction header → 401, verifier never called", async () => { let called = false; const res = await handleRequest( messagesRequest({}), storeKitEnv, handlers({ verifyTransaction: async () => { called = true; return prod(); }, }), ); assert.equal(res.status, 401); assert.equal(called, false); }); test("handleRequest: invalid JWS → 401, no upstream call", async () => { const calls: string[] = []; const orig = globalThis.fetch; globalThis.fetch = (async (u: unknown) => { calls.push(String(u)); return new Response("x"); }) as typeof fetch; try { const res = await handleRequest( messagesRequest({ "X-IAP-Transaction": "bad" }), storeKitEnv, handlers({ verifyTransaction: async () => { throw new Error("bad signature"); }, }), ); assert.equal(res.status, 401); assert.equal(calls.length, 0); } finally { globalThis.fetch = orig; } }); test("handleRequest: unentitled caller → 403, no upstream call", async () => { const calls: string[] = []; const orig = globalThis.fetch; globalThis.fetch = (async (u: unknown) => { calls.push(String(u)); return new Response("x"); }) as typeof fetch; try { const res = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok" }), storeKitEnv, handlers({ verifyTransaction: async () => prod({ bundleId: "com.evil.app" }) }), ); assert.equal(res.status, 403); assert.equal(calls.length, 0); } finally { globalThis.fetch = orig; } }); test("handleRequest: entitled caller → forwards with a clean, injected header set", async () => { const orig = globalThis.fetch; let target = ""; let sent: Headers | null = null; globalThis.fetch = (async (u: unknown, init: RequestInit) => { target = String(u); sent = new Headers(init.headers); return new Response(JSON.stringify({ ok: true }), { status: 200 }); }) as typeof fetch; try { const res = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok", cookie: "session=abc", "anthropic-beta": "evil-flag", }), storeKitEnv, handlers(), ); assert.equal(res.status, 200); assert.equal(target, "https://api.anthropic.com/v1/messages"); const h = sent as unknown as Headers; assert.equal(h.get("x-api-key"), "sk-ant-PLACEHOLDER"); assert.equal(h.get("anthropic-version"), "2023-06-01"); assert.equal(h.get("content-type"), "application/json"); // Nothing the client sent leaks upstream. assert.equal(h.get("X-IAP-Transaction"), null); assert.equal(h.get("cookie"), null); assert.equal(h.get("anthropic-beta"), null); } finally { globalThis.fetch = orig; } }); // --- App Attest gate --- const appAttestEnv: Env = { ANTHROPIC_API_KEY: "sk-ant-PLACEHOLDER", APP_ATTEST_TEAM_ID: "ABCDE12345", APP_ATTEST_BUNDLE_ID: "com.example.app", }; test("handleRequest: App Attest only, missing assertion → 401", async () => { const res = await handleRequest( messagesRequest({}), appAttestEnv, handlers(), ); assert.equal(res.status, 401); }); test("handleRequest: App Attest only, valid assertion → forwards", async () => { const orig = globalThis.fetch; let target = ""; globalThis.fetch = (async (u: unknown) => { target = String(u); return new Response("{}", { status: 200 }); }) as typeof fetch; try { const res = await handleRequest( messagesRequest({ "X-App-Attest-Assertion": "ok", "X-App-Attest-Key-Id": "key-1", }), appAttestEnv, handlers(), ); assert.equal(res.status, 200); assert.equal(target, "https://api.anthropic.com/v1/messages"); } finally { globalThis.fetch = orig; } }); test("handleRequest: App Attest failure → 401, no upstream call", async () => { const calls: string[] = []; const orig = globalThis.fetch; globalThis.fetch = (async (u: unknown) => { calls.push(String(u)); return new Response("x"); }) as typeof fetch; try { const failAttest: AppAttestVerifier = async () => { throw new Error("bad assertion"); }; const res = await handleRequest( messagesRequest({ "X-App-Attest-Assertion": "bad", "X-App-Attest-Key-Id": "key-1", }), appAttestEnv, handlers({ verifyAppAttest: failAttest }), ); assert.equal(res.status, 401); assert.equal(calls.length, 0); } finally { globalThis.fetch = orig; } }); test("handleRequest: both gates configured, request must clear both", async () => { const orig = globalThis.fetch; globalThis.fetch = (async () => new Response("{}", { status: 200 })) as typeof fetch; try { const bothEnv: Env = { ...storeKitEnv, ...appAttestEnv }; // StoreKit passes, App Attest fails → rejected. const failAttest: AppAttestVerifier = async () => { throw new Error("bad assertion"); }; const rejected = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok", "X-App-Attest-Assertion": "bad", "X-App-Attest-Key-Id": "key-1", }), bothEnv, handlers({ verifyAppAttest: failAttest }), ); assert.equal(rejected.status, 401); // Both pass → forwarded. const ok = await handleRequest( messagesRequest({ "X-IAP-Transaction": "ok", "X-App-Attest-Assertion": "ok", "X-App-Attest-Key-Id": "key-1", }), bothEnv, handlers(), ); assert.equal(ok.status, 200); } finally { globalThis.fetch = orig; } });