"""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