"""SheetExtractorAgent fallback ladder tests (bare-list wrap + compact retry).""" from unittest.mock import patch from backend.agents.base import AgentScope, AgentUsage from backend.agents.extractors import SheetExtractorAgent, _wrap_bare_list def _scope(): return AgentScope( scope_id="sheet:4", payload={"page": {"page_number": 4, "base64": "QUJD"}}, ) def _objects(n=2): return [ { "object_id": f"obj-{i}", "object_type": "equipment", "category": "mechanical", "name": f"RTU-{i}", "source_text": f"RTU-{i}", "confidence": "high", } for i in range(n) ] def test_wrap_bare_list_builds_sheet_envelope(): wrapped = _wrap_bare_list(_objects(3), page_number=4) assert wrapped["sheet"] == {} assert len(wrapped["objects"]) == 3 def test_wrap_bare_list_passes_dicts_and_none_through(): assert _wrap_bare_list({"sheet": {}, "objects": []}, 1) == {"sheet": {}, "objects": []} assert _wrap_bare_list(None, 1) is None def test_run_accepts_bare_list_response(): agent = SheetExtractorAgent(usage=AgentUsage()) with patch("backend.agents.extractors.call_json", return_value=_objects(5)) as mock_call: result = agent.run(_scope()) assert not result.error assert len(result.artifacts) == 1 sheet = result.artifacts[0] assert sheet["page_number"] == 4 assert len(sheet["assertions"]) == 5 # No compact retry needed when the first call yields data. assert mock_call.call_count == 1 # Reasoning knobs are forwarded (None when config is blank in tests). assert "reasoning_effort" in mock_call.call_args.kwargs assert "reasoning_max_tokens" in mock_call.call_args.kwargs def test_run_compact_retry_after_hard_failure(): agent = SheetExtractorAgent(usage=AgentUsage()) with patch("backend.agents.extractors.call_json", side_effect=[None, {"sheet": {"sheet_number": "A102"}, "objects": _objects(2)}]) as mock_call: result = agent.run(_scope()) assert not result.error assert result.artifacts[0]["sheet_number"] == "A102" assert mock_call.call_count == 2 # Second call carried the compact suffix. assert "COMPACT RETRY" in mock_call.call_args_list[1].kwargs["user_text"] def test_run_fails_only_after_both_attempts_miss(): agent = SheetExtractorAgent(usage=AgentUsage()) with patch("backend.agents.extractors.call_json", return_value=None) as mock_call: result = agent.run(_scope()) assert result.error == "no structured extraction returned" assert mock_call.call_count == 2