"""Tests for the marchwarden CLI.""" from unittest.mock import patch from click.testing import CliRunner from cli.main import cli, render_result from researchers.web.models import ( Citation, ConfidenceFactors, CostMetadata, DiscoveryEvent, Gap, GapCategory, OpenQuestion, ResearchResult, ) from rich.console import Console def _fixture_result() -> ResearchResult: return ResearchResult( answer="Tomatoes, peppers, squash, and beans grow well in Utah.", citations=[ Citation( source="web", locator="https://extension.usu.edu/yard-and-garden", title="USU Extension — Yard and Garden", snippet="USU recommends warm-season crops for Utah's climate.", raw_excerpt="Tomatoes, peppers, and squash thrive in Utah summers.", confidence=0.9, ), ], gaps=[ Gap( topic="Microclimate variation", category=GapCategory.SCOPE_EXCEEDED, detail="Did not investigate elevation-specific recommendations.", ), ], discovery_events=[ DiscoveryEvent( type="related_research", suggested_researcher="docs", query="Utah USDA hardiness zones", reason="Zone-specific guidance would improve answer.", ), ], open_questions=[ OpenQuestion( question="What are the best cool-season crops?", context="Answer focused on warm-season crops.", priority="medium", ), ], confidence=0.82, confidence_factors=ConfidenceFactors( num_corroborating_sources=3, source_authority="high", contradiction_detected=False, query_specificity_match=0.85, budget_exhausted=False, recency="current", ), cost_metadata=CostMetadata( tokens_used=4321, iterations_run=3, wall_time_sec=12.5, budget_exhausted=False, model_id="claude-sonnet-4-6", ), trace_id="trace-abc-123", ) class TestRenderResult: def test_renders_all_sections(self): console = Console(record=True, width=120) render_result(_fixture_result(), console) out = console.export_text() assert "Tomatoes" in out assert "USU Extension" in out assert "scope_exceeded" in out assert "related_research" in out assert "cool-season" in out assert "Confidence" in out assert "claude-sonnet-4-6" in out assert "trace-abc-123" in out class TestAskCommand: def test_ask_invokes_mcp_and_renders(self): runner = CliRunner() fixture = _fixture_result() async def fake_call(question, depth, max_iterations, token_budget): assert question == "What grows in Utah?" assert depth == "shallow" assert max_iterations == 2 assert token_budget == 5000 return fixture with patch("cli.main.call_research_tool", side_effect=fake_call): result = runner.invoke( cli, [ "ask", "What grows in Utah?", "--depth", "shallow", "--max-iterations", "2", "--budget", "5000", ], ) assert result.exit_code == 0, result.output assert "Tomatoes" in result.output assert "trace-abc-123" in result.output def test_ask_handles_error(self): runner = CliRunner() async def boom(**kwargs): raise RuntimeError("mcp went sideways") with patch("cli.main.call_research_tool", side_effect=boom): result = runner.invoke(cli, ["ask", "anything"]) assert result.exit_code == 1 assert "mcp went sideways" in result.output