| """Tests for app.services.state_manager — session state orchestration.""" |
|
|
| import pytest |
|
|
| from app.services.state_manager import ( |
| JOURNEY_STATES, |
| advance_journey, |
| can_advance_to, |
| get_current_journey_state, |
| init_session_state, |
| ) |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def mock_session_state(monkeypatch): |
| """Replace st.session_state with a plain dict for every test.""" |
| fake_state = {} |
| monkeypatch.setattr("app.services.state_manager.st.session_state", fake_state) |
| return fake_state |
|
|
|
|
| |
|
|
|
|
| def test_journey_states_order(): |
| assert JOURNEY_STATES == [ |
| "INGEST", |
| "PRESCREEN", |
| "VALIDATE_TRIALS", |
| "GAP_FOLLOWUP", |
| "SUMMARY", |
| ] |
|
|
|
|
| |
|
|
|
|
| def test_init_sets_all_defaults(mock_session_state): |
| init_session_state() |
| assert mock_session_state["journey_state"] == "INGEST" |
| assert mock_session_state["parlant_session_id"] is None |
| assert mock_session_state["parlant_agent_id"] is None |
| assert mock_session_state["patient_profile"] is None |
| assert mock_session_state["uploaded_files"] == [] |
| assert mock_session_state["search_anchors"] is None |
| assert mock_session_state["trial_candidates"] == [] |
| assert mock_session_state["eligibility_ledger"] == [] |
| assert mock_session_state["last_event_offset"] == 0 |
|
|
|
|
| def test_init_does_not_overwrite_existing(mock_session_state): |
| mock_session_state["journey_state"] = "PRESCREEN" |
| init_session_state() |
| assert mock_session_state["journey_state"] == "PRESCREEN" |
|
|
|
|
| |
|
|
|
|
| def test_get_current_journey_state_default(mock_session_state): |
| assert get_current_journey_state() == "INGEST" |
|
|
|
|
| def test_get_current_journey_state_returns_set_value(mock_session_state): |
| mock_session_state["journey_state"] = "GAP_FOLLOWUP" |
| assert get_current_journey_state() == "GAP_FOLLOWUP" |
|
|
|
|
| |
|
|
|
|
| def test_advance_forward(mock_session_state): |
| mock_session_state["journey_state"] = "INGEST" |
| advance_journey("PRESCREEN") |
| assert mock_session_state["journey_state"] == "PRESCREEN" |
|
|
|
|
| def test_advance_multiple_steps_forward(mock_session_state): |
| mock_session_state["journey_state"] = "INGEST" |
| advance_journey("VALIDATE_TRIALS") |
| assert mock_session_state["journey_state"] == "VALIDATE_TRIALS" |
|
|
|
|
| def test_advance_refuses_backward(mock_session_state): |
| mock_session_state["journey_state"] = "VALIDATE_TRIALS" |
| with pytest.raises(ValueError, match="backward"): |
| advance_journey("INGEST") |
| assert mock_session_state["journey_state"] == "VALIDATE_TRIALS" |
|
|
|
|
| def test_advance_same_state_is_idempotent(mock_session_state): |
| mock_session_state["journey_state"] = "PRESCREEN" |
| advance_journey("PRESCREEN") |
| assert mock_session_state["journey_state"] == "PRESCREEN" |
|
|
|
|
| def test_advance_invalid_state(mock_session_state): |
| mock_session_state["journey_state"] = "INGEST" |
| with pytest.raises(ValueError, match="Invalid"): |
| advance_journey("NONEXISTENT") |
|
|
|
|
| |
|
|
|
|
| def test_can_advance_to_prescreen_without_profile(mock_session_state): |
| init_session_state() |
| assert can_advance_to("PRESCREEN") is False |
|
|
|
|
| def test_can_advance_to_prescreen_with_profile(mock_session_state): |
| init_session_state() |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| assert can_advance_to("PRESCREEN") is True |
|
|
|
|
| def test_can_advance_to_validate_trials_without_profile(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "PRESCREEN" |
| assert can_advance_to("VALIDATE_TRIALS") is False |
|
|
|
|
| def test_can_advance_to_validate_trials_with_profile(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "PRESCREEN" |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| assert can_advance_to("VALIDATE_TRIALS") is True |
|
|
|
|
| def test_can_advance_to_gap_followup_without_candidates(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "VALIDATE_TRIALS" |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| assert can_advance_to("GAP_FOLLOWUP") is False |
|
|
|
|
| def test_can_advance_to_gap_followup_with_candidates(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "VALIDATE_TRIALS" |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| mock_session_state["trial_candidates"] = [{"nct_id": "NCT001"}] |
| assert can_advance_to("GAP_FOLLOWUP") is True |
|
|
|
|
| def test_can_advance_to_summary_without_ledger(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "GAP_FOLLOWUP" |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| mock_session_state["trial_candidates"] = [{"nct_id": "NCT001"}] |
| assert can_advance_to("SUMMARY") is False |
|
|
|
|
| def test_can_advance_to_summary_with_ledger(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "GAP_FOLLOWUP" |
| mock_session_state["patient_profile"] = {"demographics": {"age": 62}} |
| mock_session_state["trial_candidates"] = [{"nct_id": "NCT001"}] |
| mock_session_state["eligibility_ledger"] = [{"trial_id": "NCT001", "result": "ELIGIBLE"}] |
| assert can_advance_to("SUMMARY") is True |
|
|
|
|
| def test_can_advance_to_invalid_state(mock_session_state): |
| init_session_state() |
| assert can_advance_to("NONEXISTENT") is False |
|
|
|
|
| def test_can_advance_to_backward_state(mock_session_state): |
| init_session_state() |
| mock_session_state["journey_state"] = "VALIDATE_TRIALS" |
| assert can_advance_to("INGEST") is False |
|
|