From 1104cf97ca0974872dfc43b80cc31d92b31eccab Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 10 May 2026 12:35:33 -0400 Subject: [PATCH] tests/aliases: gap-fill list_term_aliases + tokenizer helpers (10 new tests) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The aliases.py public surface had 18 tests covering happy-paths, audit discipline, domain isolation, lowercase normalization, and expand-query semantics. Three direct gaps: - list_term_aliases (no test at all): filter-by-domain, filter-by- term-substring, unreachable-db fail-closed, missing-table fail- closed - _tokenize_fts_query (only via expand-query smoke): preserve quoted phrases, parentheses-as-tokens (OR-expansion contract), unterminated-quote fallback - _quote_for_fts + _match_quoting (no direct test): pass-through quoted, defensive quote-multi-word, quoted-reference symmetry 10 new tests; aliases.py test count 18 → 28. Same fixture pattern as the existing tests (sqlite tmp DB with SCHEMA_SQL applied). --- tests/test_aliases.py | 118 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/tests/test_aliases.py b/tests/test_aliases.py index 723a47a..b101791 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -255,3 +255,121 @@ def test_expand_query_unreachable_db_returns_original(): "incidence", "geometry", "/nonexistent/path/aliases.db" ) assert expanded == "incidence" + + +# --- list_term_aliases (gap-fill 2026-05-10) ------------------------- + + +def test_list_term_aliases_filters_by_domain(db): + """`list_term_aliases(domain=X)` returns only rows in that + domain. Same pattern as list_citation_aliases filter.""" + add_term_alias( + db, term="set", alternate_term="class", + domain="set-theory", decision_by="fox", + ) + add_term_alias( + db, term="line", alternate_term="ray", + domain="geometry", decision_by="fox", + ) + add_term_alias( + db, term="incidence", alternate_term="connection", + domain="geometry", decision_by="fox", + ) + from arborist.qa.aliases import list_term_aliases + + geom = list_term_aliases(db, domain="geometry") + assert len(geom) == 2 + assert all(a.domain == "geometry" for a in geom) + set_th = list_term_aliases(db, domain="set-theory") + assert len(set_th) == 1 + assert set_th[0].term == "set" + + +def test_list_term_aliases_filters_by_term_substring(db): + """`term_filter` is a substring match (LIKE %X%) on term.""" + add_term_alias(db, term="incidence", alternate_term="connection", + domain="geometry", decision_by="fox") + add_term_alias(db, term="line incidence", alternate_term="line connection", + domain="geometry", decision_by="fox") + add_term_alias(db, term="parallel", alternate_term="parallels", + domain="geometry", decision_by="fox") + from arborist.qa.aliases import list_term_aliases + + inc = list_term_aliases(db, term_filter="incidence") + assert len(inc) == 2 + assert all("incidence" in a.term for a in inc) + + +def test_list_term_aliases_unreachable_db_returns_empty(): + """Same fail-closed contract as expand_query_with_term_aliases: + nonexistent DB → empty list, no exception.""" + from arborist.qa.aliases import list_term_aliases + + assert list_term_aliases("/nonexistent/path/aliases.db") == [] + + +def test_list_term_aliases_handles_missing_table(tmp_path): + """A SQLite file without the term_aliases table → empty list, + not an OperationalError.""" + import sqlite3 + + bad_db = tmp_path / "no-schema.db" + sqlite3.connect(str(bad_db)).execute("CREATE TABLE foo (x TEXT)").connection.close() + from arborist.qa.aliases import list_term_aliases + + assert list_term_aliases(str(bad_db)) == [] + + +# --- internal helper coverage (small surfaces, easy regression risk) - + + +def test_tokenize_fts_query_preserves_quoted_phrase(): + from arborist.qa.aliases import _tokenize_fts_query + + assert _tokenize_fts_query('"line incidence"') == ['"line incidence"'] + assert _tokenize_fts_query('foo "bar baz" qux') == [ + "foo", '"bar baz"', "qux" + ] + + +def test_tokenize_fts_query_handles_parentheses_as_tokens(): + """OR-expansion produces queries like `(a OR b)`; the tokenizer + must preserve `(` and `)` as standalone tokens (round-trip).""" + from arborist.qa.aliases import _tokenize_fts_query + + assert _tokenize_fts_query("(a OR b)") == ["(", "a", "OR", "b", ")"] + + +def test_tokenize_fts_query_unterminated_quote_falls_back(): + """A user query with a stray opening quote shouldn't lose data — + the unmatched-quote tail becomes one token.""" + from arborist.qa.aliases import _tokenize_fts_query + + out = _tokenize_fts_query('foo "bar baz') + assert out[0] == "foo" + assert "bar baz" in out[-1] + + +def test_quote_for_fts_passes_through_already_quoted(): + from arborist.qa.aliases import _quote_for_fts + + assert _quote_for_fts('"line incidence"') == '"line incidence"' + assert _quote_for_fts("incidence") == "incidence" + + +def test_quote_for_fts_quotes_multiword_bare_token(): + """Defense-in-depth: a bare token that contains a space gets + quoted (shouldn't happen via _tokenize_fts_query but the helper + is defensive).""" + from arborist.qa.aliases import _quote_for_fts + + assert _quote_for_fts("two words") == '"two words"' + + +def test_match_quoting_quoted_reference(): + from arborist.qa.aliases import _match_quoting + + # Quoted reference → quoted alt. + assert _match_quoting('"line incidence"', "line connection") == '"line connection"' + # Bare reference → bare alt. + assert _match_quoting("incidence", "connection") == "connection"