beacon-trial-finder / tests /test_models.py
KevinIsInCoding
test(critic-fixes): address brittleness, over-mocking, and missing edge cases (#20)
3d5e588 unverified
Raw
History Blame Contribute Delete
4.45 kB
"""Tests for models.py β€” pure deterministic functions only."""
from __future__ import annotations
import math
import pytest
from models import PatientProfile, geocode_zip, haversine_miles
@pytest.fixture
def minimal_patient(**kwargs):
"""Minimal PatientProfile factory β€” callers only specify fields that vary."""
def _make(**overrides):
defaults = dict(disease="ALS", age=45, onset_months=6, zip_code="10001", lat=40.75, lon=-73.99)
defaults.update(overrides)
return PatientProfile(**defaults)
return _make
# ---------------------------------------------------------------------------
# haversine_miles
# ---------------------------------------------------------------------------
class TestHaversineMiles:
def test_same_point_is_zero(self):
assert haversine_miles(42.0, -71.0, 42.0, -71.0) == 0.0
def test_known_distance_boston_nyc(self):
# Boston (42.3601, -71.0589) to NYC (40.7128, -74.0060) β‰ˆ 190 miles
dist = haversine_miles(42.3601, -71.0589, 40.7128, -74.0060)
assert 185 < dist < 195
def test_symmetry(self):
d1 = haversine_miles(42.0, -71.0, 34.0, -118.0)
d2 = haversine_miles(34.0, -118.0, 42.0, -71.0)
assert math.isclose(d1, d2, rel_tol=1e-9)
def test_short_distance(self):
# Two points ~1 mile apart
dist = haversine_miles(42.3370, -71.1061, 42.3514, -71.1061)
assert 0.8 < dist < 1.2
def test_returns_float(self):
result = haversine_miles(0.0, 0.0, 0.0, 1.0)
assert isinstance(result, float)
# ---------------------------------------------------------------------------
# PatientProfile.summary
# ---------------------------------------------------------------------------
class TestPatientProfileSummary:
def test_summary_contains_disease(self, als_patient):
assert "Amyotrophic Lateral Sclerosis" in als_patient.summary()
def test_summary_contains_age(self, als_patient):
assert "52" in als_patient.summary()
def test_summary_contains_onset_months(self, als_patient):
assert "18" in als_patient.summary()
def test_summary_contains_diagnosis_months(self, als_patient):
assert "12" in als_patient.summary()
def test_summary_contains_zip(self, als_patient):
assert "02115" in als_patient.summary()
def test_summary_contains_radius(self, als_patient):
assert "100" in als_patient.summary()
def test_summary_contains_benchmarks(self, als_patient):
text = als_patient.summary()
assert "alsfrs_r" in text
assert "38" in text
def test_summary_no_benchmarks(self, minimal_patient):
assert "Benchmarks" not in minimal_patient().summary()
def test_summary_phase_labels(self, minimal_patient):
text = minimal_patient(phases=["2", "3", "0", "na"]).summary()
assert "Phase 2" in text
assert "Phase 3" in text
assert "Early Phase 1" in text
assert "Not Applicable" in text
def test_summary_study_type_defaults(self, minimal_patient):
text = minimal_patient().summary()
assert "Clinical trials" in text
assert "Observational" not in text
assert "EAP" not in text
def test_summary_include_observational(self, minimal_patient):
assert "Observational" in minimal_patient(include_observational=True).summary()
def test_summary_include_eap(self, minimal_patient):
assert "EAP" in minimal_patient(include_eap=True).summary()
def test_summary_returns_string(self, als_patient):
assert isinstance(als_patient.summary(), str)
def test_summary_no_phases(self, minimal_patient):
assert "Phases:" not in minimal_patient().summary()
# ---------------------------------------------------------------------------
# geocode_zip β€” httpx mocked
# ---------------------------------------------------------------------------
class TestGeocodeZip:
def test_returns_lat_lon_from_response(self, httpx_mock):
httpx_mock.add_response(json=[{"lat": "42.3370", "lon": "-71.1061"}])
lat, lon = geocode_zip("02115", "US")
assert lat == pytest.approx(42.3370)
assert lon == pytest.approx(-71.1061)
def test_empty_results_raises_value_error(self, httpx_mock):
httpx_mock.add_response(json=[])
with pytest.raises(ValueError, match="Cannot geocode"):
geocode_zip("00000", "US")