/** * End-to-end tests for the forward/reverse transform pipeline and `ProxyServer`, * driven against a local `node:http` stub standing in for api.anthropic.com plus * a temp credentials file — deterministic, no live Anthropic call. Also covers * the sanitize/reverse-map round-trip, billing fingerprint, and `resolveConfig`. */ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { computeBillingFingerprint } from "../src/proxy/billing-fingerprint.js"; import { DEFAULT_PROP_RENAMES, DEFAULT_REPLACEMENTS, DEFAULT_REVERSE_MAP, DEFAULT_TOOL_RENAMES, } from "../src/proxy/constants.js"; import { reverseMap } from "../src/proxy/reverse-map.js"; import { applyReplacements } from "../src/proxy/sanitize.js"; import { ProxyServer } from "../src/proxy/server.js"; import { applyQuotedRenames } from "../src/proxy/tool-rename.js"; import { AnthropicProxyService, resolveConfig } from "../src/services/proxy-service.js"; import { loadCredentials } from "../src/utils/credentials-loader.js"; // String literals chosen from the eliza-fingerprint dictionaries shipped // in v0.2.0. They must round-trip through the forward + reverse maps without // loss, regardless of whether they happen to be identity entries. const ROUNDTRIP_WORD = "native-reasoning"; const TOOL_KEY = "bash"; const TOOL_VAL = "Bash"; const SSE_KEY = "bash"; const SSE_VAL = "Bash"; const cleanup: Array<() => Promise | void> = []; afterEach(async () => { while (cleanup.length) { const fn = cleanup.pop(); try { await fn?.(); } catch { /* swallow */ } } }); function withEnv(overrides: Record, fn: () => T): T { const prev: Record = {}; for (const [k, v] of Object.entries(overrides)) { prev[k] = process.env[k]; if (v === undefined) delete process.env[k]; else process.env[k] = v; } const restore = () => { for (const [k, v] of Object.entries(prev)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; } }; try { const result = fn(); if (result && typeof (result as Promise).finally === "function") { return (result as Promise).finally(restore) as T; } restore(); return result; } catch (err) { restore(); throw err; } } describe("string sanitize roundtrip", () => { it("forward then reverse on a known paired key yields original input", () => { const original = `pre ${ROUNDTRIP_WORD} mid end`; const forward = applyReplacements(original, DEFAULT_REPLACEMENTS); const back = applyReplacements(forward, DEFAULT_REVERSE_MAP); expect(back).toBe(original); }); }); describe("tool name rename roundtrip", () => { it("forward quoted rename produces the renamed token", () => { const sample = JSON.stringify({ tool: TOOL_KEY, args: { v: 1 } }); const forward = applyQuotedRenames(sample, DEFAULT_TOOL_RENAMES); expect(forward).toBe(JSON.stringify({ tool: TOOL_VAL, args: { v: 1 } })); const back = reverseMap(forward, { toolRenames: DEFAULT_TOOL_RENAMES, propRenames: DEFAULT_PROP_RENAMES, reverseMap: DEFAULT_REVERSE_MAP, }); expect(back).toBe(sample); }); it("handles escaped-quoted tokens in SSE delta payloads", () => { const inner = JSON.stringify({ tool: SSE_VAL, text: "hi" }); const sseChunk = `data: {"type":"input_json_delta","partial_json":${JSON.stringify(inner)}}`; const back = reverseMap(sseChunk, { toolRenames: DEFAULT_TOOL_RENAMES, propRenames: DEFAULT_PROP_RENAMES, reverseMap: DEFAULT_REVERSE_MAP, }); expect(back).toContain(`\\"${SSE_KEY}\\"`); expect(back).not.toContain(`\\"${SSE_VAL}\\"`); }); }); describe("billing fingerprint", () => { it("hashes deterministically with known input", () => { const input = "hello world this is a sample message"; const a = computeBillingFingerprint(input); const b = computeBillingFingerprint(input); expect(a).toBe(b); expect(a).toHaveLength(3); expect(/^[0-9a-f]{3}$/.test(a)).toBe(true); const c = computeBillingFingerprint("a completely different prompt body for hashing"); expect(c).toHaveLength(3); expect([a, c].length).toBe(2); }); }); describe("AnthropicProxyService modes", () => { it("starts in off mode and does not listen", async () => { const service = await withEnv({ CLAUDE_MAX_PROXY_MODE: "off" }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("off"); expect(service.getProxyUrl()).toBeNull(); }); it("starts in shared mode and reports upstream", async () => { const upstream = createServer((req, res) => { if (req.url === "/health") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok" })); } else { res.writeHead(404); res.end(); } }); await new Promise((resolve) => upstream.listen(0, "127.0.0.1", resolve)); const port = (upstream.address() as { port: number }).port; cleanup.push(() => new Promise((resolve) => upstream.close(() => resolve()))); const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "shared", CLAUDE_MAX_PROXY_UPSTREAM: `http://127.0.0.1:${port}`, }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("shared"); expect(service.getProxyUrl()).toBe(`http://127.0.0.1:${port}`); const status = await service.getStatus(); expect(status.upstream?.reachable).toBe(true); expect(status.upstream?.status).toBe(200); }); it("starts in inline mode and listens (when credentials present, else falls back to off)", async () => { const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "inline", CLAUDE_MAX_PROXY_PORT: "0", CLAUDE_CODE_OAUTH_TOKEN: "test-oauth-token-not-real", }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); if (service.getEffectiveMode() === "inline") { expect(service.getProxyUrl()).toMatch(/^http:\/\/127\.0\.0\.1:/); } else { expect(service.getEffectiveMode()).toBe("off"); } }); it("rejects non-loopback inline binds without proxy auth", async () => { const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "inline", CLAUDE_MAX_PROXY_BIND_HOST: "0.0.0.0", CLAUDE_MAX_PROXY_AUTH_TOKEN: undefined, CLAUDE_CODE_OAUTH_TOKEN: "test-oauth-token-not-real", }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("off"); expect(service.getStartError()).toMatch(/CLAUDE_MAX_PROXY_AUTH_TOKEN/); }); it("rejects plain-http shared upstreams unless they are private", async () => { const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "shared", CLAUDE_MAX_PROXY_UPSTREAM: "http://example.com/proxy", }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("off"); expect(service.getStartError()).toMatch(/https/); }); it("treats invalid proxy mode as off instead of silently starting inline", async () => { const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "sharedd", CLAUDE_CODE_OAUTH_TOKEN: "test-oauth-token-not-real", }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("off"); expect(service.getStartError()).toMatch(/Invalid CLAUDE_MAX_PROXY_MODE/); }); it("does not poison ANTHROPIC_BASE_URL when inline startup falls back to off", async () => { const previous = process.env.ANTHROPIC_BASE_URL; process.env.ANTHROPIC_BASE_URL = "auto"; const blocker = createServer((_req, res) => { res.writeHead(204); res.end(); }); await new Promise((resolve) => blocker.listen(0, "127.0.0.1", resolve)); const port = (blocker.address() as { port: number }).port; cleanup.push(() => new Promise((resolve) => blocker.close(() => resolve()))); try { const service = await withEnv( { CLAUDE_MAX_PROXY_MODE: "inline", CLAUDE_MAX_PROXY_PORT: String(port), CLAUDE_CODE_OAUTH_TOKEN: "test-oauth-token-not-real", }, () => AnthropicProxyService.start({} as unknown as never) ); cleanup.push(() => service.stop()); expect(service.getEffectiveMode()).toBe("off"); expect(service.getStartError()).toMatch(/EADDRINUSE/); expect(process.env.ANTHROPIC_BASE_URL).toBe("auto"); } finally { if (previous === undefined) delete process.env.ANTHROPIC_BASE_URL; else process.env.ANTHROPIC_BASE_URL = previous; } }); }); describe("ProxyServer auth and URL contracts", () => { it("publishes the assigned port when configured with port 0", async () => { const server = new ProxyServer({ port: 0, bindHost: "127.0.0.1", envToken: "test-oauth-token-not-real", }); await server.start(); cleanup.push(() => server.stop()); const url = server.getUrl(); expect(url).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/); expect(url).not.toBe("http://127.0.0.1:0"); const response = await fetch(`${url}/health`); expect(response.status).toBe(200); }); it("requires proxy auth for /health when auth token is configured", async () => { const server = new ProxyServer({ port: 0, bindHost: "127.0.0.1", envToken: "test-oauth-token-not-real", proxyAuthToken: "proxy-token", }); await server.start(); cleanup.push(() => server.stop()); const url = server.getUrl(); await expect(fetch(`${url}/health`)).resolves.toMatchObject({ status: 401, }); await expect( fetch(`${url}/health`, { headers: { Authorization: "Bearer proxy-token" }, }) ).resolves.toMatchObject({ status: 200, }); }); }); describe("credentials loader", () => { it("returns error (not throw) when no credentials file exists and no env token set", () => { const result = withEnv({ CLAUDE_CODE_OAUTH_TOKEN: undefined }, () => loadCredentials({ credentialsPath: "/nonexistent/path/that/does/not/exist.json", }) ); if (result.creds === null) { expect(result.error).toBeDefined(); expect(result.error).toMatch(/not found|missing|failed/i); } else { expect(result.creds.accessToken).toBeDefined(); } }); it("uses CLAUDE_CODE_OAUTH_TOKEN env when provided", () => { const result = loadCredentials({ envToken: "env-token-abc" }); expect(result.creds).not.toBeNull(); expect(result.creds?.accessToken).toBe("env-token-abc"); expect(result.creds?.source).toBe("env"); }); }); describe("config resolver", () => { it("defaults to inline mode and port 18801", () => { const cfg = withEnv( { CLAUDE_MAX_PROXY_MODE: undefined, CLAUDE_MAX_PROXY_PORT: undefined, CLAUDE_MAX_PROXY_BIND_HOST: undefined, }, () => resolveConfig() ); expect(cfg.mode).toBe("inline"); expect(cfg.port).toBe(18801); expect(cfg.bindHost).toBe("127.0.0.1"); }); it("loads fingerprint overrides from CLAUDE_MAX_PROXY_CONFIG_PATH", () => { const dir = mkdtempSync(join(tmpdir(), "anthropic-proxy-config-")); cleanup.push(() => rmSync(dir, { recursive: true, force: true })); const configPath = join(dir, "config.json"); writeFileSync( configPath, JSON.stringify({ replacements: [["framework-A", "framework-B"]], toolRenames: [["custom_tool", "Read"]], propRenames: [["customProp", "path"]], reverseMap: [["framework-B", "framework-A"]], systemPromptStrip: { start: "FRAMEWORK_START", end: "FRAMEWORK_END", paraphrase: '{"type":"text","text":"Short framework policy."}', minStripLen: 10, }, }) ); const cfg = withEnv( { CLAUDE_MAX_PROXY_CONFIG_PATH: configPath, CLAUDE_MAX_PROXY_MODE: "off", }, () => resolveConfig() ); expect(cfg.configError).toBeUndefined(); expect(cfg.configPath).toBe(configPath); expect(cfg.fingerprintConfig?.replacements).toEqual([["framework-A", "framework-B"]]); expect(cfg.fingerprintConfig?.systemPromptStrip).toMatchObject({ start: "FRAMEWORK_START", end: "FRAMEWORK_END", minStripLen: 10, }); }); it("fails closed when an explicit fingerprint config path is missing", () => { const cfg = withEnv( { CLAUDE_MAX_PROXY_CONFIG_PATH: "/missing/anthropic-proxy-config.json", CLAUDE_MAX_PROXY_MODE: "inline", }, () => resolveConfig() ); expect(cfg.mode).toBe("inline"); expect(cfg.configError).toContain("CLAUDE_MAX_PROXY_CONFIG_PATH not found"); }); });