Spaces:
Running
Running
| """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}" | |