headroom/tests/test_provider_codex_model_metadata.py
JD Davis e6243f65c9
refactor(providers): split proxy route adapters (#1934)
## Description
Refactors provider-specific proxy routing into provider-owned helper
modules so `headroom/providers/proxy_routes.py` primarily registers
routes and delegates behavior. This keeps Codex, OpenAI
Responses/images, model metadata, Vertex, Cloud Code, passthrough target
selection, and request path normalization logic testable outside the
route table.

Closes #

## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Documentation update
- [ ] Performance improvement
- [x] Code refactoring (no functional changes)

## Changes Made
- Extracted Codex routing helpers for headers, endpoint URLs, image
forwarding, response subpaths, and model metadata.
- Moved provider target selection, route specs, OpenAI Responses/images
helpers, Vertex runtime helpers, Cloud Code path normalization,
passthrough telemetry, and request scope normalization into focused
modules.
- Kept `proxy_routes.py` as route registration/delegation and preserved
current-main `/v1/messages` custom-base behavior.
- Added focused provider/proxy tests for the extracted modules and route
delegation behavior.

## Testing
- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output
```text
python -m pytest tests/test_package_init_lazy.py::test_codex_package_import_stays_runtime_only tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py::test_provider_passthrough_routes_forward_expected_targets tests/test_provider_proxy_routes.py::test_proxy_route_helpers_prefer_legacy_targets_and_gemini_passthrough tests/test_provider_proxy_routes.py::test_provider_specific_routes_delegate_to_expected_proxy_handlers tests/test_provider_proxy_routes.py::test_openai_response_websocket_aliases_delegate_to_openai_ws_handler tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_returns_502_on_http_failure tests/test_provider_proxy_routes.py::test_openai_response_subpath_passthrough_uses_openai_target tests/test_provider_proxy_routes.py::test_openai_response_subpath_aliases_and_chatgpt_auth_use_expected_targets tests/test_provider_proxy_routes.py::test_openai_image_routes_use_codex_backend_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_openai_image_codex_response_strips_stale_compression_headers tests/test_provider_proxy_routes.py::test_openai_image_edits_api_key_auth_falls_through_to_openai_passthrough tests/test_provider_proxy_routes.py::test_openai_image_edits_preserves_multipart_body_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_gemini_batch_embed_contents_passthrough_uses_gemini_target tests/test_provider_proxy_routes.py::test_v1_models_fetches_codex_registry_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_falls_back_to_synthetic_list_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_get_single_dynamic_under_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_still_forwards_under_non_chatgpt_auth tests/test_provider_proxy_routes.py::test_v1_models_routes_claude_code_gateway_discovery_to_anthropic tests/test_provider_proxy_routes.py::test_anthropic_model_metadata_strips_ansi_model_ids tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py::test_with_project_prefix_round_trips_through_split tests/test_vertex_claude_compression.py
============================ 102 passed in 34.83s =============================

python -m ruff check headroom/providers/cloudcode headroom/providers/codex headroom/providers/vertex headroom/providers/model_metadata.py headroom/providers/openai_images.py headroom/providers/openai_responses.py headroom/providers/proxy_targets.py headroom/providers/route_specs.py headroom/providers/proxy_routes.py headroom/proxy/handlers/openai.py headroom/proxy/passthrough.py headroom/proxy/request_scope.py headroom/proxy/project_context.py tests/test_package_init_lazy.py tests/test_provider_cloudcode_runtime.py tests/test_provider_codex_endpoints.py tests/test_provider_codex_headers.py tests/test_provider_codex_images.py tests/test_provider_codex_model_metadata.py tests/test_provider_codex_responses.py tests/test_provider_model_metadata.py tests/test_provider_openai_images.py tests/test_provider_openai_responses.py tests/test_provider_proxy_targets.py tests/test_provider_route_specs.py tests/test_provider_vertex_runtime.py tests/test_proxy_request_scope.py tests/test_provider_proxy_routes.py tests/test_custom_base_passthrough_telemetry.py tests/test_proxy_passthrough.py tests/test_proxy_google_cloudcode_route_aliases.py tests/test_proxy_project_savings.py tests/test_vertex_claude_compression.py
All checks passed!

python -m compileall -q headroom\providers\cloudcode headroom\providers\codex headroom\providers\vertex headroom\providers\model_metadata.py headroom\providers\openai_images.py headroom\providers\openai_responses.py headroom\providers\proxy_targets.py headroom\providers\route_specs.py headroom\providers\proxy_routes.py headroom\proxy\handlers\openai.py headroom\proxy\passthrough.py headroom\proxy\request_scope.py headroom\proxy\project_context.py
# no output; exited 0

git commit -m "refactor(providers): split proxy route adapters"
Sync plugin versions.....................................................Passed
check for merge conflicts................................................Passed
ruff.....................................................................Passed
ruff-format..............................................................Passed
mypy.....................................................................Passed
```

## Real Behavior Proof
- Environment: Windows PowerShell, Python 3.13.13, branch
`jd/provider-route-slices` based on `headroomlabs/main`.
- Exact command / steps: Ran the focused provider/proxy pytest suite,
focused ruff command, compileall over changed Python modules, and commit
hooks.
- Observed result: Provider/proxy route behavior tests passed; lint,
formatting, and mypy passed.
- Not tested: Full pytest suite, live upstream provider calls, and
manual end-to-end proxy traffic.

## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)
N/A

## Additional Notes
Documentation and CHANGELOG updates are N/A for this internal refactor.
The full pytest suite was not run; coverage here is focused on
provider/proxy routing behavior touched by this slice.

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-07-12 16:27:45 -05:00

162 lines
5.1 KiB
Python

import base64
import json
import httpx
import pytest
from headroom.providers.codex import (
CodexRoutingDecision as ExportedCodexRoutingDecision,
)
from headroom.providers.codex import (
resolve_codex_routing as exported_resolve_codex_routing,
)
from headroom.providers.codex.model_metadata import (
codex_model_registry_entry,
fetch_chatgpt_codex_model_ids,
normalize_codex_registry_headers,
synthetic_model_get_response,
synthetic_models_list_response,
)
from headroom.providers.codex.runtime import (
decode_openai_bearer_payload,
resolve_codex_routing,
resolve_codex_routing_headers,
)
def _jwt(payload: dict) -> str:
header = {"alg": "none", "typ": "JWT"}
def encode(part: dict) -> str:
raw = json.dumps(part, separators=(",", ":")).encode("utf-8")
return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
return f"{encode(header)}.{encode(payload)}."
def test_codex_runtime_resolves_chatgpt_account_from_openai_oauth_jwt() -> None:
token = _jwt(
{
"https://api.openai.com/auth": {
"chatgpt_account_id": "acct-from-jwt",
}
}
)
payload = decode_openai_bearer_payload({"authorization": f"Bearer {token}"})
headers, is_chatgpt_auth = resolve_codex_routing_headers({"authorization": f"Bearer {token}"})
assert payload == {
"https://api.openai.com/auth": {
"chatgpt_account_id": "acct-from-jwt",
}
}
assert is_chatgpt_auth is True
assert headers["ChatGPT-Account-ID"] == "acct-from-jwt"
def test_codex_runtime_exposes_named_routing_decision() -> None:
decision = resolve_codex_routing({"ChatGPT-Account-ID": "acct-explicit"})
assert decision.is_chatgpt_auth is True
assert decision.headers == {"ChatGPT-Account-ID": "acct-explicit"}
def test_codex_package_exports_routing_decision() -> None:
decision = exported_resolve_codex_routing({"ChatGPT-Account-ID": "acct-root"})
assert isinstance(decision, ExportedCodexRoutingDecision)
assert decision.is_chatgpt_auth is True
assert decision.headers == {"ChatGPT-Account-ID": "acct-root"}
def test_codex_runtime_ignores_malformed_oauth_jwt() -> None:
assert decode_openai_bearer_payload({"authorization": "Bearer header.***.sig"}) is None
headers, is_chatgpt_auth = resolve_codex_routing_headers(
{"authorization": "Bearer header.***.sig"}
)
assert is_chatgpt_auth is False
assert headers == {"authorization": "Bearer header.***.sig"}
def test_codex_registry_headers_normalize_account_and_accept_headers() -> None:
headers = normalize_codex_registry_headers(
{
"Host": "localhost:8787",
"authorization": "Bearer token",
"ChatGPT-Account-ID": "acct",
"Accept": "text/event-stream",
}
)
assert headers == {
"authorization": "Bearer token",
"chatgpt-account-id": "acct",
"accept": "application/json",
}
@pytest.mark.asyncio
async def test_codex_model_registry_fetch_returns_slugs() -> None:
class FakeClient:
def __init__(self) -> None:
self.calls: list[tuple[str, dict[str, str]]] = []
async def get(self, url, **kwargs): # type: ignore[no-untyped-def]
self.calls.append((url, dict(kwargs.get("headers", {}))))
return httpx.Response(
200,
json={"models": [{"slug": "gpt-5.5"}, {"slug": ""}, {"slug": "gpt-5.4"}]},
)
client = FakeClient()
model_ids = await fetch_chatgpt_codex_model_ids(
client,
{"authorization": "Bearer token", "chatgpt-account-id": "acct"},
"0.135.0",
)
assert model_ids == ("gpt-5.5", "gpt-5.4")
assert client.calls == [
(
"https://chatgpt.com/backend-api/codex/models?client_version=0.135.0",
{
"authorization": "Bearer token",
"chatgpt-account-id": "acct",
"accept": "application/json",
},
)
]
def test_codex_synthetic_model_metadata_responses() -> None:
list_payload = json.loads(synthetic_models_list_response().body)
known_payload = json.loads(synthetic_model_get_response("gpt-5.5").body)
unknown = synthetic_model_get_response("gpt-99-future")
assert list_payload["object"] == "list"
assert "gpt-5.5" in {entry["id"] for entry in list_payload["data"]}
assert known_payload == {
"id": "gpt-5.5",
"object": "model",
"created": 0,
"owned_by": "openai",
}
assert unknown.status_code == 404
def test_codex_model_registry_entry_preserves_upstream_fields_and_defaults() -> None:
entry = codex_model_registry_entry(
"gpt-5.3-codex-spark",
{"display_name": "Spark", "context_window": 12345},
)
assert entry["slug"] == "gpt-5.3-codex-spark"
assert entry["display_name"] == "Spark"
assert entry["context_window"] == 12345
assert entry["default_reasoning_level"] == "medium"
assert entry["supports_parallel_tool_calls"] is True
assert entry["supported_in_api"] is True