"""Tests for scripts/search.py — output formatting functions. Pure-function tests (no network, no mocking) covering: * format_brief — title/url/content rendering, snippet truncation, suggestions, answers, empty input * format_urls — basic list, skip-empty-url, empty input * _format_results — json/urls/brief/csv dispatch + fetched-pages section """ import json import types from search import format_brief, format_urls, _format_results # ----- format_brief ----- def test_format_brief_basic(): """Basic brief output includes title, URL, and content lines.""" results = {"results": [ {"title": "Hello World", "url": "https://example.com/1", "content": "A short snippet."}, ]} out = format_brief(results) assert "1. Hello World" in out assert "https://example.com/1" in out assert "A short snippet." in out def test_format_brief_no_content(): """When content is empty/missing, no content line is emitted.""" results = {"results": [ {"title": "No Snippet", "url": "https://example.com/2"}, ]} out = format_brief(results) assert "1. No Snippet" in out assert "https://example.com/2" in out # Only title + url lines plus a trailing blank line — no content line. lines = out.split("\n") assert len(lines) == 3 assert lines[2] == "" def test_format_brief_snippet_truncation(): """snippet_len > 0 truncates content to that many characters.""" long_text = "abcdefghijklmnopqrstuvwxyz" results = {"results": [ {"title": "T", "url": "https://example.com", "content": long_text}, ]} out = format_brief(results, snippet_len=5) assert "abcde" in out assert "abcdef" not in out # truncated beyond 5 chars def test_format_brief_with_suggestions(): """Suggestions list is rendered as a comma-separated line.""" results = { "results": [{"title": "T", "url": "https://example.com", "content": "c"}], "suggestions": ["python asyncio", "python requests"], } out = format_brief(results) assert "Suggestions: python asyncio, python requests" in out def test_format_brief_with_answers(): """Each answer is emitted on its own 'Answer:' line.""" results = { "results": [{"title": "T", "url": "https://example.com", "content": "c"}], "answers": ["42", "the answer"], } out = format_brief(results) assert "Answer: 42" in out assert "Answer: the answer" in out def test_format_brief_empty_results(): """Empty results list (or missing key) yields an empty string.""" assert format_brief({"results": []}) == "" assert format_brief({}) == "" # ----- format_urls ----- def test_format_urls_basic(): """Each result URL appears on its own line.""" results = {"results": [ {"url": "https://a.com/1"}, {"url": "https://b.com/2"}, ]} out = format_urls(results) assert out == "https://a.com/1\nhttps://b.com/2" def test_format_urls_skips_empty(): """Results with empty/missing URLs are skipped.""" results = {"results": [ {"url": "https://a.com/1"}, {"url": ""}, {"title": "no url here"}, {"url": "https://b.com/2"}, ]} out = format_urls(results) assert out == "https://a.com/1\nhttps://b.com/2" def test_format_urls_empty_results(): """Empty results yield an empty string.""" assert format_urls({"results": []}) == "" assert format_urls({}) == "" # ----- _format_results ----- def test_format_results_json(): """json format emits valid, parseable JSON mirroring the input.""" results = {"results": [ {"title": "T", "url": "https://example.com", "content": "c"}, ], "suggestions": ["x"]} args = types.SimpleNamespace(format="json") out = _format_results(results, args) parsed = json.loads(out) assert parsed == results def test_format_results_brief(): """brief format dispatches to format_brief.""" results = {"results": [ {"title": "T", "url": "https://example.com", "content": "snippet"}, ]} args = types.SimpleNamespace(format="brief", snippet_len=0, fetch=0) out = _format_results(results, args) assert "1. T" in out assert "https://example.com" in out assert "snippet" in out def test_format_results_urls(): """urls format dispatches to format_urls.""" results = {"results": [ {"url": "https://a.com/1"}, {"url": "https://b.com/2"}, ]} args = types.SimpleNamespace(format="urls") out = _format_results(results, args) assert out == "https://a.com/1\nhttps://b.com/2" def test_format_results_csv_smoke(): """csv format emits the header row + one row per result (smoke test).""" results = {"results": [ {"title": "T1", "url": "https://a.com", "engine": "google", "score": 1.0, "published_date": "2024-01-01", "content": "snip"}, ]} args = types.SimpleNamespace(format="csv", snippet_len=0, fetch=0) out = _format_results(results, args) assert "title,url,engine,score,published_date,content" in out assert "T1" in out assert "https://a.com" in out def test_format_results_brief_with_fetched(): """brief + args.fetch>0 + results['fetched'] appends a fetched section.""" results = { "results": [{"title": "T", "url": "https://example.com", "content": "c"}], "fetched": [ {"url": "https://example.com", "status": "ok", "text": "Full page content here."}, ], } args = types.SimpleNamespace(format="brief", snippet_len=0, fetch=3) out = _format_results(results, args) assert "FETCHED PAGES (1 pages)" in out assert "--- https://example.com ---" in out assert "Full page content here." in out def test_format_results_brief_with_fetched_error(): """Fetched entries with status != 'ok' render an [ERROR: ...] line.""" results = { "results": [{"title": "T", "url": "https://example.com", "content": "c"}], "fetched": [ {"url": "https://broken.example", "status": "error", "error": "timeout"}, ], } args = types.SimpleNamespace(format="brief", snippet_len=0, fetch=3) out = _format_results(results, args) assert "FETCHED PAGES (1 pages)" in out assert "[ERROR: timeout]" in out