"""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"]