Spaces:
Sleeping
Sleeping
| """The `gr.api` machine endpoints are gated by caller-token verification (ADR-0004/0018). | |
| A machine call carries no OAuth session, so identity comes from a caller token resolved via | |
| `whoami`. These endpoints are the orchestrator's front door — if they are open, the allow-list | |
| on the UI is decorative. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import pytest | |
| import gradio_ui | |
| class _Request: | |
| """Minimal stand-in for `gr.Request` — only `.headers.get` is used.""" | |
| def __init__(self, headers=None): | |
| self.headers = headers or {} | |
| def _enforce(monkeypatch, tmp_path): | |
| monkeypatch.setenv("ACCESS_CONTROL", "enforce") | |
| monkeypatch.setenv("ALLOWED_IDS", "orchestrator-bot") | |
| monkeypatch.setenv("LOG_SINK", "local") | |
| monkeypatch.setenv("LOG_SINK_LOCAL_DIR", str(tmp_path / "run_logs")) | |
| gradio_ui._WHOAMI_CACHE.clear() | |
| yield | |
| gradio_ui._WHOAMI_CACHE.clear() | |
| def _explode(*a, **k): # pragma: no cover | |
| raise AssertionError("tool was called despite a denied access decision") | |
| MACHINE_CASES = [ | |
| ("query_variant_status", {"genes": "KRAS", "source": "cbioportal:paad_tcga"}), | |
| ("variant_by_subtype", {"genes": "KRAS", "source": "cbioportal:paad_tcga"}), | |
| ("panel", {}), | |
| ] | |
| def test_machine_endpoint_denies_tokenless_call(monkeypatch, endpoint, kwargs): | |
| """No token header ⇒ denied, fail-closed, before any tool call.""" | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode) | |
| body = json.loads(getattr(gradio_ui, endpoint)(**kwargs, request=_Request())) | |
| assert body["status"] == "denied" | |
| def test_machine_endpoint_denies_unresolvable_token(monkeypatch, endpoint, kwargs): | |
| """A token `whoami` cannot resolve is an anonymous caller, not a trusted one.""" | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: None) | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode) | |
| request = _Request({"x-orchestrator-token": "Bearer nonsense"}) | |
| body = json.loads(getattr(gradio_ui, endpoint)(**kwargs, request=request)) | |
| assert body["status"] == "denied" | |
| def test_machine_endpoint_denies_resolvable_but_unlisted_identity(monkeypatch): | |
| """A real HF account that is not allow-listed is still refused.""" | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "random-person") | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| request = _Request({"x-orchestrator-token": "tok"}) | |
| body = json.loads(gradio_ui.query_variant_status(genes="KRAS", source="x", request=request)) | |
| assert body["status"] == "denied" | |
| assert "random-person" in body["reason"] | |
| def test_machine_endpoint_allows_listed_identity(monkeypatch): | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot") | |
| monkeypatch.setattr( | |
| gradio_ui, | |
| "_query_variant_status", | |
| lambda genes, source: {"n_samples": 7, "genes": {}, "source": source}, | |
| ) | |
| request = _Request({"x-orchestrator-token": "Bearer good-token"}) | |
| body = json.loads( | |
| gradio_ui.query_variant_status( | |
| genes="KRAS,TP53", source="cbioportal:paad_tcga", request=request | |
| ) | |
| ) | |
| assert body["n_samples"] == 7 | |
| def test_bearer_prefix_is_stripped(monkeypatch): | |
| seen = {} | |
| monkeypatch.setattr( | |
| gradio_ui, "_resolve_token_identity", lambda token: seen.setdefault("token", token) | |
| ) | |
| gradio_ui._machine_caller_identity(_Request({"x-orchestrator-token": "Bearer abc123"})) | |
| assert seen["token"] == "abc123" | |
| def test_empty_genes_falls_back_to_full_panel(): | |
| """The machine contract sends a comma-separated string; empty means 'the whole panel'.""" | |
| assert gradio_ui._split_genes("") == list(gradio_ui.PANEL) | |
| assert gradio_ui._split_genes("kras, tp53") == ["KRAS", "TP53"] | |
| # --------------------------------------------------------------------------- # | |
| # Denial DIAGNOSIS — the three failure modes must be distinguishable | |
| # --------------------------------------------------------------------------- # | |
| # Regression guard for a real misdiagnosis (2026-07-27→28). The orchestrator was denied and | |
| # the response said "Please sign in with your HuggingFace account", so the investigation went | |
| # server-side — probing headers, suspecting `whoami`. Nothing was wrong here: the caller had | |
| # simply sent no header at all (its own HF_TOKEN was unset). These three states are | |
| # operationally different and must never again be one indistinguishable message. | |
| def test_no_token_header_is_diagnosed_as_caller_misconfiguration(monkeypatch): | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| body = json.loads(gradio_ui.query_variant_status(source="x", request=_Request())) | |
| assert body["status"] == "denied" | |
| assert body["machine_auth"] == "no_token_header" | |
| # names the calling Space's secret, and does NOT tell a machine to sign in | |
| assert "HF_TOKEN" in body["reason"] | |
| assert "sign in" not in body["reason"].lower() | |
| def test_unresolvable_token_is_diagnosed_separately_from_a_missing_one(monkeypatch): | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: None) | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| body = json.loads( | |
| gradio_ui.query_variant_status( | |
| source="x", request=_Request({"x-orchestrator-token": "bad-token"}) | |
| ) | |
| ) | |
| assert body["machine_auth"] == "token_unresolved" | |
| def test_resolved_but_unlisted_identity_is_the_only_real_authorization_denial(monkeypatch): | |
| """Identity resolved fine — this one IS an allow-list decision, and still names who.""" | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "stranger-bot") | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| body = json.loads( | |
| gradio_ui.query_variant_status( | |
| source="x", request=_Request({"x-orchestrator-token": "good-token"}) | |
| ) | |
| ) | |
| assert body["machine_auth"] == "not_allowlisted" | |
| assert "stranger-bot" in body["reason"] | |
| def test_identity_resolution_reports_resolved_on_success(monkeypatch): | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot") | |
| identity, diagnosis = gradio_ui._machine_caller_identity( | |
| _Request({"x-orchestrator-token": "good-token"}) | |
| ) | |
| assert (identity, diagnosis) == ("orchestrator-bot", "resolved") | |
| # --------------------------------------------------------------------------- # | |
| # Argument DIAGNOSIS — the same lesson, applied to caller-side type mistakes | |
| # --------------------------------------------------------------------------- # | |
| # Found while verifying the ADR-0007 deploy: passing the obvious-looking | |
| # `genes=["KRAS","TP53"]` returned | |
| # {"status": "error", "reason": "AttributeError: 'list' object has no attribute 'split'"} | |
| # — indistinguishable from a server fault, so it sends the reader into this repo when the fix | |
| # is in their own call. Exactly the failure mode the denial-diagnosis block above exists for. | |
| def _allowed(monkeypatch): | |
| monkeypatch.setattr(gradio_ui, "_resolve_token_identity", lambda token: "orchestrator-bot") | |
| return _Request({"x-orchestrator-token": "tok"}) | |
| def test_list_genes_reports_a_caller_side_argument_error(monkeypatch): | |
| """A list of genes is named as such, with the exact string to send instead.""" | |
| request = _allowed(monkeypatch) | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) # never runs | |
| body = json.loads( | |
| gradio_ui.query_variant_status( | |
| genes=["KRAS", "TP53"], source="cbioportal:paad_tcga", request=request | |
| ) | |
| ) | |
| assert body["machine_input"] == "genes_not_a_string" | |
| assert 'genes="KRAS,TP53"' in body["reason"] # the fix, spelled out | |
| assert "caller-side" in body["reason"] | |
| assert "AttributeError" not in body["reason"] | |
| def test_non_string_source_does_not_raise_before_the_gate(monkeypatch): | |
| """`registered_source` runs pre-gate; a non-string `source` must not blow up there. | |
| This was an uncaught exception (a 500), not a result — the one input that escaped | |
| `_safe_result` entirely because the audit descriptor is built before the auth check. | |
| """ | |
| request = _allowed(monkeypatch) | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| body = json.loads( | |
| gradio_ui.query_variant_status(genes="KRAS", source=["cbioportal:paad_tcga"], request=request) | |
| ) | |
| assert body["machine_input"] == "source_not_a_string" | |
| def test_argument_errors_are_reported_only_after_the_auth_gate(monkeypatch): | |
| """A tokenless caller gets `denied`, not a critique of their arguments.""" | |
| monkeypatch.setattr(gradio_ui, "_query_variant_status", _explode) | |
| body = json.loads( | |
| gradio_ui.query_variant_status(genes=["KRAS"], source=1234, request=_Request()) | |
| ) | |
| assert body["status"] == "denied" | |
| assert "machine_input" not in body | |
| def test_every_string_argument_is_covered(monkeypatch, field): | |
| request = _allowed(monkeypatch) | |
| monkeypatch.setattr(gradio_ui, "_variant_by_subtype", _explode) | |
| body = json.loads( | |
| gradio_ui.variant_by_subtype( | |
| genes="KRAS", source="cbioportal:paad_tcga", request=request, **{field: ["x"]} | |
| ) | |
| ) | |
| assert body["machine_input"] == f"{field}_not_a_string" | |
| def test_valid_strings_are_unaffected(monkeypatch): | |
| """The happy path must not acquire a new way to fail.""" | |
| request = _allowed(monkeypatch) | |
| monkeypatch.setattr( | |
| gradio_ui, "_query_variant_status", lambda genes, source: {"n_samples": 7, "genes": {}} | |
| ) | |
| body = json.loads( | |
| gradio_ui.query_variant_status( | |
| genes="KRAS,TP53", source="cbioportal:paad_tcga", request=request | |
| ) | |
| ) | |
| assert body["n_samples"] == 7 | |
| assert "machine_input" not in body | |