"""Tests for the fold_structure agent tool — the Bench's auto-opening 3-D view. The privacy contract is the point of this tool and is asserted here: it must resolve a NAMED protein to a PUBLIC AlphaFold-DB model by sending only the gene symbol + organism, and must never accept or forward a sequence (de-novo folding of a user's own variant goes to a third party, so it stays an explicit, separately-consented client-side action). """ import pytest from dee.core import agent_tools as t def test_fold_structure_is_registered_and_specced(): assert "fold_structure" in t._TOOLS spec = next(s for s in t.TOOL_SPECS if s["function"]["name"] == "fold_structure") params = spec["function"]["parameters"] assert set(params["required"]) == {"gene_symbol", "organism"} # It must NOT take a sequence — that's the whole privacy boundary. assert "sequence" not in params["properties"] # NO enum on organism. AlphaFold DB covers all of UniProt, and an enum is # a hard gate: the model physically cannot emit "yeast" if the schema only # lists human and mouse, so it reports the organism as unsupported instead # of trying. That is how SpCas9 — modelled as Q99ZW2 the whole time — came # back to a user as "no structure available". assert "enum" not in params["properties"]["organism"] def test_fold_structure_requires_gene_and_organism(): assert t._tool_fold_structure({"organism": "human"})["ok"] is False # A bare symbol IS genuinely ambiguous, so an empty organism is refused — # but the refusal must be "which organism?", never "that one isn't allowed". bad = t._tool_fold_structure({"gene_symbol": "MC1R"}) assert bad["ok"] is False assert "which organism" in bad["error"].lower() def test_fold_structure_accepts_any_organism(monkeypatch): """Regression: bacteria and yeast must reach the resolver untouched.""" seen = {} def fake(organism, gene_symbol): seen["args"] = (organism, gene_symbol) return {"ok": True, "uniprot": "Q99ZW2", "alphafold_url": "u", "alphafold_page": "p"} from dee.core import resolve as _resolve monkeypatch.setattr(_resolve, "resolve_uniprot", fake) out = t._tool_fold_structure({"gene_symbol": "cas9", "organism": "S. pyogenes"}) assert out["ok"] is True and out["uniprot"] == "Q99ZW2" assert seen["args"] == ("s. pyogenes", "cas9") def test_fold_structure_returns_public_model_reference(monkeypatch): seen = {} def fake_resolve_uniprot(organism, gene_symbol): seen["args"] = (organism, gene_symbol) return {"ok": True, "uniprot": "Q01726", "alphafold_url": "https://alphafold.ebi.ac.uk/files/AF-Q01726-F1-model_v6.pdb", "alphafold_page": "https://alphafold.ebi.ac.uk/entry/Q01726"} from dee.core import resolve as _resolve monkeypatch.setattr(_resolve, "resolve_uniprot", fake_resolve_uniprot) out = t._tool_fold_structure({"gene_symbol": "MC1R", "organism": "human"}) assert out["ok"] is True assert out["uniprot"] == "Q01726" assert out["alphafold_url"].startswith("https://alphafold.ebi.ac.uk/") assert out["source"] == "AlphaFold DB" # ONLY (organism, gene) was sent onward — no sequence anywhere in the call. assert seen["args"] == ("human", "MC1R") def test_fold_structure_surfaces_lookup_failure_honestly(monkeypatch): from dee.core import resolve as _resolve monkeypatch.setattr(_resolve, "resolve_uniprot", lambda o, g: {"ok": False, "error": "No reviewed UniProt entry found for ZZZ (human)."}) out = t._tool_fold_structure({"gene_symbol": "ZZZ", "organism": "human"}) assert out["ok"] is False assert "ZZZ" in out["error"] # --------------------------------------------------------------------------- # # The failure UI must name the actual cause # --------------------------------------------------------------------------- # # An audit found AlphaFold-DB up and serving 200s (v6, ~0.3s) while the viewer # was telling a user "AlphaFold-DB is temporarily unreachable". The headline # was the WHOLE message, so every possible cause — a Mol* bundle that never # loaded, a stale model version 404, an aborted timeout, a parse failure — # was reported as an EBI outage, and nothing on screen could distinguish them. def _app_js(): with open("dee/static/app.js", encoding="utf-8") as fh: return fh.read() def test_the_error_ui_accepts_and_shows_a_reason(): src = _app_js() assert "function _afShowError(host, transient, retry, reason)" in src # Shown to the user, and logged in full for whoever is debugging. assert "console.warn('[alphafold] '" in src def test_the_headline_no_longer_asserts_that_ebi_is_down(): """'did not return the model' describes what we observed. 'is temporarily unreachable' is a claim about a third party's uptime that we were making without evidence — and that the audit showed was often false.""" src = _app_js() assert "AlphaFold-DB is temporarily unreachable" not in src assert "AlphaFold-DB did not return the model" in src def test_every_failure_path_passes_a_reason(): """A reason parameter nothing populates is decoration. Each call site must say which failure it is reporting.""" src = _app_js() for reason in ( "the 3-D viewer library did not load (CDN or network)", "the AlphaFold API has no entry for this accession", "every candidate model URL failed to load", ): assert reason in src, f"missing reason: {reason}"