| import pytest |
|
|
| from core.ports import ChatModel |
| from core.schemas import RouteResponse |
| from core.route import ( |
| route_from_chips, |
| route_from_message, |
| resolve_route, |
| step1_trace_line, |
| Step1Result, |
| ) |
|
|
|
|
| class StubChatModel(ChatModel): |
| """Records the schema it was called with; returns a canned RouteResponse.""" |
|
|
| def __init__(self, canned: RouteResponse): |
| self._canned = canned |
| self.calls = [] |
|
|
| def structured(self, messages, schema): |
| self.calls.append((messages, schema)) |
| return self._canned |
|
|
| def complete(self, messages): |
| raise NotImplementedError |
|
|
|
|
| def test_route_from_chips_is_identity(): |
| r = route_from_chips(["WASH"], ["Humanitarian Conditions"], ["Context"]) |
| assert isinstance(r, RouteResponse) |
| assert r.sectors == ["WASH"] |
| assert r.lens == ["Humanitarian Conditions"] |
| assert r.type == ["Context"] |
|
|
|
|
| def test_route_from_message_calls_model_with_route_schema(): |
| canned = RouteResponse(sectors=["WASH"], lens=[], type=[]) |
| stub = StubChatModel(canned) |
| r = route_from_message("water access in Aleppo?", stub) |
| assert r is canned |
| assert len(stub.calls) == 1 |
| _messages, schema = stub.calls[0] |
| assert schema is RouteResponse |
|
|
|
|
| def test_route_from_message_prompt_contains_axis_labels(): |
| stub = StubChatModel(RouteResponse(sectors=[], lens=[], type=[])) |
| route_from_message("q", stub) |
| messages, _schema = stub.calls[0] |
| blob = " ".join(m["content"] for m in messages) |
| assert "WASH" in blob and "Humanitarian Conditions" in blob and "Context" in blob |
|
|
|
|
| def test_resolve_route_message_partitions_out_of_scope(): |
| canned = RouteResponse(sectors=["WASH", "Health"], lens=["Impact"], type=[]) |
| res = resolve_route("message", "q", [], [], [], StubChatModel(canned)) |
| assert isinstance(res, Step1Result) |
| assert res.route.sectors == ["WASH", "Health"] |
| assert res.in_scope == ["WASH"] |
| assert res.out_of_scope == ["Health"] |
|
|
|
|
| def test_resolve_route_chips_no_model_needed(): |
| res = resolve_route("chips", "", ["Food Security"], ["At Risk"], ["Context"], None) |
| assert res.route.sectors == ["Food Security"] |
| assert res.in_scope == ["Food Security"] |
| assert res.out_of_scope == [] |
|
|
|
|
| def test_resolve_route_empty_question_raises(): |
| with pytest.raises(ValueError, match="question"): |
| resolve_route("message", " ", [], [], [], StubChatModel( |
| RouteResponse(sectors=[], lens=[], type=[]))) |
|
|
|
|
| def test_resolve_route_zero_in_scope_passes_through(): |
| canned = RouteResponse(sectors=["Health"], lens=[], type=[]) |
| res = resolve_route("message", "q", [], [], [], StubChatModel(canned)) |
| assert res.in_scope == [] |
| assert res.out_of_scope == ["Health"] |
|
|
|
|
| def test_resolve_route_chips_zero_in_scope_passes_through(): |
| res = resolve_route("chips", "", [], ["At Risk"], [], None) |
| assert res.in_scope == [] |
|
|
|
|
| def test_step1_trace_line_with_lens(): |
| res = Step1Result( |
| route=RouteResponse(sectors=["WASH", "Food Security"], lens=["Humanitarian Conditions"], type=[]), |
| in_scope=["WASH", "Food Security"], |
| out_of_scope=[], |
| ) |
| line = step1_trace_line(res) |
| assert line == "Step 1 β Understood request β WASH, Food Security (lens: Humanitarian Conditions)" |
|
|
|
|
| def test_step1_trace_line_without_lens(): |
| res = Step1Result( |
| route=RouteResponse(sectors=["WASH"], lens=[], type=[]), |
| in_scope=["WASH"], |
| out_of_scope=["Health"], |
| ) |
| assert step1_trace_line(res) == "Step 1 β Understood request β WASH, Health" |
|
|