"""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 # noqa: E402 from api.main import app # noqa: E402 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") # required motif prefix 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 # pydantic validation 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(): # No protein supplied → resolved from the trait's validated 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")