"""Tests for the CRISPR provenance layer (dee/core/crispr_methods.py). These assert the disclosure actually discloses — specifically the two scope limits a reviewing scientist flagged, which the algorithm code knew about but the UI never showed: the composite's weighting, and that indel predictions are neither the real inDelphi model nor tuned to the user's cell type. """ import pytest from dee import server from dee.core import crispr_methods as cm @pytest.fixture def client(): app = server.create_app() app.config.update(TESTING=True) return app.test_client() def test_every_ordered_method_exists_and_is_complete(): for key in cm.METHOD_ORDER: m = cm.METHODS[key] for field in ("label", "what", "formula", "basis", "limits"): assert m.get(field), f"{key} is missing {field}" assert isinstance(m.get("citations"), list) def test_composite_formula_is_stated_not_hidden(): # The reviewer's ask: "the weights making the composite score should be # transparent." # # CONTRACT CHANGED 2026-08-01, deliberately. This test used to assert # `"no hidden" in basis`, which locked in a false claim: the composite is # NOT a plain product. crispr.py has always computed # `on_target * (1 - 0.6 * self_off)`, so "no hidden weights and no tuning # constants" was the disclosure hiding the one weight there is. The # provenance now states the weight and calls it a judgement call; the # value is asserted against the engine's own constant in # tests/test_crispr_disclosure.py so the two cannot drift apart again. from dee.core.crispr import COMPOSITE_OFFTARGET_WEIGHT c = cm.METHODS["composite"] assert "on_target" in c["formula"] and "self_off" in c["formula"] assert str(COMPOSITE_OFFTARGET_WEIGHT) in c["formula"] assert "judgement call" in c["basis"] def test_on_target_does_not_claim_to_be_rule_set_2(): m = cm.METHODS["on_target"] assert "not Doench Rule Set 2" in m["basis"] or "INSPIRED" in m["basis"] assert "0.55" in m["limits"] # the honest correlation range def test_self_off_declares_its_input_only_scope(): # Self-off is deliberately input-only; the genome-wide answer lives in the # separate "Genome off" column, so this must say so rather than imply the # tool has no off-target capability at all. m = cm.METHODS["self_off"] assert "only the sequence you pasted" in m["limits"] def test_indels_declare_heuristic_and_cell_type_agnostic(): # The two things the scientist's review turned on. m = cm.METHODS["indels"] assert "HEURISTIC" in m["limits"] assert "CELL-TYPE-AGNOSTIC" in m["limits"] # Name real cell lines so the caveat is concrete, not hand-wavy. assert "HEK293" in m["limits"] # Credit the model it approximates. assert any("inDelphi" in c for c in m["citations"]) def test_methods_route_is_public_and_shaped(client): r = client.get("/api/crispr/methods") # no auth — auditable by anyone assert r.status_code == 200 body = r.get_json() assert body["ok"] is True assert body["order"] == cm.METHOD_ORDER assert set(body["methods"]) == set(cm.METHOD_ORDER) assert "off-target" in body["summary"] # --------------------------------------------------------------------------- # # Genome off-target: the column that decides whether "consolidation" is real # --------------------------------------------------------------------------- # def test_genome_off_is_documented_with_both_scope_limits(): m = cm.METHODS["genome_off"] # Coverage limit: human/mouse are CDS-only, not whole genome. assert "CODING SEQUENCE ONLY" in m["limits"] assert "intergenic" in m["limits"] # Depth limit: only top-ranked guides are screened. assert "top-ranked" in m["limits"] # Privacy: the guide never leaves the engine. assert "never leaves" in m["basis"] def test_self_off_points_at_genome_search_not_an_external_tool(): # The old copy sent users to CRISPOR from here; it should now point at # the engine's own genome column instead. m = cm.METHODS["self_off"] assert "Genome off" in m["limits"] assert "CRISPOR" not in m["limits"] def test_genome_offtarget_top_n_is_bounded(): # The whole reason the feature was unshippable: a ~3.5s query per guide # across all 50 candidates. Keep the screened set small and explicit. from dee.core.crispr import GENOME_OFFTARGET_TOP_N assert 1 <= GENOME_OFFTARGET_TOP_N <= 15 # --------------------------------------------------------------------------- # # Genome registry: which organisms, and at what coverage # --------------------------------------------------------------------------- # def test_full_genome_organisms_are_actually_full_genome(): from dee.core.offtarget import GENOME_SOURCES, is_organism_ready full = {k for k, v in GENOME_SOURCES.items() if v["scope"] == "full genome"} # Small enough to index completely — an off-target ANYWHERE is found. assert {"ecoli", "yeast", "worm", "fly"} <= full for o in full: assert is_organism_ready(o) assert GENOME_SOURCES[o]["url"].startswith("https://") def test_mammals_are_declared_cds_only_not_silently_partial(): from dee.core.offtarget import GENOME_SOURCES for o in ("human", "mouse"): assert "CDS" in GENOME_SOURCES[o]["scope"] # And the user-facing methods must say so, with the reason. limits = cm.METHODS["genome_off"]["limits"] assert "CODING SEQUENCE ONLY" in limits # The reason must be stated (memory), without false precision — the # per-site cost was measured at roughly 90-260 B depending on how the # baseline is counted, so we claim an order of magnitude, not a figure. assert "390 million" in limits and "gigabytes" in limits def test_index_cache_is_bounded(): # Six organisms x multi-GB indexes would OOM the Space; the cache must evict. from dee.core import offtarget as ot assert ot._MAX_CACHED_INDEXES >= 1 assert callable(ot._evict_if_needed) # --------------------------------------------------------------------------- # # B2: a bare gene symbol needs only a species — not an accession # --------------------------------------------------------------------------- # def test_symbol_without_organism_returns_actionable_signal(): from dee.core import resolve as r out = r.resolve_target("TP53", organism="") assert out["ok"] is False assert out.get("needs_organism") is True # structured, so the UI can offer a pick assert out.get("pending") == "TP53" # echoes the symbol to re-run assert "accession" in out["error"].lower() # explicitly tells them none is needed def test_pasted_sequence_never_triggers_organism_prompt(): from dee.core import resolve as r out = r.resolve_target("ATGGCC" * 40, organism="") assert out["ok"] is True assert not out.get("needs_organism")