From f567a0afbc01732f699a6ab6364c063e8eb95a57 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Fri, 3 Jul 2026 23:53:25 +0300 Subject: [PATCH 1/2] test: cover search no-Lance error path + multi-list RRF (#358) Two highest-value coverage gaps from #358: - search no-Lance error path: every search test monkeypatched run_search, so search_v2's genuine `except Exception` envelope was never exercised. Add a test that runs search against an index dir with no Lance tables (no monkeypatch) and asserts success=False with a non-empty message and no traceback, while graph- only tools still succeed against the same graph. - multi-list RRF: _rrf_merge was covered only for the weighted two-list case. Add a test that a row reinforced across two ranked lists accumulates score and outranks a singleton, dedups by row key, and orders by summed score. resolve no-match is already covered (test_resolve_status_none_returns_nonempty_message). Co-Authored-By: Claude --- tests/test_mcp_v2.py | 20 ++++++++++++++++++++ tests/test_search_lancedb.py | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tests/test_mcp_v2.py b/tests/test_mcp_v2.py index 4ceb829a..3a771973 100644 --- a/tests/test_mcp_v2.py +++ b/tests/test_mcp_v2.py @@ -584,6 +584,26 @@ def test_search_unknown_filter_key_returns_failure(monkeypatch, ladybug_graph) - assert "typo_key" in out.message +def test_search_no_lance_index_returns_failure_envelope(monkeypatch, ladybug_graph, tmp_path) -> None: + """Real search error path (issue #358): every other search test monkeypatches + run_search, so the genuine `except Exception` envelope in search_v2 was never + exercised. With no Lance vector index present, search returns a structured + failure (success=False, non-empty message, no traceback) while the graph-only + tools still succeed against the same graph — proving the failure is + vector-specific, not a crash.""" + empty_index = tmp_path / "no-lance-index" + empty_index.mkdir() + monkeypatch.setenv("JAVA_CODEBASE_RAG_INDEX_DIR", str(empty_index)) + out = search_v2("ChatService", graph=ladybug_graph) + assert out.success is False + assert out.message is not None and out.message.strip() + # Graph-only tools still work (the failure is vector-specific, not a crash). + found = find_v2("symbol", {"role": "CONTROLLER"}, graph=ladybug_graph) + assert found.success is True + assert found.results + + + def test_search_cross_kind_filter_returns_failure(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter={"path_prefix": "/api"}, graph=ladybug_graph) diff --git a/tests/test_search_lancedb.py b/tests/test_search_lancedb.py index bae58b7f..c34ffca6 100644 --- a/tests/test_search_lancedb.py +++ b/tests/test_search_lancedb.py @@ -37,6 +37,26 @@ def test_rrf_merge_weights_second_list_by_row() -> None: assert by_file["b.java"] > by_file["c.java"] +def test_rrf_merge_reinforced_row_across_lists_outranks_singleton() -> None: + """Multi-list RRF (issue #358): a row reinforced across two ranked lists + accumulates score and outranks a row appearing in only one list. Merging + dedups by (filename, range_start, range_end) and orders by summed score — + the core of multi-table fused ranking, previously covered only for the + weighted two-list case.""" + list_a = [{"filename": "a.java", "range_start": 1, "range_end": 5}] + list_b = [{"filename": "a.java", "range_start": 1, "range_end": 5}] # same key, distinct row + list_c = [{"filename": "z.java", "range_start": 9, "range_end": 99}] # singleton, rank 0 + merged = _rrf_merge([list_a, list_b, list_c], k=60) + by_file = {m["filename"]: float(m["_rrf_score"]) for m in merged} + # 'a.java' (rank 0 in two lists) sums two contributions; 'z.java' only one. + assert by_file["a.java"] > by_file["z.java"] + # The two a.java entries collapse to one (dedup by row key). + assert len(merged) == 2 + # Highest summed score first. + assert merged[0]["filename"] == "a.java" + + + def test_java_enriched_columns_include_symbol_identity_fields() -> None: assert "symbol_id" in JAVA_ENRICHED_COLUMNS assert "metadata" in JAVA_ENRICHED_COLUMNS From c663fe02bece727e0ba4fc0e768d49e40de1f58d Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Sat, 4 Jul 2026 08:43:22 +0300 Subject: [PATCH 2/2] style(tests): collapse extra blank lines after the new tests (E303) The two tests added by this PR were each followed by three blank lines before the next top-level def; PEP8/ruff E303 allows at most two. The repo's ruff config does not enable E3 today so this is latent, but it would surface under a stricter lint gate. --- tests/test_mcp_v2.py | 1 - tests/test_search_lancedb.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/test_mcp_v2.py b/tests/test_mcp_v2.py index 3a771973..7e9f4856 100644 --- a/tests/test_mcp_v2.py +++ b/tests/test_mcp_v2.py @@ -603,7 +603,6 @@ def test_search_no_lance_index_returns_failure_envelope(monkeypatch, ladybug_gra assert found.results - def test_search_cross_kind_filter_returns_failure(monkeypatch, ladybug_graph) -> None: monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: _fake_search_rows()) out = search_v2("ChatService", filter={"path_prefix": "/api"}, graph=ladybug_graph) diff --git a/tests/test_search_lancedb.py b/tests/test_search_lancedb.py index c34ffca6..a6197f9a 100644 --- a/tests/test_search_lancedb.py +++ b/tests/test_search_lancedb.py @@ -56,7 +56,6 @@ def test_rrf_merge_reinforced_row_across_lists_outranks_singleton() -> None: assert merged[0]["filename"] == "a.java" - def test_java_enriched_columns_include_symbol_identity_fields() -> None: assert "symbol_id" in JAVA_ENRICHED_COLUMNS assert "metadata" in JAVA_ENRICHED_COLUMNS