diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index d75e657..25b3646 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -2,7 +2,7 @@ > **Note:** This document is automatically generated and verified against the live test suite by `scripts/generate_requirements.py` and `tests/backend/test_requirements_sync.py`. -**Test Verification Baseline:** **1185 Automated Tests** (821 Pytest Backend + 314 Vitest Frontend + 50 Playwright E2E). +**Test Verification Baseline:** **1187 Automated Tests** (823 Pytest Backend + 314 Vitest Frontend + 50 Playwright E2E). --- @@ -1399,6 +1399,10 @@ over the PostgresConnectionAdapter using an initialized test engine._ - `test_upsert_reports_failure_when_every_record_is_rejected` - `test_rejected_record_warnings_do_not_log_the_chunk_text` +#### `tests/test_trace_path_case_insensitive.py` (2 tests) +- `test_lowercase_symbol_resolves_to_the_indexed_symbol` +- `test_graph_traversal_sql_has_no_sqlite_only_collation` + #### `tests/test_trace_path_compact.py` (3 tests) - `test_trace_nodes_have_no_permalinks_by_default_but_the_target_keeps_one` - `test_include_permalinks_returns_them_on_every_node` diff --git a/app/services/graph_traversal.py b/app/services/graph_traversal.py index 789079c..84e827e 100644 --- a/app/services/graph_traversal.py +++ b/app/services/graph_traversal.py @@ -83,7 +83,7 @@ def trace_symbol_path( if not symbols_found: nc_query = ( "SELECT id, repo, filepath, name, full_symbol, kind, start_line, end_line, signature " - "FROM ast_symbols WHERE (name = ? COLLATE NOCASE OR full_symbol = ? COLLATE NOCASE OR full_symbol LIKE ? COLLATE NOCASE)" + "FROM ast_symbols WHERE (LOWER(name) = LOWER(?) OR LOWER(full_symbol) = LOWER(?) OR LOWER(full_symbol) LIKE LOWER(?))" + repo_sql ) symbols_found = conn.execute(nc_query, [target_sym, target_sym, f"%.{target_sym}"] + repo_arg).fetchall() @@ -107,7 +107,7 @@ def trace_symbol_path( if not rel_hit: rel_hit = conn.execute( "SELECT source_symbol, target_symbol FROM ast_relationships " - "WHERE (source_symbol = ? COLLATE NOCASE OR target_symbol = ? COLLATE NOCASE)" + "WHERE (LOWER(source_symbol) = LOWER(?) OR LOWER(target_symbol) = LOWER(?))" + repo_sql + " LIMIT 1", [target_sym, target_sym] + repo_arg, ).fetchone() diff --git a/tests/test_trace_path_case_insensitive.py b/tests/test_trace_path_case_insensitive.py new file mode 100644 index 0000000..c68cbe3 --- /dev/null +++ b/tests/test_trace_path_case_insensitive.py @@ -0,0 +1,22 @@ +"""trace_path: a symbol in another letter case resolves, with SQL that PostgreSQL accepts (no SQLite-only COLLATE NOCASE).""" + +import inspect +import json + +import app.services.graph_traversal as graph_traversal +from app.services.graph_traversal import trace_symbol_path + +# Same database setup as the DI caller tests: the method rows and CALLS rows of a small C# graph. +from tests.test_trace_path_di_callers import REPO, di_graph # noqa: F401 (pytest fixture) + + +def test_lowercase_symbol_resolves_to_the_indexed_symbol(di_graph): # noqa: F811 + result = json.loads(trace_symbol_path(symbol="assistantservice.publishinstance", repo=REPO, direction="callers", depth=1)) + assert "error" not in result + assert result["target"]["symbol"] == "AssistantService.PublishInstance" + assert [c["symbol"] for c in result["callers"]] == ["AssistantsController.PublishInstance"] + + +def test_graph_traversal_sql_has_no_sqlite_only_collation(): + # PostgreSQL rejects `COLLATE NOCASE` ("collation nocase for encoding UTF8 does not exist"). + assert "NOCASE" not in inspect.getsource(graph_traversal).upper()