mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
109 lines
4.9 KiB
Python
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")
|