Files
treg/tests/test_walking_skeleton.py

109 lines
4.9 KiB
Python

"""Step 1 proof: register an ENV-secret tool, call it through the proxy, and confirm the
registry injected the credential the caller never held — and never leaked the secret back.
The echo upstream + authed `clients` fixture live in conftest.py.
"""
from __future__ import annotations
from httpx import AsyncClient
async def _register_posthog_like(c: AsyncClient, *, auth_in="header") -> str:
s = await c.post("/secrets", json={"name": "phx", "value": "test-secret-123"})
assert s.status_code == 200, s.text
secret_id = s.json()["id"]
name = f"echo-{auth_in}"
t = await c.post(
"/tools",
json={
"name": name,
"base_url": "http://upstream",
"secret_id": secret_id,
"auth_in": auth_in,
"auth_name": "api_key" if auth_in == "query" else "Authorization",
"auth_format": "{secret}" if auth_in == "query" else "Bearer {secret}",
},
)
assert t.status_code == 200, t.text
return name
async def test_proxy_injects_query_credential_and_relays_body(clients: AsyncClient):
name = await _register_posthog_like(clients, auth_in="query")
r = await clients.post(f"/call/{name}/echo?foo=bar", content=b"hello-body")
assert r.status_code == 200, r.text
data = r.json()
assert data["query"]["api_key"] == "test-secret-123" # injected into query
assert data["query"]["foo"] == "bar" # caller's own params preserved
assert data["body"] == "hello-body" # body relayed verbatim
async def test_proxy_secret_file_injects_extracted_token(clients: AsyncClient):
# A `.secret`-style JSON token file (GSC/GCP shape): registry pulls access_token + injects it.
s = await clients.post(
"/secrets",
json={"name": "gsc", "kind": "secret_file", "value": '{"access_token": "AT-XYZ", "refresh_token": "r"}'},
)
sid = s.json()["id"]
t = await clients.post(
"/tools",
json={"name": "gsc-tool", "base_url": "http://upstream", "secret_id": sid, "injector": "secret_file"},
)
assert t.status_code == 200, t.text
r = await clients.get("/call/gsc-tool/echo")
assert r.status_code == 200, r.text
assert r.json()["auth"] == "Bearer AT-XYZ" # extracted from the JSON blob, caller never held it
async def test_secret_value_never_returned(clients: AsyncClient):
await _register_posthog_like(clients)
r = await clients.get("/secrets")
assert r.status_code == 200
assert all("value" not in row for row in r.json())
async def test_meta_reports_the_released_version(clients: AsyncClient):
"""`app_version` is a hash of index.html — it answers "has the dashboard bundle changed?", which
is what an open tab compares to offer a refresh. It is NOT the release version, and after
publishing 0.9.0 there was no way to confirm from the live path which version was serving.
Both are kept because they answer different questions."""
body = (await clients.get("/meta")).json()
assert body["treg_version"], "a release check must be able to read the version from outside"
assert body["treg_version"] != body["app_version"], "these are different questions"
assert body["treg_version"][0].isdigit(), f"expected a version, got {body['treg_version']!r}"
def test_the_pool_cannot_outnumber_postgres_during_a_deploy():
"""A rolling deploy runs TWO instances against one database. At 20+40 each, that was 120
potential connections against a basic-plan ceiling of ~100 — a deploy could starve Postgres with
no bug anywhere, which is exactly what happened on 2026-08-15. Two instances of the current
numbers must stay comfortably under a 97-connection ceiling.
Counts EVERY primary pool, not just the API's: splitting one pool into three is a fine way to protect
the API and a fine way to walk back into this outage, and only the sum tells the two apart."""
from treg.infra import db
assert db.POOL_SPECS, "expected explicit pool bounds for the postgres path"
per_instance = db.connection_budget(workers=1)["per_instance"]
assert per_instance * 2 <= 90, (
f"two deploy-time instances could hold {per_instance * 2} connections — "
"that is how the 2026-08-15 outage started")
def test_migrations_fail_fast_rather_than_queueing_the_world():
"""An ALTER on a hot table needs an exclusive lock. Without a lock_timeout it QUEUES behind live
traffic, and every new query then queues behind IT — both instances starve and the shared
database wedges (2026-08-15, root cause). The startup path must set the timeout before running
migrations so a contended deploy fails cleanly instead."""
from importlib.resources import files
env_src = files("treg").joinpath("alembic", "env.py").read_text()
assert "lock_timeout = '5s'" in env_src
assert "statement_timeout = '120s'" in env_src
assert env_src.index("lock_timeout = '5s'") < env_src.index("run_sync(_run_migrations)"), (
"the timeout must be set BEFORE Alembic migrations run")