B6 built the project registry by importing src.projects locally (host-path hack
+ importlib.reload), so it evaluated ORCH_PROJECTS_JSON from the launcher's
process-env. On the deployer's canonical host run that var is unset → built-in
default (ET+ORCH) → false FAIL even when staging isolation is healthy.
- Add read-only additive endpoint GET /projects (src/main.py) returning
known_plane_project_ids + {plane_project_id, repo, work_item_prefix, name}
of the live process; no secrets. Existing routes unchanged.
- Rewrite B6 to fetch GET {base}/projects via the same stdlib _get helper as
A/B4/B5/C; drop the host-path hack and importlib.reload (launch-invariant).
- Isolate the verdict in pure _evaluate_b6(known) -> (passed, detail); contract
unchanged (PASS iff SANDBOX in known and prod ET/ORCH absent). Endpoint
degradation (non-200 / missing key / bad body / network) → deterministic FAIL.
- src/projects.py and .env* untouched.
Docs (golden source): API table + staging-gate B6 mechanic in
docs/architecture/README.md; B6 description + isolation row in
docs/operations/STAGING_CHECK.md; CHANGELOG entry.
Tests: tests/test_staging_check_b6.py (TC-01..TC-07), tests/test_projects_endpoint.py.
Refs: ORCH-048
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
56 lines
2.1 KiB
Python
56 lines
2.1 KiB
Python
"""ORCH-048: tests for the read-only GET /projects diagnostics endpoint.
|
|
|
|
Added by ADR-001 so the staging-check suite (B6) can read the project registry of
|
|
the *live* instance over HTTP instead of importing src.projects into the script's
|
|
own process-env. The endpoint is read-only / additive and must expose only
|
|
id / repo / prefix / name — never secrets.
|
|
"""
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from src.main import app
|
|
from src import projects as P
|
|
from src.projects import reload_projects
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def test_projects_endpoint_returns_known_ids():
|
|
"""GET /projects → 200 with known_plane_project_ids matching the registry."""
|
|
resp = client.get("/projects")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert "known_plane_project_ids" in body
|
|
assert set(body["known_plane_project_ids"]) == P.known_plane_project_ids()
|
|
|
|
|
|
def test_projects_endpoint_lists_projects_with_expected_fields():
|
|
"""Each project entry exposes exactly id/repo/prefix/name (no secrets)."""
|
|
body = client.get("/projects").json()
|
|
assert isinstance(body["projects"], list)
|
|
assert len(body["projects"]) == len(P.PROJECTS)
|
|
allowed = {"plane_project_id", "repo", "work_item_prefix", "name"}
|
|
for entry in body["projects"]:
|
|
assert set(entry.keys()) == allowed
|
|
# No secret-looking keys leaked into the payload.
|
|
for key in entry:
|
|
assert "token" not in key.lower()
|
|
assert "secret" not in key.lower()
|
|
|
|
|
|
def test_projects_endpoint_reflects_custom_registry(monkeypatch):
|
|
"""The endpoint reflects the running process's registry, not a hardcoded one."""
|
|
custom = (
|
|
'[{"plane_project_id": "endpoint-uuid", "repo": "endpoint-repo", '
|
|
'"work_item_prefix": "EP", "name": "Endpoint"}]'
|
|
)
|
|
monkeypatch.setattr(P.settings, "projects_json", custom)
|
|
reload_projects()
|
|
try:
|
|
body = client.get("/projects").json()
|
|
assert body["known_plane_project_ids"] == ["endpoint-uuid"]
|
|
assert body["projects"][0]["repo"] == "endpoint-repo"
|
|
assert body["projects"][0]["work_item_prefix"] == "EP"
|
|
finally:
|
|
reload_projects()
|