Spaces:
Sleeping
Sleeping
avoigt1121
feat(panel): widen 19 → 29 genes (HR + MMR), and stop silently dropping uncurated genes
7ae3398 | """The deploy surface exists and matches the published contract. | |
| `deploy/orchestrator_registration.yaml` is what the orchestrator merges into its `agents.yaml`. | |
| If a tool is renamed here and not there (or vice versa), the orchestrator dispatches into | |
| nothing — so the descriptor and the live `gr.api` names are checked against each other. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| import gradio_ui | |
| def _descriptor() -> str: | |
| return Path("deploy/orchestrator_registration.yaml").read_text() | |
| def test_mcp_server_registers_the_contract_tool_names(): | |
| """The MCP tool names ARE the published contract — not the Python wrapper names.""" | |
| import asyncio | |
| from fastmcp import Client | |
| from src.server import mcp | |
| async def _names(): | |
| async with Client(mcp) as client: | |
| return sorted(t.name for t in await client.list_tools()) | |
| assert mcp.name == "pdac-genomics-agent" | |
| assert asyncio.run(_names()) == ["query_variant_status", "variant_by_subtype"] | |
| def test_machine_endpoints_exist_with_request_injection(): | |
| """Each `gr.api` target must accept an injected `request` — the token gate needs it.""" | |
| import inspect | |
| for name in ("query_variant_status", "variant_by_subtype", "panel"): | |
| fn = getattr(gradio_ui, name) | |
| params = inspect.signature(fn).parameters | |
| assert "request" in params, f"{name} cannot gate without an injected request" | |
| assert params["request"].default is None | |
| def test_api_names_match_the_orchestrator_descriptor(): | |
| """The frozen names in the descriptor must be the ones actually registered.""" | |
| descriptor = _descriptor() | |
| for tool_name in ("query_variant_status", "variant_by_subtype"): | |
| assert tool_name in descriptor | |
| def test_descriptor_arg_names_match_the_live_signatures(): | |
| """The published `args` must be the parameters the endpoints actually take. | |
| String equality on tool names caught renames but not the drift that actually happened: | |
| the descriptor advertised two args for `variant_by_subtype` (which takes four) and | |
| `list[str]` for `genes` (which is a comma-separated string). Compare structurally. | |
| """ | |
| import inspect | |
| import yaml # a gradio dependency, so always present where the Space runs | |
| tools = {t["name"]: t for t in yaml.safe_load(_descriptor())["tools"]} | |
| for name, entry in tools.items(): | |
| params = inspect.signature(getattr(gradio_ui, name)).parameters | |
| declared = set(entry["args"] or {}) | |
| live = {p for p in params if p != "request"} | |
| assert declared == live, f"{name}: descriptor says {declared}, live takes {live}" | |
| for arg in declared: | |
| # PEP 563 is on in `gradio_ui`, so annotations arrive as strings. | |
| assert params[arg].annotation == "str", f"{name}.{arg} is not a plain string" | |
| assert entry["args"][arg] == "str", ( | |
| f"{name}.{arg} is declared as {entry['args'][arg]!r}, but every machine " | |
| "argument is a plain string" | |
| ) | |
| def test_descriptor_does_not_restate_the_cohort_set_wrongly(): | |
| """Whatever cohorts the descriptor names must be curated artifacts that exist.""" | |
| import re | |
| import yaml | |
| doc = yaml.safe_load(_descriptor()) | |
| curated = {p.stem for p in Path("src/resources/curated").glob("*.json")} | |
| # Study ids appear in the illustrative tail of `cohorts:` as a comma-separated run. | |
| named = set(re.findall(r"(?:^|[ ,])([a-z][a-z0-9]+(?:_[a-z0-9]+)+)(?=[,.\s]|$)", doc["cohorts"])) | |
| named -= {"src", "resources", "curated", "json"} | |
| assert named == curated, ( | |
| f"descriptor cohort list has drifted — extra: {sorted(named - curated)}, " | |
| f"missing: {sorted(curated - named)}" | |
| ) | |
| def test_blocks_app_is_importable(): | |
| """The Space's `app_file` must expose a `demo` that built without error.""" | |
| assert gradio_ui.demo is not None | |
| assert gradio_ui.demo.title == "PDAC Genomics Agent" | |
| def test_panel_endpoint_returns_the_v1_panel(monkeypatch): | |
| """The orchestrator can ask for the panel rather than hardcoding the gene list. | |
| Asserted against `load_panel()` rather than a literal count, precisely because the panel is | |
| config that widens (19 → 29 on 2026-08-05). A hardcoded number here would fail on every | |
| widening for no reason; what actually matters is that the endpoint and the resource agree. | |
| """ | |
| import json | |
| from src.workflows.variant_status import load_panel | |
| monkeypatch.delenv("ACCESS_CONTROL", raising=False) | |
| body = json.loads(gradio_ui.panel(request=None)) | |
| assert body["n_genes"] == len(load_panel()) | |
| assert body["panel"] == load_panel() | |
| assert "KRAS" in body["panel"] | |