| """API-level smoke tests using FastAPI's TestClient."""
|
| import os
|
| import sys
|
|
|
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
| from fastapi.testclient import TestClient
|
| from api.main import app
|
|
|
| client = TestClient(app)
|
|
|
|
|
| def test_health():
|
| r = client.get("/api/health")
|
| assert r.status_code == 200
|
| assert r.json()["status"] == "ok"
|
|
|
|
|
| def test_index_page_renders():
|
| r = client.get("/")
|
| assert r.status_code == 200
|
| assert "Plant DNA Designer" in r.text
|
|
|
|
|
| def test_traits_partial():
|
| r = client.get("/traits/rice")
|
| assert r.status_code == 200
|
| assert "Bacterial blight resistance" in r.text
|
|
|
|
|
| def test_traits_unknown_species_404():
|
| assert client.get("/traits/nope").status_code == 404
|
|
|
|
|
| def test_design_endpoint():
|
| r = client.post("/api/design", json={
|
| "species": "rice", "gc_target": 45, "traits": ["Drought tolerance"],
|
| "codon_table": "rice", "protein": "MKAILV", "cas_type": "Cas9 (NGG)",
|
| "grna_num": 3, "population_size": 20, "generations": 10, "num_sequences": 1,
|
| })
|
| assert r.status_code == 200, r.text
|
| body = r.json()
|
| assert body["sequence"].startswith("ACGTGG")
|
| assert {"gc_content", "melting_temp", "molecular_weight", "cai", "mrna_stability"}.issubset(set(body["metrics"]))
|
|
|
|
|
| def test_design_rejects_bad_protein():
|
| r = client.post("/api/design", json={
|
| "species": "rice", "traits": ["Drought tolerance"], "protein": "MKZZZ",
|
| "population_size": 20, "generations": 5,
|
| })
|
| assert r.status_code == 422
|
|
|
|
|
| def test_design_requires_trait():
|
| r = client.post("/api/design", json={
|
| "species": "rice", "traits": [], "protein": "MKAILV",
|
| "population_size": 20, "generations": 10,
|
| })
|
| assert r.status_code == 400
|
|
|
|
|
| def test_analyze_endpoint():
|
| r = client.post("/api/analyze", json={"dna": "TATAATCAATGATAGGCC", "codon_table": "rice"})
|
| assert r.status_code == 200
|
| assert "metrics" in r.json() and "motifs" in r.json()
|
|
|
|
|
| def test_export_fasta():
|
| r = client.get("/api/export/fasta", params={"dna": "ATGAAAGCT", "name": "rice"})
|
| assert r.status_code == 200
|
| assert r.text.startswith(">")
|
|
|
|
|
| def test_mechanism_endpoint():
|
| r = client.get("/api/mechanism/Drought tolerance")
|
| assert r.status_code == 200
|
| body = r.json()
|
| assert body["primary_effector"]["gene"] == "DREB2A"
|
| assert "effectors" in body
|
|
|
|
|
| def test_mechanism_endpoint_unknown_404():
|
| assert client.get("/api/mechanism/not a trait").status_code == 404
|
|
|
|
|
| def test_design_without_protein_uses_trait_effector():
|
|
|
| r = client.post("/api/design", json={
|
| "species": "rice", "gc_target": 45, "traits": ["Drought tolerance"],
|
| "codon_table": "rice", "cas_type": "Cas9 (NGG)",
|
| "grna_num": 3, "population_size": 20, "generations": 10, "num_sequences": 1,
|
| })
|
| assert r.status_code == 200, r.text
|
| bt = r.json()["biological_target"]
|
| assert bt["primary"]["gene"] == "DREB2A"
|
| assert bt["encoded_protein_source"] == "trait-derived effector"
|
|
|
|
|
| def test_design_pareto_mode_returns_front():
|
| r = client.post("/api/design", json={
|
| "species": "rice", "gc_target": 50, "traits": ["Drought tolerance"],
|
| "codon_table": "rice", "cas_type": "Cas9 (NGG)", "grna_num": 3,
|
| "population_size": 30, "generations": 15, "pareto": True,
|
| })
|
| assert r.status_code == 200, r.text
|
| body = r.json()
|
| assert body["pareto_front"] and len(body["pareto_front"]) >= 1
|
| assert "axes" in body["pareto_front"][0]
|
| assert "pareto" in body["ga_report"]["mode"]
|
|
|
|
|
| def test_design_includes_report_card():
|
| r = client.post("/api/design", json={
|
| "species": "rice", "gc_target": 45, "traits": ["Drought tolerance"],
|
| "codon_table": "rice", "cas_type": "Cas9 (NGG)", "grna_num": 3,
|
| "population_size": 20, "generations": 10, "num_sequences": 1,
|
| })
|
| assert r.status_code == 200, r.text
|
| card = r.json()["report_card"]
|
| assert card["grade"] in ("Strong", "Good", "Needs work")
|
| assert 0 <= card["score"] <= 100
|
| labels = {it["label"] for it in card["items"]}
|
| assert {"Expression strength", "Plant safety", "Silencing risk", "Synthesis-ready"}.issubset(labels)
|
| for it in card["items"]:
|
| assert it["status"] in ("good", "watch", "fix")
|
|
|