|
11 | 11 | from pathlib import Path |
12 | 12 | from unittest.mock import patch |
13 | 13 |
|
| 14 | +from api.search import _index_hit_excluded |
14 | 15 | from app import create_app |
15 | 16 | from tests.conftest import FIXTURES, assert_error_response |
16 | 17 | from utils.search_index import build_search_index, reset_background_for_tests |
@@ -105,12 +106,18 @@ def test_invalid_since_days_zero(client_single): |
105 | 106 |
|
106 | 107 |
|
107 | 108 | def test_projects_unavailable(client_single, monkeypatch): |
108 | | - monkeypatch.setattr("api.search._projects_dir_available", lambda _path: False) |
| 109 | + monkeypatch.setattr("api.search._projects_dir_inaccessible", lambda _path: True) |
109 | 110 | resp = client_single.get("/api/search?q=Hello") |
110 | 111 | assert resp.status_code == 503 |
111 | 112 | assert_error_response(resp, expected_code="SEARCH_PROJECTS_UNAVAILABLE") |
112 | 113 |
|
113 | 114 |
|
| 115 | +def test_missing_projects_dir_is_not_unavailable(client_single, monkeypatch): |
| 116 | + monkeypatch.setattr("api.search._projects_dir_inaccessible", lambda _path: False) |
| 117 | + resp = client_single.get("/api/search?q=Hello") |
| 118 | + assert resp.status_code == 200 |
| 119 | + |
| 120 | + |
114 | 121 | def test_index_unavailable_when_locked(tmp_path, monkeypatch): |
115 | 122 | recent_ts = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") |
116 | 123 | client = _seed_indexed_client(tmp_path, monkeypatch, timestamp=recent_ts) |
@@ -154,8 +161,7 @@ def _seed_indexed_client(tmp_path, monkeypatch, *, timestamp: str): |
154 | 161 | with patches[0]: |
155 | 162 | assert build_search_index(str(tmp_path / "projects"), [], force=True) is True |
156 | 163 |
|
157 | | - app = create_app(base_dir=str(tmp_path / "projects")) |
158 | | - app.config["TESTING"] = True |
| 164 | + app = create_app(base_dir=str(tmp_path / "projects"), testing=True) |
159 | 165 | return app.test_client() |
160 | 166 |
|
161 | 167 |
|
@@ -185,6 +191,36 @@ def test_search_uses_index_when_usable(tmp_path, monkeypatch): |
185 | 191 | assert len(resp.get_json()) >= 1 |
186 | 192 |
|
187 | 193 |
|
| 194 | +def test_index_hit_excluded_fails_closed_when_session_unreadable(): |
| 195 | + rules = [[("word", "secret")]] |
| 196 | + with ( |
| 197 | + patch("api.search.get_summary", return_value=None), |
| 198 | + patch("api.search.get_cached_session", side_effect=OSError("unreadable")), |
| 199 | + ): |
| 200 | + assert _index_hit_excluded( |
| 201 | + rules, |
| 202 | + "rules-fp", |
| 203 | + project_name="demo", |
| 204 | + file_path="/tmp/session.jsonl", |
| 205 | + mtime=1.0, |
| 206 | + ) |
| 207 | + |
| 208 | + |
| 209 | +def test_search_falls_back_on_tokenless_query(tmp_path, monkeypatch): |
| 210 | + recent_ts = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") |
| 211 | + client = _seed_indexed_client(tmp_path, monkeypatch, timestamp=recent_ts) |
| 212 | + seen: list[bool] = [] |
| 213 | + |
| 214 | + def _fake_live_scan(*_args, **_kwargs): |
| 215 | + seen.append(True) |
| 216 | + return [] |
| 217 | + |
| 218 | + with patch("api.search._search_live_scan", side_effect=_fake_live_scan): |
| 219 | + resp = client.get("/api/search?q=!!!") |
| 220 | + assert resp.status_code == 200 |
| 221 | + assert seen == [True] |
| 222 | + |
| 223 | + |
188 | 224 | def test_search_falls_back_when_index_query_fails(tmp_path, monkeypatch): |
189 | 225 | recent_ts = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") |
190 | 226 | client = _seed_indexed_client(tmp_path, monkeypatch, timestamp=recent_ts) |
|
0 commit comments