import copy import pytest from central.app.ai.fake_provider import DeterministicFakeSemanticProvider from central.app.ai.llm_provider import LlmAnswerError, LlmEvalItem from central.app.ai.provider import ProviderContractError, ProviderTransportError def _item(work_item_id="w1"): return LlmEvalItem( work_item_id=work_item_id, description="AGUA MINERAL 500ML", normalized_description="AGUA MINERAL 500ML", brand=None, unit="ML", segment="mercado", candidates=[("22011000", "AGUAS MINERAIS", 0.9), ("22019000", "OUTRAS AGUAS", 0.4)], ) @pytest.mark.parametrize( ("scenario", "selected", "abstain"), [ ("CORRECT", "22011000", False), ("WRONG", "22019000", False), ("ABSTAIN", None, True), ], ) def test_fake_provider_returns_deterministic_valid_answers(scenario, selected, abstain): provider = DeterministicFakeSemanticProvider() answers = provider.select_batch([_item()], scenario=scenario) assert answers == provider.select_batch([_item()], scenario=scenario) assert answers[0]["work_item_id"] == "w1" assert answers[0]["selected_ncm"] == selected assert answers[0]["abstain"] is abstain assert provider.calls == 2 @pytest.mark.parametrize("scenario", ["INVALID_JSON", "MISSING_ITEM", "DUPLICATE_ITEM", "OUT_OF_CANDIDATE"]) def test_fake_provider_rejects_contract_scenarios_without_mutating_items(scenario): provider = DeterministicFakeSemanticProvider() items = [_item("w1"), _item("w2")] original = copy.deepcopy(items) with pytest.raises((LlmAnswerError, ProviderContractError)): provider.select_batch(items, scenario=scenario) assert items == original assert provider.calls == 1 @pytest.mark.parametrize("scenario", ["TIMEOUT", "429", "500"]) def test_fake_provider_raises_typed_transport_errors(scenario): provider = DeterministicFakeSemanticProvider() with pytest.raises(ProviderTransportError): provider.select_batch([_item()], scenario=scenario) assert provider.calls == 1 def test_fake_provider_rejects_unknown_scenarios(): with pytest.raises(ValueError): DeterministicFakeSemanticProvider().select_batch([_item()], scenario="UNKNOWN")