From f1e998f010fd5e8b01a2ceeb645a0c3b36c7394b Mon Sep 17 00:00:00 2001 From: HeaTTap Date: Thu, 10 Sep 2026 08:04:11 +0000 Subject: [PATCH] test(brain_ask): cover looks_like_entity and _links_in (#4) Add unit tests in tests/test_brain_ask.py covering looks_like_entity and _links_in without requiring models or GPU. Fix _links_in to ignore fenced code blocks so code snippets are not treated as graph edges. All 49 tests pass. --- brain_ask.py | 4 +- tests/test_brain_ask.py | 111 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/test_brain_ask.py diff --git a/brain_ask.py b/brain_ask.py index 83f44b0..e346bcb 100644 --- a/brain_ask.py +++ b/brain_ask.py @@ -75,12 +75,14 @@ def looks_like_entity(q): return False +FENCED_CODE_RX = re.compile(r'(?:```[\s\S]*?```|~~~[\s\S]*?~~~)') WIKILINK_RX = re.compile(r'\[\[([^\]\|#]+)') def _links_in(path): - """Outgoing wikilink TARGETS in a note (alias/heading stripped).""" + """Outgoing wikilink TARGETS in a note (alias/heading stripped, code fences ignored).""" try: t = Path(path).read_text(encoding='utf-8', errors='ignore') except Exception: return [] + t = FENCED_CODE_RX.sub('', t) return [m.strip() for m in WIKILINK_RX.findall(t)] diff --git a/tests/test_brain_ask.py b/tests/test_brain_ask.py new file mode 100644 index 0000000..c8b6fec --- /dev/null +++ b/tests/test_brain_ask.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +"""Unit tests for pure functions in brain_ask.py. + +Covers looks_like_entity() and _links_in() without model downloads or GPUs. +Addresses issue #4: +- looks_like_entity(): verifies entity gate switches off graph expansion for + proper nouns, CamelCase, snake_case, known tools, and non-Latin (Cyrillic) names, + while keeping graph on for themes and acronyms. +- _links_in(): table-driven parsing of wikilinks in markdown files using tmp_path, + including aliases, anchors, multiple links per line, fenced code blocks, and no links. +""" +import sys +from pathlib import Path +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from brain_ask import looks_like_entity, _links_in # noqa: E402 + + +@pytest.mark.parametrize( + ("query", "expected"), + [ + # Known tools and products + ("obsidian", True), + ("sqlite", True), + ("whatsapp", True), + # Snake_case identifiers + ("turnstate_hook", True), + ("my_project", True), + # CamelCase tools and libraries + ("NotebookLM", True), + ("PyTorch", True), + ("FastAPI", True), + # Proper nouns and names (single word or two tokens) + ("Feynman", True), + ("Turing", True), + ("Alan Turing", True), + ("Richard Feynman", True), + # Proper noun in short query (idx > 0) + ("notes on Feynman", True), + ("who is Turing", True), + # Non-Latin script (Cyrillic entities and names) + ("Тьюринг", True), + ("Алан Тьюринг", True), + ("«Тьюринг»", True), + ("заметки про Тьюринга", True), + # Empty / whitespace / None + ("", False), + (" ", False), + (None, False), + # Query with > 5 tokens + ("this is a very long query with too many words", False), + # Lowercase common-noun phrases / themes (graph stays on) + ("graph memory", False), + ("approaches to agent memory", False), + ("second brain", False), + # Non-Latin script lowercase themes + ("векторный поиск", False), + ("теория графов", False), + # Bare topic acronyms (uppercase <= 3 chars) + ("AI", False), + ("RAG", False), + ("DAO", False), + ("РФ", False), + # Multi-word sentence query where only first word is capitalized + ("How does graph memory work", False), + ], +) +def test_looks_like_entity(query, expected): + assert looks_like_entity(query) is expected + + +@pytest.mark.parametrize( + ("content", "expected"), + [ + # A file with no links at all + ("A plain note with no wikilinks inside.", []), + # A link with an alias + ("See [[note|display text]] for details.", ["note"]), + # A link with a heading anchor + ("Refer to [[note#section]] above.", ["note"]), + # A link with both heading anchor and alias + ("Check [[note#section|display text]] here.", ["note"]), + # A line with two links on it + ("Two links on one line: [[first]] and [[second]].", ["first", "second"]), + # Multiple links across lines + ("Line one [[alpha]].\nLine two [[beta]].", ["alpha", "beta"]), + # A wikilink inside a fenced code block (backticks) should NOT count + ("```python\nx = [[code_note]]\n```", []), + # A wikilink inside a fenced code block (tildes) should NOT count + ("~~~markdown\n[[ignored_note]]\n~~~", []), + # Mixed note: links outside code blocks count, links inside code blocks do not + ( + "Link outside [[valid_note]].\n" + "```\n" + "[[inside_code]]\n" + "```\n" + "Another outside [[another_valid#anchor|alias]].", + ["valid_note", "another_valid"], + ), + ], +) +def test_links_in_content_cases(tmp_path, content, expected): + note_file = tmp_path / "note.md" + note_file.write_text(content, encoding="utf-8") + assert _links_in(note_file) == expected + + +def test_links_in_nonexistent_file(tmp_path): + missing_file = tmp_path / "does_not_exist.md" + assert _links_in(missing_file) == []