Spaces:
Running
Running
Download bioai-platform/backend/tests/test_smoke_admet.py from Samad14/bio-nexus-api: direct link, hf CLI and curl.
- Browser
- Download file 5.91 kB
-
https://huggingface.co/spaces/Samad14/bio-nexus-api/resolve/main/bioai-platform/backend/tests/test_smoke_admet.py
- Command line
-
hf download hf://spaces/Samad14/bio-nexus-api/bioai-platform/backend/tests/test_smoke_admet.py
-
curl -L -o test_smoke_admet.py https://huggingface.co/spaces/Samad14/bio-nexus-api/resolve/main/bioai-platform/backend/tests/test_smoke_admet.py
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" | |
| 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}" | |
| 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" | |
| 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 | |
| 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_ | |
| 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 | |