bio-nexus-api / tests /test_smoke_admet.py
Samad14's picture
fix(blast): poll cap 65min, DNA validation, program/db/max_hits params
cd0c7a9 verified
Raw
History Blame Contribute Delete
5.91 kB
"""
Smoke tests for the ADMET descriptor endpoint.
Covers:
- Valid SMILES return 200 with correct shape
- Invalid SMILES return 422 or meaningful error
- Response contains 3a (core descriptors) and 3b (toxicity) sections
- _methodology metadata present
- Toxicity section contains _disclaimer
- All core numeric fields are finite numbers
"""
import math
import pytest
from tests.conftest import requires_rdkit
BASE = "/api/admet/descriptors"
@requires_rdkit
class TestADMETBasic:
"""Core endpoint health checks."""
def test_aspirin_returns_200(self, client):
resp = client.post(BASE, json={"smiles": "CC(=O)OC1=CC=CC=C1C(=O)O"})
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "complete"
assert body["result"] is not None
def test_result_has_core_fields(self, client, valid_smiles):
result = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]
for field in [
"smiles", "formula", "molecular_weight", "logp", "tpsa",
"hbd", "hba", "rotatable_bonds", "qed_score",
"heavy_atoms", "molar_refractivity", "molecular_volume",
"fsp3", "ring_count", "aromatic_ring_count",
]:
assert field in result, f"Missing core field: {field}"
def test_numeric_fields_are_finite(self, client, valid_smiles):
result = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]
numeric_fields = [
"molecular_weight", "logp", "tpsa", "qed_score",
"molar_refractivity", "molecular_volume", "fsp3",
]
for field in numeric_fields:
val = result[field]
assert isinstance(val, (int, float)), f"{field} is not numeric: {val}"
assert math.isfinite(val), f"{field} is not finite: {val}"
@requires_rdkit
class TestADMETMethodology:
"""Verify 3a/3b split metadata."""
def test_methodology_present(self, client, valid_smiles):
result = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]
assert "_methodology" in result
def test_core_descriptors_are_3a(self, client, valid_smiles):
meth = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["_methodology"]
assert meth["core_descriptors"]["tier"] == "3a"
assert meth["core_descriptors"]["confidence"] == "high"
def test_toxicity_is_3b(self, client, valid_smiles):
meth = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["_methodology"]
assert meth["toxicity"]["tier"] == "3b"
assert meth["toxicity"]["confidence"] == "approximate"
def test_drug_likeness_is_3a(self, client, valid_smiles):
meth = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["_methodology"]
assert meth["drug_likeness"]["tier"] == "3a"
@requires_rdkit
class TestADMETToxicity:
"""Verify toxicity section shape and disclaimer."""
def test_toxicity_has_disclaimer(self, client, valid_smiles):
tox = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["toxicity"]
assert "_disclaimer" in tox
assert "heuristic" in tox["_disclaimer"].lower() or "no ML" in tox["_disclaimer"]
def test_toxicity_has_all_fields(self, client, valid_smiles):
tox = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["toxicity"]
for field in [
"ames_mutagenicity", "ames_alerts", "herg_liability",
"hepatotoxicity_dili", "skin_sensitization",
"acute_toxicity_ld50", "ld50_estimate_log", "risk_score",
]:
assert field in tox, f"Missing toxicity field: {field}"
def test_risk_score_in_range(self, client, valid_smiles):
score = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["toxicity"]["risk_score"]
assert 0 <= score <= 10
@requires_rdkit
class TestADMETSafety:
"""Verify drug-likeness and structural alerts sections."""
def test_drug_likeness_has_lipinski(self, client, valid_smiles):
dl = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["drug_likeness"]
assert "lipinski" in dl
assert "pass" in dl["lipinski"]
assert "violation_count" in dl["lipinski"]
def test_structural_alerts_present(self, client, valid_smiles):
sa = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["structural_alerts"]
assert "pains" in sa
assert "brenk" in sa
assert "total_alert_count" in sa
def test_absorption_section_present(self, client, valid_smiles):
abs_ = client.post(BASE, json={"smiles": valid_smiles}).json()["result"]["absorption"]
assert "oral_bioavailability" in abs_
assert "caco2_permeability" in abs_
assert "hia" in abs_
@requires_rdkit
class TestADMETEdgeCases:
"""Edge cases and error handling."""
def test_invalid_smiles_returns_error(self, client, invalid_smiles):
resp = client.post(BASE, json={"smiles": invalid_smiles})
assert resp.status_code in (400, 422, 500)
body = resp.json()
assert "error" in body or "detail" in body
def test_empty_smiles_returns_422(self, client):
resp = client.post(BASE, json={"smiles": ""})
assert resp.status_code == 422
def test_missing_smiles_returns_422(self, client):
resp = client.post(BASE, json={})
assert resp.status_code == 422
def test_parametrized_molecules(self, client, sample_smiles):
"""Run every molecule in the parametrized fixture."""
name, smiles = sample_smiles
resp = client.post(BASE, json={"smiles": smiles})
assert resp.status_code == 200, f"{name} failed: {resp.text}"
result = resp.json()["result"]
assert result is not None
assert result["molecular_weight"] > 0