syntheogenesis / tests /test_fold_structure_tool.py
github-actions[bot]
Deploy d0d1a1b
01b8103
Raw
History Blame Contribute Delete
5.6 kB
"""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}"