#!/usr/bin/env python3 """Run INSIDE the api image by tests/test_tool_toggle.sh - see that file. Asserts the half of the switch that needs a real app: a disabled tool is absent from the OpenAPI spec the model reads, AND its endpoint 404s. Both halves matter. Absent-from-the-spec is what stops the context being spent describing it; gone-from-the-routes is what stops a stale cached spec somewhere else from calling it anyway. """ import importlib import os import sys import tempfile os.environ["OWUITOOLS_AUTH_TOKEN"] = "toggle-probe" # Makes vm_agent appear: main.py registers that router only when the job # volume is mounted, so without this the tool is absent for a reason that # has nothing to do with the switch being tested. os.environ["OWUITOOLS_VM_JOBS_DIR"] = tempfile.mkdtemp() sys.path.insert(0, "/app") FAILS: list[str] = [] def check(label: str, condition: bool, detail: str = "") -> None: if condition: print(f"ok {label}") else: FAILS.append(f"{label}: {detail}") print(f"FAIL {label}\n {detail}") def offered(config_path: str): """Reimport the app against a given config and return (app, tool names).""" os.environ["OWUITOOLS_TOOLS_CONFIG"] = config_path for module in [name for name in list(sys.modules) if name.startswith("owuitools")]: del sys.modules[module] main = importlib.import_module("owuitools.main") spec = main.app.openapi() names = {operation.get("operationId") for path in spec["paths"].values() for operation in path.values()} return main.app, {name for name in names if name} _, everything = offered("/configs/tools.toml") check("the shipped config offers every tool", len(everything) == 22, f"{len(everything)} tools: {sorted(everything)}") with tempfile.NamedTemporaryFile("w", suffix=".toml", delete=False) as handle: handle.write("[tools]\nsearch_papers = false\npcb_calc = false\n" "vm_agent = false\nnot_a_tool = false\n") switched = handle.name app, trimmed = offered(switched) check("exactly the three disabled tools left the spec", everything - trimmed == {"search_papers", "pcb_calc", "vm_agent"}, f"removed {sorted(everything - trimmed)}") check("nothing else was removed", len(trimmed) == len(everything) - 3, f"{len(trimmed)} remain of {len(everything)}") from starlette.testclient import TestClient # noqa: E402 client = TestClient(app) auth = {"Authorization": "Bearer toggle-probe"} disabled_call = client.post("/tools/pcb_calc", json={"calculation": "via_current"}, headers=auth) check("a disabled tool's endpoint is gone, not merely undescribed", disabled_call.status_code == 404, f"got HTTP {disabled_call.status_code}") enabled_call = client.post("/tools/subnet_calc", json={"network": "192.168.1.0/24"}, headers=auth) check("an enabled tool still answers", enabled_call.status_code == 200, f"got HTTP {enabled_call.status_code}") _, fallback = offered("/nonexistent/tools.toml") check("a missing config leaves every tool on", fallback == everything, f"{len(fallback)} vs {len(everything)}") os.unlink(switched) print() if FAILS: print(f"{len(FAILS)} FAILED") for line in FAILS: print(f" - {line}") raise SystemExit(1) print("all route-level toggle checks passed")