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