| 1 | """Unavailable external-plan sources must never broaden retrieval.""" |
| 2 | |
| 3 | from unittest.mock import patch |
| 4 | |
| 5 | import pytest |
| 6 | |
| 7 | from lib import pipeline, planner |
| 8 | |
| 9 | |
| 10 | def plan_with(*source_lists): |
| 11 | return { |
| 12 | "intent": "opinion", |
| 13 | "freshness_mode": "balanced_recent", |
| 14 | "cluster_mode": "debate", |
| 15 | "subqueries": [ |
| 16 | {"label": f"part-{i}", "search_query": "test topic", "ranking_query": "test topic", |
| 17 | "sources": sources, "weight": 1.0} |
| 18 | for i, sources in enumerate(source_lists) |
| 19 | ], |
| 20 | } |
| 21 | |
| 22 | |
| 23 | @pytest.mark.parametrize("depth", ["quick", "default", "deep"]) |
| 24 | def test_all_unavailable_fails_before_retrieval(depth, capsys): |
| 25 | with patch.object(pipeline, "_retrieve_stream") as retrieve: |
| 26 | with pytest.raises(ValueError, match="No available planned sources"): |
| 27 | pipeline.run( |
| 28 | topic="test topic", config={}, mock=True, web_backend="none", |
| 29 | requested_sources=["reddit", "hackernews"], depth=depth, |
| 30 | external_plan=plan_with(["instagram"]), |
| 31 | ) |
| 32 | retrieve.assert_not_called() |
| 33 | assert "part-0" in capsys.readouterr().err |
| 34 | |
| 35 | |
| 36 | @pytest.mark.parametrize("depth", ["quick", "default", "deep"]) |
| 37 | def test_mixed_plan_skips_empty_subquery_without_expansion(depth, capsys): |
| 38 | with patch.object(pipeline, "_retrieve_stream", return_value=([], {})) as retrieve: |
| 39 | report = pipeline.run( |
| 40 | topic="test topic", config={}, mock=True, web_backend="none", |
| 41 | requested_sources=["reddit", "hackernews"], depth=depth, |
| 42 | external_plan=plan_with(["instagram"], ["reddit", "instagram"]), |
| 43 | ) |
| 44 | assert report.query_plan.subqueries[0].label == "part-1" |
| 45 | assert report.query_plan.subqueries[0].sources == ["reddit"] |
| 46 | assert retrieve.call_args_list |
| 47 | assert {call.kwargs["source"] for call in retrieve.call_args_list} == {"reddit"} |
| 48 | assert "part-0" in capsys.readouterr().err |
| 49 | |
| 50 | |
| 51 | def test_internal_empty_intersection_retains_fallback(): |
| 52 | plan = planner._sanitize_plan( |
| 53 | plan_with(["instagram"]), "test topic", ["reddit", "hackernews"], None, "default", |
| 54 | ) |
| 55 | assert plan.subqueries[0].sources == ["reddit", "hackernews"] |
| 56 |