"""Tests for `handle_search` — the home page + primary user flow.""" from handlers import handle_search def test_empty_index_empty_query_shows_welcome(temp_db, csrf_session): resp = handle_search({}) assert resp["status"] == 200 body = resp["body"] assert "Your index is empty" in body # Links the welcome panel offers as equal-weight starting points. assert "/add" in body assert "/style" in body assert "/subscriptions" in body def test_empty_index_with_query_shows_no_results(temp_db, csrf_session): resp = handle_search({"q": ["rust"]}) assert resp["status"] == 200 assert "No results in your index" in resp["body"] def test_populated_index_with_matching_query_returns_results(seeded_db, csrf_session): resp = handle_search({"q": ["rust"]}) assert resp["status"] == 200 assert "Rust Intro" in resp["body"] # Page count shown in meta line. assert "4 pages indexed" in resp["body"] def test_query_only_matches_relevant_pages(seeded_db, csrf_session): resp = handle_search({"q": ["ocaml"]}) body = resp["body"] assert "Why OCaml" in body assert "Python Tips" not in body assert "Rust Intro" not in body def test_pagination_query_param_respected(seeded_db, csrf_session): """A high page number should still render without crashing.""" resp = handle_search({"q": ["example"], "p": ["99"]}) assert resp["status"] == 200 def test_trusted_sites_fallback_surfaces_when_query_matches_link_label(seeded_db, csrf_session): """Links extracted from indexed pages act as a fallback when direct results are absent or thin; labels are substring-matched case-insensitively.""" resp = handle_search({"q": ["advanced"]}) body = resp["body"] # The label "advanced rust guide" is on a link extracted from rust-intro. assert "advanced rust guide" in body assert "trusted sites" in body def test_page_count_in_meta_line(seeded_db, csrf_session): resp = handle_search({}) assert "4 pages indexed" in resp["body"] def test_csp_and_security_headers_not_in_handler_but_via_dispatch(seeded_db, csrf_session): """Handler itself returns no security headers; dispatch_request wraps them. This test documents the boundary so future refactors don't break assumptions.""" resp = handle_search({}) assert "headers" not in resp or "Content-Security-Policy" not in resp.get("headers", {})