Files
zeekayandHanzo Dev fad96e267f no hogs: the root config, the tooling, and the receiver baseline
.stamphog/ was a config directory at the repo root — policy.yml,
review-guidance.md — read by the PR review agent. It is .stamp/ now, and the 34
files that name it follow: the workflows, tools/pr-approval-agent, tach.toml,
the sandbox Dockerfile, bin/debrand.

One behaviour change worth naming rather than burying: the agent finds its own
previous PR comment by an HTML marker, `<!-- stamphog:v1 ... -->`, and that
marker is now `<!-- stamp:v1 ... -->`. Open PRs will get one new comment instead
of an edit, once, and then match again.

insights/test/setup_receivers_baseline.txt records registered signal receivers
by module path, so it names products.review.backend now.

stamphog: zero occurrences anywhere in the tree. review_hog: only inside
migration files, whose names are their identity in django_migrations.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:21:41 -07:00

128 lines
4.7 KiB
Python

"""Tests for ai-gateway routing in gateway.py."""
import pytest
from gateway import AI_PRODUCT, analytics_extra_properties, gateway_env, resolve_gateway_config
@pytest.fixture(autouse=True)
def _clear_gateway_env(monkeypatch):
monkeypatch.delenv("AI_GATEWAY_URL", raising=False)
monkeypatch.delenv("AI_GATEWAY_API_KEY", raising=False)
def test_unset_returns_none_direct_path(monkeypatch):
assert resolve_gateway_config() is None
@pytest.mark.parametrize(
"url,api_key",
[
pytest.param("https://gateway.us.hanzo.ai/v1", "", id="url-only"),
pytest.param("", "sk-secret", id="key-only"),
],
)
def test_half_applied_config_falls_back(monkeypatch, url, api_key):
monkeypatch.setenv("AI_GATEWAY_URL", url)
monkeypatch.setenv("AI_GATEWAY_API_KEY", api_key)
assert resolve_gateway_config() is None
def test_url_without_v1_falls_back(monkeypatch):
monkeypatch.setenv("AI_GATEWAY_URL", "https://gateway.us.hanzo.ai")
monkeypatch.setenv("AI_GATEWAY_API_KEY", "sk-secret")
assert resolve_gateway_config() is None
def test_schemeless_url_falls_back(monkeypatch):
# Schemeless parses all into .path, so the /v1 check alone would pass.
monkeypatch.setenv("AI_GATEWAY_URL", "gateway.us.hanzo.ai/v1")
monkeypatch.setenv("AI_GATEWAY_API_KEY", "sk-secret")
assert resolve_gateway_config() is None
@pytest.mark.parametrize(
"bad_url",
[
pytest.param("https://gateway.us.hanzo.ai/v1?x=y", id="query"),
pytest.param("https://gateway.us.hanzo.ai/v1#frag", id="fragment"),
],
)
def test_url_with_query_or_fragment_falls_back(monkeypatch, bad_url):
# A query/fragment survives /v1 stripping and would corrupt the base URL.
monkeypatch.setenv("AI_GATEWAY_URL", bad_url)
monkeypatch.setenv("AI_GATEWAY_API_KEY", "sk-secret")
assert resolve_gateway_config() is None
@pytest.mark.parametrize(
"configured_url,expected_base",
[
pytest.param("https://gateway.us.hanzo.ai/v1", "https://gateway.us.hanzo.ai", id="strips-v1"),
pytest.param(
"https://gateway.us.hanzo.ai/v1/", "https://gateway.us.hanzo.ai", id="strips-v1-trailing-slash"
),
],
)
def test_resolves_and_strips_v1_for_anthropic_base(monkeypatch, configured_url, expected_base):
# SDK re-appends /v1/messages, so resolve() returns the bare host.
monkeypatch.setenv("AI_GATEWAY_URL", configured_url)
monkeypatch.setenv("AI_GATEWAY_API_KEY", "sk-secret")
assert resolve_gateway_config() == (expected_base, "sk-secret")
def test_gateway_env_points_sdk_at_gateway():
env = gateway_env("https://gateway.us.hanzo.ai", "sk-secret", {"stamp_pr_number": 123})
assert env["ANTHROPIC_BASE_URL"] == "https://gateway.us.hanzo.ai"
assert env["ANTHROPIC_AUTH_TOKEN"] == "sk-secret"
assert env["ANTHROPIC_API_KEY"] == "sk-secret"
def test_gateway_env_tags_ai_product_and_attribution():
# Go gateway reads one X-Insights-Properties JSON blob, not x-insights-property-*.
env = gateway_env("https://host", "sk-secret", {"stamp_pr_number": 123, "stamp_repo": "Insights/insights"})
headers = env["ANTHROPIC_CUSTOM_HEADERS"]
assert headers == (
'X-Insights-Properties: {"ai_product":"aio_stamp","stamp_pr_number":123,"stamp_repo":"Insights/insights"}'
)
assert "x-insights-property-" not in headers
def test_ai_product_uses_aio_prefix_no_reserved_prefix():
assert AI_PRODUCT == "aio_stamp"
assert not AI_PRODUCT.startswith("$")
def test_header_values_are_single_line():
env = gateway_env("https://host", "sk-secret", {"stamp_pr_title": "line one\nline two"})
header = env["ANTHROPIC_CUSTOM_HEADERS"]
assert "\n" not in header
assert '"stamp_pr_title":"line one line two"' in header
def test_none_attribution_values_dropped():
env = gateway_env("https://host", "sk-secret", {"stamp_commit_type": None})
assert "stamp_commit_type" not in env["ANTHROPIC_CUSTOM_HEADERS"]
def test_extra_properties_parses_server_injected_json(monkeypatch):
monkeypatch.setenv("STAMPFN_EXTRA_PROPERTIES", '{"stamp_runtime":"hosted","stamp_team_id":2}')
assert analytics_extra_properties() == {"stamp_runtime": "hosted", "stamp_team_id": 2}
@pytest.mark.parametrize(
"raw",
[
pytest.param(None, id="absent-action-path"),
pytest.param("{not json", id="malformed-json"),
pytest.param('["not", "a", "dict"]', id="non-dict-json"),
],
)
def test_extra_properties_degrade_to_empty(monkeypatch, raw):
# Telemetry must never fail a review: anything but a JSON object degrades to {}.
if raw is None:
monkeypatch.delenv("STAMPFN_EXTRA_PROPERTIES", raising=False)
else:
monkeypatch.setenv("STAMPFN_EXTRA_PROPERTIES", raw)
assert analytics_extra_properties() == {}