test(openclaw): cover branch routing paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
JerrettDavis 2026-04-08 22:44:25 -05:00
parent 2f05705043
commit 37f32a8922
5 changed files with 397 additions and 299 deletions

View file

@ -355,6 +355,11 @@ def start_openclaw_gateway(env: dict[str, str], cwd: Path) -> subprocess.Popen[s
)
def stop_openclaw_gateway(env: dict[str, str], cwd: Path) -> None:
log("Stopping OpenClaw gateway after e2e verification")
run(["openclaw", "gateway", "stop"], env=env, cwd=cwd, timeout=60)
def verify_installs() -> None:
log("Verifying installed packages and binaries")
for tool in ("headroom", "codex", "aider", "openclaw"):
@ -617,6 +622,7 @@ def verify_openclaw_wrap(
finally:
if gateway_proc is not None:
stop_process(gateway_proc)
stop_openclaw_gateway(base_env, project_dir)
def main() -> None:

View file

@ -1132,6 +1132,24 @@ def openclaw(
elif not local_source_mode and skip_build:
click.echo(" Skipping build: npm install mode does not build local source.")
effective_python_path = python_path
if effective_python_path is None and not no_auto_start and sys.executable:
effective_python_path = sys.executable
existing_entry = _read_openclaw_config_value(openclaw_bin, "plugins.entries.headroom")
entry = _build_openclaw_plugin_entry(
existing_entry=existing_entry,
proxy_port=proxy_port,
startup_timeout_ms=startup_timeout_ms,
python_path=effective_python_path,
no_auto_start=no_auto_start,
gateway_provider_ids=gateway_provider_ids,
enabled=True,
)
click.echo(" Writing plugin configuration...")
_write_openclaw_plugin_entry(openclaw_bin, entry)
install_cmd = [
openclaw_bin,
"plugins",
@ -1184,19 +1202,6 @@ def openclaw(
elif verbose and install_result.stdout.strip():
click.echo(install_result.stdout.strip())
existing_entry = _read_openclaw_config_value(openclaw_bin, "plugins.entries.headroom")
entry = _build_openclaw_plugin_entry(
existing_entry=existing_entry,
proxy_port=proxy_port,
startup_timeout_ms=startup_timeout_ms,
python_path=python_path,
no_auto_start=no_auto_start,
gateway_provider_ids=gateway_provider_ids,
enabled=True,
)
click.echo(" Writing plugin configuration...")
_write_openclaw_plugin_entry(openclaw_bin, entry)
_set_openclaw_context_engine_slot(openclaw_bin, "headroom")
_run_checked(
[openclaw_bin, "config", "validate"],

View file

@ -1,300 +1,101 @@
/**
* Integration tests for HeadroomContextEngine.
*
* Tests the full flow: proxy management, AgentMessage conversion,
* compression via proxy, and round-trip back to AgentMessage.
*
* Requires: Python 3 + headroom-ai[proxy] installed
* Run: HEADROOM_INTEGRATION=1 npx vitest run test/engine.test.ts
*/
import { describe, it, expect, beforeAll, afterAll, vi, afterEach } from "vitest";
import { HeadroomContextEngine } from "../src/engine.js";
import { agentToOpenAI, openAIToAgent } from "../src/convert.js";
import { ProxyManager } from "../src/proxy-manager.js";
import { afterEach, describe, expect, it, vi } from "vitest";
const RUN = process.env.HEADROOM_INTEGRATION === "1";
const PROXY_URL = process.env.HEADROOM_PROXY_URL ?? "http://127.0.0.1:8787";
const mocked = vi.hoisted(() => ({
start: vi.fn(async () => "http://127.0.0.1:8787"),
stop: vi.fn(async () => undefined),
logger: {
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
},
}));
vi.mock("headroom-ai", () => ({
compress: vi.fn(),
}));
vi.mock("../src/proxy-manager.js", () => ({
ProxyManager: class {
start = mocked.start;
stop = mocked.stop;
},
defaultLogger: mocked.logger,
}));
import { HeadroomContextEngine } from "../src/engine.js";
afterEach(() => {
vi.restoreAllMocks();
mocked.start.mockReset();
mocked.start.mockResolvedValue("http://127.0.0.1:8787");
mocked.stop.mockClear();
mocked.logger.debug.mockClear();
mocked.logger.error.mockClear();
mocked.logger.info.mockClear();
mocked.logger.warn.mockClear();
});
// Proxy probing and ProxyManager.start tests live in proxy-manager.test.ts
describe("AgentMessage conversion", () => {
it("converts user message", () => {
const agent = [{ role: "user", content: "hello", timestamp: Date.now() }];
const openai = agentToOpenAI(agent);
expect(openai).toHaveLength(1);
expect(openai[0]).toMatchObject({ role: "user", content: "hello" });
});
it("converts assistant with tool_use blocks", () => {
const agent = [
{
role: "assistant",
content: [
{ type: "text", text: "Let me search" },
{ type: "tool_use", id: "tu_1", name: "search", input: { q: "test" } },
],
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(agent);
expect(openai[0].role).toBe("assistant");
expect(openai[0].content).toBe("Let me search");
expect(openai[0].tool_calls).toHaveLength(1);
expect(openai[0].tool_calls![0].function.name).toBe("search");
});
it("converts assistant with toolCall blocks", () => {
const agent = [
{
role: "assistant",
content: [
{ type: "text", text: "Let me search" },
{ type: "toolCall", id: "call_1|fc_1", name: "search", arguments: { q: "test" } },
],
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(agent);
expect(openai[0].role).toBe("assistant");
expect(openai[0].content).toBe("Let me search");
expect(openai[0].tool_calls).toHaveLength(1);
expect(openai[0].tool_calls![0].id).toBe("call_1|fc_1");
expect(openai[0].tool_calls![0].function.name).toBe("search");
});
it("converts toolResult message", () => {
const agent = [
{
role: "toolResult",
content: '{"results": [1, 2, 3]}',
tool_use_id: "tu_1",
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(agent);
expect(openai[0].role).toBe("tool");
expect(openai[0].content).toBe('{"results": [1, 2, 3]}');
expect(openai[0].tool_call_id).toBe("tu_1");
});
it("round-trips user message", () => {
const original = [{ role: "user", content: "hello", timestamp: Date.now() }];
const openai = agentToOpenAI(original);
const back = openAIToAgent(openai);
expect(back[0].role).toBe("user");
expect(back[0].content).toBe("hello");
});
it("round-trips assistant text-only (content always array)", () => {
const original = [
{
role: "assistant",
content: [{ type: "text", text: "Hello there!" }],
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(original);
const back = openAIToAgent(openai);
expect(back[0].role).toBe("assistant");
// OpenClaw requires content to ALWAYS be an array for assistant messages
const content = back[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content[0]).toEqual({ type: "text", text: "Hello there!" });
});
it("round-trips assistant with tool calls", () => {
const original = [
{
role: "assistant",
content: [
{ type: "text", text: "Searching..." },
{ type: "toolCall", id: "call_1|fc_1", name: "search", arguments: { q: "test" } },
],
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(original);
const back = openAIToAgent(openai);
expect(back[0].role).toBe("assistant");
const content = back[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content).toContainEqual(expect.objectContaining({ type: "text", text: "Searching..." }));
expect(content).toContainEqual(
expect.objectContaining({ type: "toolCall", id: "call_1|fc_1", name: "search" }),
);
});
it("round-trips toolResult", () => {
const original = [
{
role: "toolResult",
content: '{"data": true}',
tool_use_id: "tu_1",
timestamp: Date.now(),
},
];
const openai = agentToOpenAI(original);
const back = openAIToAgent(openai);
expect(back[0].role).toBe("toolResult");
expect(back[0].content).toEqual([{ type: "text", text: '{"data": true}' }]);
expect(back[0].tool_use_id).toBe("tu_1");
});
});
describe("HeadroomContextEngine startup behavior", () => {
it("bootstrap schedules proxy startup without blocking on it", async () => {
const start = vi.fn(
() => new Promise<string>((resolve) => setTimeout(() => resolve("http://127.0.0.1:8787"), 50)),
);
vi.spyOn(ProxyManager.prototype, "start").mockImplementation(start);
describe("HeadroomContextEngine proxy startup helpers", () => {
it("bootstraps by scheduling proxy startup when enabled", async () => {
const engine = new HeadroomContextEngine();
const result = await engine.bootstrap({
sessionId: "test-session",
sessionFile: "/tmp/test-session.jsonl",
});
expect(result).toEqual({ bootstrapped: true, reason: "proxy startup scheduled" });
expect(start).toHaveBeenCalledTimes(1);
expect(engine.getProxyUrl()).toBeNull();
await expect(
engine.bootstrap({
sessionId: "session-1",
sessionFile: "session.jsonl",
}),
).resolves.toEqual({
bootstrapped: true,
reason: "proxy startup scheduled",
});
expect(mocked.start).toHaveBeenCalledTimes(1);
});
it("assemble returns original messages while proxy startup is still pending", async () => {
const start = vi.fn(
() => new Promise<string>((resolve) => setTimeout(() => resolve("http://127.0.0.1:8787"), 50)),
);
vi.spyOn(ProxyManager.prototype, "start").mockImplementation(start);
it("removes unsubscribed proxy listeners before notifying readiness", async () => {
const engine = new HeadroomContextEngine();
const messages = [{ role: "user", content: "hello", timestamp: Date.now() }];
const first = vi.fn();
const second = vi.fn();
const result = await engine.assemble({
sessionId: "test-session",
const unsubscribeFirst = engine.onProxyReady(first);
engine.onProxyReady(second);
unsubscribeFirst();
engine.ensureProxyStarted();
await engine.ensureProxyUrl();
expect(first).not.toHaveBeenCalled();
expect(second).toHaveBeenCalledWith("http://127.0.0.1:8787");
});
it("returns the existing proxy URL without starting again", async () => {
const engine = new HeadroomContextEngine();
(engine as { proxyUrl: string | null }).proxyUrl = "http://127.0.0.1:8787";
await expect(engine.ensureProxyUrl()).resolves.toBe("http://127.0.0.1:8787");
expect(mocked.start).not.toHaveBeenCalled();
});
it("throws when proxy startup is disabled", async () => {
const engine = new HeadroomContextEngine({ enabled: false });
await expect(engine.ensureProxyUrl()).rejects.toThrow("Headroom proxy startup is disabled");
expect(mocked.start).not.toHaveBeenCalled();
});
it("schedules startup and returns original messages when assembling before proxy readiness", async () => {
const engine = new HeadroomContextEngine();
const messages = [{ role: "user", content: "hello" }];
await expect(
engine.assemble({
sessionId: "session-1",
messages,
}),
).resolves.toEqual({
messages,
model: "claude-sonnet-4-5",
estimatedTokens: 0,
});
expect(result).toEqual({ messages, estimatedTokens: 0 });
expect(start).toHaveBeenCalledTimes(1);
expect(mocked.start).toHaveBeenCalledTimes(1);
});
});
if (RUN) {
describe("ProxyManager", () => {
it("connects to configured proxy URL", { timeout: 30000 }, async () => {
const manager = new ProxyManager({ proxyUrl: PROXY_URL });
try {
const url = await manager.start();
expect(url).toMatch(/^http:\/\/(127\.0\.0\.1|localhost):\d+$/);
// Verify health
const resp = await fetch(`${url}/health`);
expect(resp.ok).toBe(true);
} finally {
await manager.stop();
}
});
});
describe("HeadroomContextEngine", () => {
let engine: HeadroomContextEngine;
beforeAll(async () => {
engine = new HeadroomContextEngine({ proxyUrl: PROXY_URL });
await engine.bootstrap({
sessionId: "test-session",
sessionFile: "/tmp/test-session.jsonl",
});
}, 30000);
afterAll(async () => {
await engine.dispose();
});
it("assemble() compresses tool outputs", { timeout: 15000 }, async () => {
// Simulate an OpenClaw agent conversation with large tool result
const serverData = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `server-${i + 1}`,
status: i % 15 === 0 ? "critical" : i % 5 === 0 ? "warning" : "healthy",
cpu: Math.round(Math.random() * 100),
memory: Math.round(Math.random() * 100),
region: ["us-east-1", "eu-west-1", "ap-southeast-1"][i % 3],
description: `Production server ${i + 1} running service-${["auth", "payment", "user", "api"][i % 4]}`,
lastAlert: i % 15 === 0 ? `Disk usage at ${90 + (i % 10)}%` : null,
}));
const messages = [
{ role: "user", content: "Check the fleet status", timestamp: Date.now() },
{
role: "assistant",
content: [
{ type: "tool_use", id: "tu_fleet", name: "getFleetStatus", input: {} },
],
timestamp: Date.now(),
},
{
role: "toolResult",
content: JSON.stringify(serverData),
tool_use_id: "tu_fleet",
timestamp: Date.now(),
},
{ role: "user", content: "Which servers are critical?", timestamp: Date.now() },
];
const result = await engine.assemble({
sessionId: "test-session",
messages,
model: "claude-sonnet-4-5",
});
console.log(
` assemble(): estimatedTokens=${result.estimatedTokens}, ` +
`systemPrompt=${result.systemPromptAddition ? "yes" : "no"}`,
);
// Messages should be returned (compressed or not)
expect(result.messages.length).toBeGreaterThan(0);
// First and last messages should still be user messages
expect(result.messages[0].role).toBe("user");
expect(result.messages[result.messages.length - 1].role).toBe("user");
});
it("assemble() preserves small conversations", { timeout: 15000 }, async () => {
const messages = [
{ role: "user", content: "Hello", timestamp: Date.now() },
{ role: "assistant", content: "Hi there!", timestamp: Date.now() },
];
const result = await engine.assemble({
sessionId: "test-session",
messages,
});
expect(result.messages).toHaveLength(2);
expect(result.messages[0].content).toBe("Hello");
expect(result.messages[1].content).toBe("Hi there!");
});
it("compact() returns success (compression handled in assemble)", async () => {
const result = await engine.compact({
sessionId: "test-session",
sessionFile: "/tmp/test.jsonl",
});
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
});
it("getStats() returns compression statistics", () => {
const stats = engine.getStats();
expect(stats).toHaveProperty("totalCompressions");
expect(stats).toHaveProperty("totalTokensSaved");
expect(stats.totalCompressions).toBeGreaterThanOrEqual(0);
});
});
}

View file

@ -67,6 +67,18 @@ def test_wrap_openclaw_default_installs_from_npm_and_restarts(runner: CliRunner)
assert ["openclaw", "gateway", "restart"] in cmds
assert ["openclaw", "plugins", "inspect", "headroom"] in cmds
config_set_index = next(
i
for i, cmd in enumerate(cmds)
if cmd[:4] == ["openclaw", "config", "set", "plugins.entries.headroom"]
)
install_index = next(
i
for i, cmd in enumerate(cmds)
if cmd[:4] == ["openclaw", "plugins", "install", "--dangerously-force-unsafe-install"]
)
assert config_set_index < install_index
# Verify plugin install in npm mode does not set cwd
install_call = next(
c
@ -91,6 +103,7 @@ def test_wrap_openclaw_default_installs_from_npm_and_restarts(runner: CliRunner)
assert payload["config"]["autoStart"] is True
assert payload["config"]["startupTimeoutMs"] == 20000
assert payload["config"]["gatewayProviderIds"] == ["openai-codex"]
assert payload["config"]["pythonPath"] == wrap_cli.sys.executable
def test_wrap_openclaw_skip_build_and_no_restart(runner: CliRunner, plugin_dir: Path) -> None:
@ -330,6 +343,86 @@ def test_wrap_openclaw_accepts_repeatable_gateway_provider_ids(runner: CliRunner
assert payload["config"]["gatewayProviderIds"] == ["openai-codex", "anthropic"]
def test_normalize_openclaw_gateway_provider_ids_dedupes_blanks_and_defaults() -> None:
assert wrap_cli._normalize_openclaw_gateway_provider_ids(
(" openai-codex ", "", "anthropic", "openai-codex", " ")
) == ["openai-codex", "anthropic"]
assert wrap_cli._normalize_openclaw_gateway_provider_ids(None) == ["openai-codex"]
def test_read_openclaw_config_value_handles_missing_and_raw_strings() -> None:
missing = MagicMock(returncode=1, stdout="", stderr="missing")
raw_string = MagicMock(returncode=0, stdout="plain-text-value\n", stderr="")
with patch("headroom.cli.wrap.subprocess.run", side_effect=[missing, raw_string]):
assert wrap_cli._read_openclaw_config_value("openclaw", "plugins.entries.headroom") is None
assert (
wrap_cli._read_openclaw_config_value(
"openclaw", "plugins.entries.headroom.config.pythonPath"
)
== "plain-text-value"
)
def test_build_openclaw_plugin_entry_sets_and_clears_python_path() -> None:
with_python = wrap_cli._build_openclaw_plugin_entry(
existing_entry={"config": {"customFlag": True}},
proxy_port=8787,
startup_timeout_ms=20000,
python_path="C:\\Python312\\python.exe",
no_auto_start=False,
gateway_provider_ids=("openai-codex",),
enabled=True,
)
assert with_python["config"]["pythonPath"] == "C:\\Python312\\python.exe"
without_python = wrap_cli._build_openclaw_plugin_entry(
existing_entry={"config": {"pythonPath": "C:\\Old\\python.exe", "customFlag": True}},
proxy_port=8787,
startup_timeout_ms=20000,
python_path=None,
no_auto_start=False,
gateway_provider_ids=("openai-codex",),
enabled=True,
)
assert "pythonPath" not in without_python["config"]
assert without_python["config"]["customFlag"] is True
def test_wrap_openclaw_no_auto_start_does_not_default_python_path(
runner: CliRunner, plugin_dir: Path
) -> None:
calls: list[dict] = []
def which(name: str) -> str | None:
return {"openclaw": "openclaw", "npm": "npm"}.get(name)
with patch("headroom.cli.wrap.shutil.which", side_effect=which):
with patch("headroom.cli.wrap.subprocess.run", side_effect=_make_successful_run(calls)):
result = runner.invoke(
main,
[
"wrap",
"openclaw",
"--plugin-path",
str(plugin_dir),
"--skip-build",
"--no-auto-start",
"--no-restart",
],
)
assert result.exit_code == 0, result.output
set_entry = next(
c
for c in calls
if c["cmd"][:4] == ["openclaw", "config", "set", "plugins.entries.headroom"]
)
payload = json.loads(set_entry["cmd"][4])
assert payload["config"]["autoStart"] is False
assert "pythonPath" not in payload["config"]
def test_wrap_openclaw_fails_for_npm_mode_hook_pack_bug_without_local_fallback(
runner: CliRunner,
) -> None:
@ -571,3 +664,37 @@ def test_unwrap_openclaw_no_restart_skips_gateway_restart(runner: CliRunner) ->
assert result.exit_code == 0, result.output
assert ["openclaw", "gateway", "restart"] not in [c["cmd"] for c in calls]
def test_unwrap_openclaw_fails_when_openclaw_missing(runner: CliRunner) -> None:
with patch("headroom.cli.wrap.shutil.which", return_value=None):
result = runner.invoke(main, ["unwrap", "openclaw"])
assert result.exit_code != 0
assert "'openclaw' not found in PATH" in result.output
def test_unwrap_openclaw_verbose_prints_gateway_and_inspect_output(runner: CliRunner) -> None:
def which(name: str) -> str | None:
return {"openclaw": "openclaw"}.get(name)
def run(cmd, **kwargs): # noqa: ANN001
if cmd[:4] == ["openclaw", "config", "get", "plugins.entries.headroom"]:
return MagicMock(
returncode=0,
stdout=json.dumps({"enabled": True, "config": {"proxyPort": 8787}}),
stderr="",
)
if cmd[:3] == ["openclaw", "gateway", "restart"]:
return MagicMock(returncode=0, stdout="gateway-restarted", stderr="")
if cmd[:3] == ["openclaw", "plugins", "inspect"]:
return MagicMock(returncode=0, stdout="inspect-disabled", stderr="")
return MagicMock(returncode=0, stdout="", stderr="")
with patch("headroom.cli.wrap.shutil.which", side_effect=which):
with patch("headroom.cli.wrap.subprocess.run", side_effect=run):
result = runner.invoke(main, ["unwrap", "openclaw", "--verbose"])
assert result.exit_code == 0, result.output
assert "gateway-restarted" in result.output
assert "inspect-disabled" in result.output

View file

@ -1,7 +1,17 @@
import base64
import json
import sys
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from headroom.proxy.handlers.openai import _resolve_codex_routing_headers
import anyio
import pytest
from fastapi import Request
from headroom.proxy.handlers.openai import (
OpenAIHandlerMixin,
_resolve_codex_routing_headers,
)
def _jwt(payload: dict) -> str:
@ -56,3 +66,152 @@ def test_resolve_codex_routing_leaves_regular_openai_bearer_tokens_unchanged():
assert is_chatgpt is False
assert "ChatGPT-Account-ID" not in headers
def test_resolve_codex_routing_returns_none_without_bearer_auth():
headers, is_chatgpt = _resolve_codex_routing_headers({})
assert is_chatgpt is False
assert headers == {}
def test_resolve_codex_routing_ignores_non_jwt_bearer_tokens():
headers, is_chatgpt = _resolve_codex_routing_headers(
{
"authorization": "Bearer not-a-jwt",
}
)
assert is_chatgpt is False
assert headers["authorization"] == "Bearer not-a-jwt"
def test_resolve_codex_routing_ignores_invalid_jwt_payloads():
invalid_payload = base64.urlsafe_b64encode(b"not-json").decode("ascii").rstrip("=")
token = f"test-header.{invalid_payload}.signature"
headers, is_chatgpt = _resolve_codex_routing_headers(
{
"authorization": f"Bearer {token}",
}
)
assert is_chatgpt is False
assert headers["authorization"] == f"Bearer {token}"
class _DummyMetrics:
async def record_request(self, **kwargs): # noqa: ANN003
return None
async def record_failed(self):
return None
class _DummyTokenizer:
def count_messages(self, messages):
return len(messages)
class _ResponseStub:
def json(self):
return {"usage": {"input_tokens": 2, "output_tokens": 1}}
class _DummyOpenAIHandler(OpenAIHandlerMixin):
OPENAI_API_URL = "https://api.openai.com"
def __init__(self) -> None:
self.rate_limiter = None
self.metrics = _DummyMetrics()
self.config = SimpleNamespace(optimize=False)
self.usage_reporter = None
self.openai_provider = SimpleNamespace()
self.anthropic_backend = None
self.cost_tracker = None
self.captured_request: tuple[str, str, dict, dict] | None = None
async def _next_request_id(self) -> str:
return "req-1"
def _extract_tags(self, headers: dict[str, str]) -> list[str]:
return []
async def _retry_request(self, method: str, url: str, headers: dict, body: dict):
self.captured_request = (method, url, headers, body)
return _ResponseStub()
def _build_request(body: dict, headers: dict[str, str]) -> Request:
payload = json.dumps(body).encode("utf-8")
async def receive():
return {"type": "http.request", "body": payload, "more_body": False}
scope = {
"type": "http",
"asgi": {"version": "3.0"},
"http_version": "1.1",
"method": "POST",
"scheme": "https",
"path": "/v1/responses",
"raw_path": b"/v1/responses",
"query_string": b"",
"headers": [
(key.lower().encode("utf-8"), value.encode("utf-8")) for key, value in headers.items()
],
"client": ("127.0.0.1", 12345),
"server": ("testserver", 443),
}
return Request(scope, receive)
def test_handle_openai_responses_routes_chatgpt_auth_to_backend_api(monkeypatch):
token = _jwt(
{
"https://api.openai.com/auth": {
"chatgpt_account_id": "acct-from-jwt",
}
}
)
request = _build_request(
{"model": "gpt-5.4", "input": "hello"},
{"Authorization": f"Bearer {token}"},
)
handler = _DummyOpenAIHandler()
monkeypatch.setattr("headroom.tokenizers.get_tokenizer", lambda model: _DummyTokenizer())
anyio.run(handler.handle_openai_responses, request)
assert handler.captured_request is not None
method, url, headers, body = handler.captured_request
assert method == "POST"
assert url == "https://chatgpt.com/backend-api/codex/responses"
assert headers["ChatGPT-Account-ID"] == "acct-from-jwt"
assert body["input"] == "hello"
class _DummyWebSocket:
def __init__(self, headers: dict[str, str]):
self.headers = headers
self.accepted_subprotocol = None
async def accept(self, subprotocol=None):
self.accepted_subprotocol = subprotocol
def test_handle_openai_responses_ws_resolves_codex_routing_headers():
class SentinelError(RuntimeError):
pass
handler = _DummyOpenAIHandler()
websocket = _DummyWebSocket({"authorization": "Bearer token"})
with patch.dict(sys.modules, {"websockets": MagicMock()}):
with patch(
"headroom.proxy.handlers.openai._resolve_codex_routing_headers",
side_effect=SentinelError("resolved"),
):
with pytest.raises(SentinelError, match="resolved"):
anyio.run(handler.handle_openai_responses_ws, websocket)