"""Tests for --queries-file batch mode: exit code semantics and JSON schema. Covers: * Exit codes: all-empty (no error) -> 2, all-error -> 1, partial -> 0 * Batch JSON shape: {"schema_version": "1.0", "queries": [...]} * Per-entry "status" field ("ok"/"error") and result/error payload shape Uses in-process main() with search_multi mocked so no network is touched. ``--retry 0`` is passed to avoid backoff sleeps when search_multi raises. """ import json import sys import urllib.error from unittest.mock import patch import pytest import search as search_mod from search import main def _batch_argv(queries_file_path, fmt="json"): """Build the sys.argv for a batch main() invocation. ``--retry 0`` keeps failing tests fast (no backoff sleeps). """ return ["search.py", "-i", "https://x.example.com", "--queries-file", str(queries_file_path), "--format", fmt, "--retry", "0"] # ===== batch exit code semantics ===== def test_batch_all_empty_results_exits_2(tmp_path, capsys): """All queries return empty results (no error) -> exit 2.""" qf = tmp_path / "queries.txt" qf.write_text("q1\nq2\n", encoding="utf-8") empty = {"results": []} with patch.object(search_mod, "search_multi", return_value=empty): with pytest.raises(SystemExit) as exc_info: with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() assert exc_info.value.code == 2 def test_batch_all_error_exits_1(tmp_path, capsys): """All queries error -> exit 1.""" qf = tmp_path / "queries.txt" qf.write_text("q1\nq2\n", encoding="utf-8") err = urllib.error.URLError("connection refused") with patch.object(search_mod, "search_multi", side_effect=err): with pytest.raises(SystemExit) as exc_info: with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() assert exc_info.value.code == 1 def test_batch_partial_results_exits_0(tmp_path, capsys): """At least one query returns results -> exit 0.""" qf = tmp_path / "queries.txt" qf.write_text("q1\nq2\n", encoding="utf-8") with_data = {"results": [{"url": "https://a.com/1", "title": "A"}]} empty = {"results": []} with patch.object(search_mod, "search_multi", side_effect=[with_data, empty]): with pytest.raises(SystemExit) as exc_info: with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() assert exc_info.value.code == 0 # ===== batch unified JSON schema ===== def test_batch_json_has_schema_version_and_queries(tmp_path, capsys): """Batch JSON output wraps entries in {schema_version, queries}.""" qf = tmp_path / "queries.txt" qf.write_text("q1\n", encoding="utf-8") mock_results = {"results": [{"url": "https://a.com/1", "title": "A"}]} with patch.object(search_mod, "search_multi", return_value=mock_results): with pytest.raises(SystemExit): with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() out, _ = capsys.readouterr() data = json.loads(out) assert data["schema_version"] == "1.0" assert "queries" in data assert isinstance(data["queries"], list) assert len(data["queries"]) == 1 def test_batch_entry_has_status_field(tmp_path, capsys): """Each batch entry has a 'status' field of 'ok' or 'error'.""" qf = tmp_path / "queries.txt" qf.write_text("q1\nq2\n", encoding="utf-8") ok_results = {"results": [{"url": "https://a.com/1", "title": "A"}]} err = urllib.error.URLError("down") with patch.object(search_mod, "search_multi", side_effect=[ok_results, err]): with pytest.raises(SystemExit): with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() out, _ = capsys.readouterr() data = json.loads(out) statuses = [e["status"] for e in data["queries"]] assert "ok" in statuses assert "error" in statuses def test_batch_success_entry_has_results_error_entry_has_error_and_code(tmp_path, capsys): """ok entry has 'results'; error entry has 'error' and 'error_code'.""" qf = tmp_path / "queries.txt" qf.write_text("q1\nq2\n", encoding="utf-8") ok_results = {"results": [{"url": "https://a.com/1", "title": "A"}]} err = urllib.error.URLError("connection refused") with patch.object(search_mod, "search_multi", side_effect=[ok_results, err]): with pytest.raises(SystemExit): with patch.object(sys, "argv", _batch_argv(qf)): with patch.object(search_mod, "setup_logging"): main() out, _ = capsys.readouterr() data = json.loads(out) ok_entry = next(e for e in data["queries"] if e["status"] == "ok") assert "results" in ok_entry err_entry = next(e for e in data["queries"] if e["status"] == "error") assert "error" in err_entry assert "error_code" in err_entry # URLError -> E_NETWORK per classify_error assert err_entry["error_code"] == "E_NETWORK"