TrialPath / tests /test_integration.py
yakilee's picture
test: update tests for evidence-linked mock data and new features
f8adedd
"""Integration tests: verify FE components correctly consume BE models."""
from __future__ import annotations
import json
from app.components.gap_card import render_gap_card
from app.components.profile_card import render_profile_card
from app.components.progress_tracker import render_progress_tracker
from app.components.trial_card import render_trial_card
from app.services.mock_data import (
MOCK_ELIGIBILITY_LEDGERS,
MOCK_PATIENT_PROFILE,
MOCK_TRIAL_CANDIDATES,
)
from app.services.state_manager import JOURNEY_STATES
from trialpath.models import (
EligibilityLedger,
PatientProfile,
SearchAnchors,
TrialCandidate,
)
class TestMockDataIntegrity:
"""Verify mock data uses real BE models correctly."""
def test_mock_profile_is_patient_profile(self):
assert isinstance(MOCK_PATIENT_PROFILE, PatientProfile)
def test_mock_trials_are_trial_candidates(self):
for t in MOCK_TRIAL_CANDIDATES:
assert isinstance(t, TrialCandidate)
def test_mock_ledgers_are_eligibility_ledgers(self):
for lg in MOCK_ELIGIBILITY_LEDGERS:
assert isinstance(lg, EligibilityLedger)
def test_mock_ledger_nct_ids_match_trials(self):
trial_ids = {t.nct_id for t in MOCK_TRIAL_CANDIDATES}
ledger_ids = {lg.nct_id for lg in MOCK_ELIGIBILITY_LEDGERS}
assert ledger_ids == trial_ids
def test_mock_profile_has_minimum_prescreen_data(self):
assert MOCK_PATIENT_PROFILE.has_minimum_prescreen_data()
def test_mock_profile_serializes_to_json(self):
data = MOCK_PATIENT_PROFILE.model_dump_json()
restored = PatientProfile.model_validate_json(data)
assert restored.patient_id == MOCK_PATIENT_PROFILE.patient_id
def test_mock_trials_serialize_to_json(self):
for t in MOCK_TRIAL_CANDIDATES:
data = t.model_dump_json()
restored = TrialCandidate.model_validate_json(data)
assert restored.nct_id == t.nct_id
def test_mock_ledgers_serialize_to_json(self):
for lg in MOCK_ELIGIBILITY_LEDGERS:
data = lg.model_dump_json()
restored = EligibilityLedger.model_validate_json(data)
assert restored.nct_id == lg.nct_id
class TestComponentModelIntegration:
"""Verify FE components produce correct output from BE models."""
def test_profile_card_renders_mock_profile(self):
spec = render_profile_card(MOCK_PATIENT_PROFILE)
assert spec["patient_id"] == "MOCK-P001"
assert spec["has_minimum_prescreen_data"] is True
assert len(spec["biomarkers"]) == 3
def test_trial_card_renders_green_trial(self):
# MOCK-NCT-FLAURA2 is LIKELY_ELIGIBLE -> green
trial = MOCK_TRIAL_CANDIDATES[1]
ledger = MOCK_ELIGIBILITY_LEDGERS[1]
spec = render_trial_card(trial, ledger)
assert spec["traffic_light"] == "green"
assert spec["nct_id"] == "MOCK-NCT-FLAURA2"
def test_trial_card_renders_yellow_trial(self):
# MOCK-NCT-KEYNOTE999 is UNCERTAIN -> yellow
trial = MOCK_TRIAL_CANDIDATES[0]
ledger = MOCK_ELIGIBILITY_LEDGERS[0]
spec = render_trial_card(trial, ledger)
assert spec["traffic_light"] == "yellow"
assert len(spec["gaps"]) == 1
def test_trial_card_renders_red_trial(self):
# MOCK-NCT-CM817 is LIKELY_INELIGIBLE -> red
trial = MOCK_TRIAL_CANDIDATES[2]
ledger = MOCK_ELIGIBILITY_LEDGERS[2]
spec = render_trial_card(trial, ledger)
assert spec["traffic_light"] == "red"
def test_gap_card_renders_from_ledger_gap(self):
ledger = MOCK_ELIGIBILITY_LEDGERS[0]
gap = ledger.gaps[0]
spec = render_gap_card(gap, affected_trials=["MOCK-NCT-KEYNOTE999"])
assert "Brain MRI" in spec["description"]
assert spec["importance_color"] == "red" # high importance
def test_progress_tracker_all_states(self):
for state in JOURNEY_STATES:
spec = render_progress_tracker(state)
assert len(spec["steps"]) == 5
current_steps = [s for s in spec["steps"] if s["status"] == "current"]
assert len(current_steps) == 1
class TestDoctorPacketGeneration:
"""Verify doctor packet export generates valid JSON/Markdown."""
def test_json_packet_structure(self):
profile = MOCK_PATIENT_PROFILE
ledgers = MOCK_ELIGIBILITY_LEDGERS
eligible = sum(1 for lg in ledgers if lg.traffic_light == "green")
uncertain = sum(1 for lg in ledgers if lg.traffic_light == "yellow")
ineligible = sum(1 for lg in ledgers if lg.traffic_light == "red")
total_gaps = sum(len(lg.gaps) for lg in ledgers)
packet = {
"patient_id": profile.patient_id,
"summary": {
"eligible_count": eligible,
"uncertain_count": uncertain,
"ineligible_count": ineligible,
"total_gaps": total_gaps,
},
"trials": [
{
"nct_id": lg.nct_id,
"overall_assessment": lg.overall_assessment.value,
"met": lg.met_count,
"not_met": lg.not_met_count,
"unknown": lg.unknown_count,
"gaps": [g.description for g in lg.gaps],
}
for lg in ledgers
],
}
serialized = json.dumps(packet, indent=2)
restored = json.loads(serialized)
assert restored["patient_id"] == "MOCK-P001"
assert restored["summary"]["eligible_count"] == 1
assert restored["summary"]["uncertain_count"] == 1
assert restored["summary"]["ineligible_count"] == 1
assert restored["summary"]["total_gaps"] == 2
assert len(restored["trials"]) == 3
def test_all_trial_nct_ids_in_packet(self):
ledgers = MOCK_ELIGIBILITY_LEDGERS
packet_ids = [lg.nct_id for lg in ledgers]
expected_ids = ["MOCK-NCT-KEYNOTE999", "MOCK-NCT-FLAURA2", "MOCK-NCT-CM817"]
assert packet_ids == expected_ids
class TestSearchAnchorsFromProfile:
"""Verify BE model can generate SearchAnchors from PatientProfile."""
def test_profile_to_search_anchors(self):
profile = MOCK_PATIENT_PROFILE
assert profile.diagnosis is not None
assert profile.performance_status is not None
anchors = SearchAnchors(
condition=profile.diagnosis.primary_condition,
subtype=profile.diagnosis.histology,
biomarkers=[b.name for b in profile.biomarkers],
stage=profile.diagnosis.stage,
age=profile.demographics.age,
performance_status_max=profile.performance_status.value,
)
assert anchors.condition == "Non-Small Cell Lung Cancer"
assert "EGFR" in anchors.biomarkers
assert anchors.stage == "IIIB"
assert anchors.age == 62
def test_search_anchors_serializes(self):
profile = MOCK_PATIENT_PROFILE
assert profile.diagnosis is not None
anchors = SearchAnchors(
condition=profile.diagnosis.primary_condition,
biomarkers=[b.name for b in profile.biomarkers],
stage=profile.diagnosis.stage,
)
data = anchors.model_dump_json()
restored = SearchAnchors.model_validate_json(data)
assert restored.condition == anchors.condition