Files
orchestrator/tests/test_projects_endpoint.py
claude-bot 2cf873a777
All checks were successful
CI / test (push) Successful in 13s
CI / test (pull_request) Successful in 12s
feat(staging-check): ORCH-048 B6 reads registry via GET /projects, not local import
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>
2026-06-06 05:25:45 +00:00

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()