""" test_searcher.py -- Tests for both search() (CLI) and search_memories() (API). Uses the real ChromaDB fixtures from conftest.py for integration tests, plus mock-based tests for error paths. """ from unittest.mock import MagicMock, patch import pytest from mempalace.searcher import SearchError, search, search_memories # ── search_memories (API) ────────────────────────────────────────────── class TestSearchMemories: def test_basic_search(self, palace_path, seeded_collection): result = search_memories("JWT authentication", palace_path) assert "results" in result assert len(result["results"]) > 0 assert result["query"] == "JWT authentication" def test_wing_filter(self, palace_path, seeded_collection): result = search_memories("planning", palace_path, wing="notes") assert all(r["wing"] == "notes" for r in result["results"]) def test_room_filter(self, palace_path, seeded_collection): result = search_memories("database", palace_path, room="backend") assert all(r["room"] == "backend" for r in result["results"]) def test_wing_and_room_filter(self, palace_path, seeded_collection): result = search_memories("code", palace_path, wing="project", room="frontend") assert all(r["wing"] == "project" and r["room"] == "frontend" for r in result["results"]) def test_n_results_limit(self, palace_path, seeded_collection): result = search_memories("code", palace_path, n_results=2) assert len(result["results"]) <= 2 def test_no_palace_returns_error(self, tmp_path): result = search_memories("anything", str(tmp_path / "missing")) assert "error" in result def test_result_fields(self, palace_path, seeded_collection): result = search_memories("authentication", palace_path) hit = result["results"][0] assert "text" in hit assert "wing" in hit assert "room" in hit assert "source_file" in hit assert "similarity" in hit assert isinstance(hit["similarity"], float) def test_search_memories_query_error(self): """search_memories returns error dict when query raises.""" mock_col = MagicMock() mock_col.query.side_effect = RuntimeError("query failed") mock_client = MagicMock() mock_client.get_collection.return_value = mock_col with patch("mempalace.searcher.chromadb.PersistentClient", return_value=mock_client): result = search_memories("test", "/fake/path") assert "error" in result assert "query failed" in result["error"] def test_search_memories_filters_in_result(self, palace_path, seeded_collection): result = search_memories("test", palace_path, wing="project", room="backend") assert result["filters"]["wing"] == "project" assert result["filters"]["room"] == "backend" # ── search() (CLI print function) ───────────────────────────────────── class TestSearchCLI: def test_search_prints_results(self, palace_path, seeded_collection, capsys): search("JWT authentication", palace_path) captured = capsys.readouterr() assert "JWT" in captured.out or "authentication" in captured.out def test_search_with_wing_filter(self, palace_path, seeded_collection, capsys): search("planning", palace_path, wing="notes") captured = capsys.readouterr() assert "Results for" in captured.out def test_search_with_room_filter(self, palace_path, seeded_collection, capsys): search("database", palace_path, room="backend") captured = capsys.readouterr() assert "Room:" in captured.out def test_search_with_wing_and_room(self, palace_path, seeded_collection, capsys): search("code", palace_path, wing="project", room="frontend") captured = capsys.readouterr() assert "Wing:" in captured.out assert "Room:" in captured.out def test_search_no_palace_raises(self, tmp_path): with pytest.raises(SearchError, match="No palace found"): search("anything", str(tmp_path / "missing")) def test_search_no_results(self, palace_path, collection, capsys): """Empty collection returns no results message.""" # collection is empty (no seeded data) result = search("xyzzy_nonexistent_query", palace_path, n_results=1) captured = capsys.readouterr() # Either prints "No results" or returns None assert result is None or "No results" in captured.out def test_search_query_error_raises(self): """search raises SearchError when query fails.""" mock_col = MagicMock() mock_col.query.side_effect = RuntimeError("boom") mock_client = MagicMock() mock_client.get_collection.return_value = mock_col with patch("mempalace.searcher.chromadb.PersistentClient", return_value=mock_client): with pytest.raises(SearchError, match="Search error"): search("test", "/fake/path") def test_search_n_results(self, palace_path, seeded_collection, capsys): search("code", palace_path, n_results=1) captured = capsys.readouterr() # Should have output with at least one result block assert "[1]" in captured.out