Navegador

navegador / tests / test_intelligence.py
Source Blame History 850 lines
5d70f82… lmata 1 """
5d70f82… lmata 2 Tests for the navegador intelligence layer.
5d70f82… lmata 3
5d70f82… lmata 4 Covers:
5d70f82… lmata 5 - SemanticSearch._cosine_similarity
5d70f82… lmata 6 - SemanticSearch.index / search (mock graph + mock LLM)
5d70f82… lmata 7 - CommunityDetector.detect / store_communities (mock graph)
5d70f82… lmata 8 - NLPEngine.natural_query / name_communities / generate_docs (mock LLM)
5d70f82… lmata 9 - DocGenerator template mode and LLM mode (mock LLM)
5d70f82… lmata 10 - CLI commands: semantic-search, communities, ask, generate-docs, docs
5d70f82… lmata 11
5d70f82… lmata 12 All LLM providers are mocked — no real API calls are made.
5d70f82… lmata 13 """
5d70f82… lmata 14
5d70f82… lmata 15 from __future__ import annotations
5d70f82… lmata 16
5d70f82… lmata 17 import json
5d70f82… lmata 18 import math
5d70f82… lmata 19 from unittest.mock import MagicMock, patch
5d70f82… lmata 20
5d70f82… lmata 21 import pytest
5d70f82… lmata 22 from click.testing import CliRunner
5d70f82… lmata 23
5d70f82… lmata 24 from navegador.cli.commands import main
5d70f82… lmata 25
5d70f82… lmata 26
5d70f82… lmata 27 # ── Helpers ───────────────────────────────────────────────────────────────────
5d70f82… lmata 28
5d70f82… lmata 29
5d70f82… lmata 30 def _mock_store(result_rows=None):
5d70f82… lmata 31 """Return a MagicMock GraphStore whose .query() returns the given rows."""
5d70f82… lmata 32 store = MagicMock()
5d70f82… lmata 33 result = MagicMock()
5d70f82… lmata 34 result.result_set = result_rows if result_rows is not None else []
5d70f82… lmata 35 store.query.return_value = result
5d70f82… lmata 36 return store
5d70f82… lmata 37
5d70f82… lmata 38
5d70f82… lmata 39 def _mock_provider(complete_return="mocked answer", embed_return=None):
5d70f82… lmata 40 """Return a MagicMock LLMProvider."""
5d70f82… lmata 41 if embed_return is None:
5d70f82… lmata 42 embed_return = [0.1, 0.2, 0.3, 0.4]
5d70f82… lmata 43 provider = MagicMock()
5d70f82… lmata 44 provider.complete.return_value = complete_return
5d70f82… lmata 45 provider.embed.return_value = embed_return
5d70f82… lmata 46 provider.name = "mock"
5d70f82… lmata 47 provider.model = "mock-model"
5d70f82… lmata 48 return provider
5d70f82… lmata 49
5d70f82… lmata 50
5d70f82… lmata 51 # ── SemanticSearch: _cosine_similarity ────────────────────────────────────────
5d70f82… lmata 52
5d70f82… lmata 53
5d70f82… lmata 54 class TestCosineSimilarity:
5d70f82… lmata 55 def setup_method(self):
5d70f82… lmata 56 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 57
5d70f82… lmata 58 self.cls = SemanticSearch
5d70f82… lmata 59
5d70f82… lmata 60 def test_identical_vectors_return_one(self):
5d70f82… lmata 61 v = [1.0, 0.0, 0.0]
5d70f82… lmata 62 assert self.cls._cosine_similarity(v, v) == pytest.approx(1.0)
5d70f82… lmata 63
5d70f82… lmata 64 def test_orthogonal_vectors_return_zero(self):
5d70f82… lmata 65 a = [1.0, 0.0]
5d70f82… lmata 66 b = [0.0, 1.0]
5d70f82… lmata 67 assert self.cls._cosine_similarity(a, b) == pytest.approx(0.0)
5d70f82… lmata 68
5d70f82… lmata 69 def test_opposite_vectors_return_minus_one(self):
5d70f82… lmata 70 a = [1.0, 0.0]
5d70f82… lmata 71 b = [-1.0, 0.0]
5d70f82… lmata 72 assert self.cls._cosine_similarity(a, b) == pytest.approx(-1.0)
5d70f82… lmata 73
5d70f82… lmata 74 def test_zero_vector_returns_zero(self):
5d70f82… lmata 75 a = [0.0, 0.0]
5d70f82… lmata 76 b = [1.0, 2.0]
5d70f82… lmata 77 assert self.cls._cosine_similarity(a, b) == 0.0
5d70f82… lmata 78
5d70f82… lmata 79 def test_different_length_vectors_return_zero(self):
5d70f82… lmata 80 a = [1.0, 2.0]
5d70f82… lmata 81 b = [1.0, 2.0, 3.0]
5d70f82… lmata 82 assert self.cls._cosine_similarity(a, b) == 0.0
5d70f82… lmata 83
5d70f82… lmata 84 def test_known_similarity(self):
5d70f82… lmata 85 a = [1.0, 1.0]
5d70f82… lmata 86 b = [1.0, 0.0]
5d70f82… lmata 87 # cos(45°) = 1/sqrt(2)
5d70f82… lmata 88 expected = 1.0 / math.sqrt(2)
5d70f82… lmata 89 assert self.cls._cosine_similarity(a, b) == pytest.approx(expected, abs=1e-6)
5d70f82… lmata 90
5d70f82… lmata 91 def test_general_non_unit_vectors(self):
5d70f82… lmata 92 a = [3.0, 4.0]
5d70f82… lmata 93 b = [3.0, 4.0]
5d70f82… lmata 94 # Same direction → 1.0 regardless of magnitude
5d70f82… lmata 95 assert self.cls._cosine_similarity(a, b) == pytest.approx(1.0)
5d70f82… lmata 96
5d70f82… lmata 97
5d70f82… lmata 98 # ── SemanticSearch: index ─────────────────────────────────────────────────────
5d70f82… lmata 99
5d70f82… lmata 100
5d70f82… lmata 101 class TestSemanticSearchIndex:
5d70f82… lmata 102 def test_index_embeds_and_stores(self):
5d70f82… lmata 103 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 104
5d70f82… lmata 105 rows = [
5d70f82… lmata 106 ["Function", "my_func", "app.py", "Does something important"],
5d70f82… lmata 107 ["Class", "MyClass", "app.py", "A useful class"],
5d70f82… lmata 108 ]
5d70f82… lmata 109 store = _mock_store(rows)
5d70f82… lmata 110 provider = _mock_provider(embed_return=[0.1, 0.2, 0.3])
5d70f82… lmata 111
5d70f82… lmata 112 ss = SemanticSearch(store, provider)
5d70f82… lmata 113 count = ss.index(limit=10)
5d70f82… lmata 114
5d70f82… lmata 115 assert count == 2
5d70f82… lmata 116 # embed called once per node
5d70f82… lmata 117 assert provider.embed.call_count == 2
5d70f82… lmata 118 # SET query called for each node
5d70f82… lmata 119 assert store.query.call_count >= 3 # 1 fetch + 2 set
5d70f82… lmata 120
5d70f82… lmata 121 def test_index_skips_nodes_without_text(self):
5d70f82… lmata 122 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 123
5d70f82… lmata 124 rows = [
5d70f82… lmata 125 ["Function", "no_doc", "app.py", ""], # empty text
5d70f82… lmata 126 ["Class", "HasDoc", "app.py", "Some docstring"],
5d70f82… lmata 127 ]
5d70f82… lmata 128 store = _mock_store(rows)
5d70f82… lmata 129 provider = _mock_provider(embed_return=[0.1, 0.2])
5d70f82… lmata 130
5d70f82… lmata 131 ss = SemanticSearch(store, provider)
5d70f82… lmata 132 count = ss.index()
5d70f82… lmata 133
5d70f82… lmata 134 assert count == 1 # only the node with text
5d70f82… lmata 135 assert provider.embed.call_count == 1
5d70f82… lmata 136
5d70f82… lmata 137 def test_index_returns_zero_for_empty_graph(self):
5d70f82… lmata 138 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 139
5d70f82… lmata 140 store = _mock_store([])
5d70f82… lmata 141 provider = _mock_provider()
5d70f82… lmata 142 ss = SemanticSearch(store, provider)
5d70f82… lmata 143 assert ss.index() == 0
5d70f82… lmata 144 provider.embed.assert_not_called()
5d70f82… lmata 145
5d70f82… lmata 146
5d70f82… lmata 147 # ── SemanticSearch: search ────────────────────────────────────────────────────
5d70f82… lmata 148
5d70f82… lmata 149
5d70f82… lmata 150 class TestSemanticSearchSearch:
5d70f82… lmata 151 def test_search_returns_sorted_results(self):
5d70f82… lmata 152 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 153
5d70f82… lmata 154 # Two nodes with known embeddings
5d70f82… lmata 155 # Node A: parallel to query → similarity 1.0
5d70f82… lmata 156 # Node B: orthogonal to query → similarity 0.0
5d70f82… lmata 157 query_vec = [1.0, 0.0]
5d70f82… lmata 158 node_a_vec = [1.0, 0.0] # sim = 1.0
5d70f82… lmata 159 node_b_vec = [0.0, 1.0] # sim = 0.0
5d70f82… lmata 160
5d70f82… lmata 161 rows = [
5d70f82… lmata 162 ["Function", "node_a", "a.py", "doc a", json.dumps(node_a_vec)],
5d70f82… lmata 163 ["Class", "node_b", "b.py", "doc b", json.dumps(node_b_vec)],
5d70f82… lmata 164 ]
5d70f82… lmata 165 store = _mock_store(rows)
5d70f82… lmata 166 provider = _mock_provider(embed_return=query_vec)
5d70f82… lmata 167
5d70f82… lmata 168 ss = SemanticSearch(store, provider)
5d70f82… lmata 169 results = ss.search("find something", limit=10)
5d70f82… lmata 170
5d70f82… lmata 171 assert len(results) == 2
5d70f82… lmata 172 assert results[0]["name"] == "node_a"
5d70f82… lmata 173 assert results[0]["score"] == pytest.approx(1.0)
5d70f82… lmata 174 assert results[1]["name"] == "node_b"
5d70f82… lmata 175 assert results[1]["score"] == pytest.approx(0.0)
5d70f82… lmata 176
5d70f82… lmata 177 def test_search_respects_limit(self):
5d70f82… lmata 178 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 179
5d70f82… lmata 180 rows = [
5d70f82… lmata 181 ["Function", f"func_{i}", "app.py", f"doc {i}", json.dumps([float(i), 0.0])]
5d70f82… lmata 182 for i in range(1, 6)
5d70f82… lmata 183 ]
5d70f82… lmata 184 store = _mock_store(rows)
5d70f82… lmata 185 provider = _mock_provider(embed_return=[1.0, 0.0])
5d70f82… lmata 186
5d70f82… lmata 187 ss = SemanticSearch(store, provider)
5d70f82… lmata 188 results = ss.search("query", limit=3)
5d70f82… lmata 189 assert len(results) == 3
5d70f82… lmata 190
5d70f82… lmata 191 def test_search_handles_invalid_embedding_json(self):
5d70f82… lmata 192 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 193
5d70f82… lmata 194 rows = [
5d70f82… lmata 195 ["Function", "bad_node", "app.py", "doc", "not-valid-json"],
5d70f82… lmata 196 ["Function", "good_node", "app.py", "doc", json.dumps([1.0, 0.0])],
5d70f82… lmata 197 ]
5d70f82… lmata 198 store = _mock_store(rows)
5d70f82… lmata 199 provider = _mock_provider(embed_return=[1.0, 0.0])
5d70f82… lmata 200
5d70f82… lmata 201 ss = SemanticSearch(store, provider)
5d70f82… lmata 202 results = ss.search("q", limit=10)
5d70f82… lmata 203 # Only good_node should appear
5d70f82… lmata 204 assert len(results) == 1
5d70f82… lmata 205 assert results[0]["name"] == "good_node"
5d70f82… lmata 206
5d70f82… lmata 207 def test_search_empty_graph_returns_empty_list(self):
5d70f82… lmata 208 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 209
5d70f82… lmata 210 store = _mock_store([])
5d70f82… lmata 211 provider = _mock_provider()
5d70f82… lmata 212 ss = SemanticSearch(store, provider)
5d70f82… lmata 213 assert ss.search("anything") == []
5d70f82… lmata 214
5d70f82… lmata 215
5d70f82… lmata 216 # ── CommunityDetector ─────────────────────────────────────────────────────────
5d70f82… lmata 217
5d70f82… lmata 218
5d70f82… lmata 219 class TestCommunityDetector:
5d70f82… lmata 220 """Tests use a fully in-memory mock — no real FalkorDB required."""
5d70f82… lmata 221
5d70f82… lmata 222 def _make_store(self, node_rows, edge_rows):
5d70f82… lmata 223 """
5d70f82… lmata 224 Return a MagicMock store that returns different rows for the first vs
5d70f82… lmata 225 subsequent query calls (nodes query, edges query).
5d70f82… lmata 226 """
5d70f82… lmata 227 store = MagicMock()
5d70f82… lmata 228
5d70f82… lmata 229 node_result = MagicMock()
5d70f82… lmata 230 node_result.result_set = node_rows
5d70f82… lmata 231 edge_result = MagicMock()
5d70f82… lmata 232 edge_result.result_set = edge_rows
5d70f82… lmata 233
5d70f82… lmata 234 # First call → node query, second call → edge query, rest → set_community
5d70f82… lmata 235 store.query.side_effect = [node_result, edge_result] + [
5d70f82… lmata 236 MagicMock(result_set=[]) for _ in range(100)
5d70f82… lmata 237 ]
5d70f82… lmata 238 return store
5d70f82… lmata 239
5d70f82… lmata 240 def test_two_cliques_form_separate_communities(self):
5d70f82… lmata 241 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 242
5d70f82… lmata 243 # Nodes: 0-1-2 form a triangle (clique), 3-4 form a pair
5d70f82… lmata 244 # They have no edges between groups → two communities
5d70f82… lmata 245 node_rows = [
5d70f82… lmata 246 [0, "func_a", "a.py", "Function"],
5d70f82… lmata 247 [1, "func_b", "a.py", "Function"],
5d70f82… lmata 248 [2, "func_c", "a.py", "Function"],
5d70f82… lmata 249 [3, "func_d", "b.py", "Function"],
5d70f82… lmata 250 [4, "func_e", "b.py", "Function"],
5d70f82… lmata 251 ]
5d70f82… lmata 252 edge_rows = [
5d70f82… lmata 253 [0, 1], [1, 2], [0, 2], # triangle
5d70f82… lmata 254 [3, 4], # pair
5d70f82… lmata 255 ]
5d70f82… lmata 256 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 257 detector = CommunityDetector(store)
5d70f82… lmata 258 communities = detector.detect(min_size=2)
5d70f82… lmata 259
5d70f82… lmata 260 assert len(communities) == 2
5d70f82… lmata 261 sizes = sorted(c.size for c in communities)
5d70f82… lmata 262 assert sizes == [2, 3]
5d70f82… lmata 263
5d70f82… lmata 264 def test_min_size_filters_small_communities(self):
5d70f82… lmata 265 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 266
5d70f82… lmata 267 node_rows = [
5d70f82… lmata 268 [0, "a", "x.py", "Function"],
5d70f82… lmata 269 [1, "b", "x.py", "Function"],
5d70f82… lmata 270 [2, "c", "x.py", "Function"], # isolated
5d70f82… lmata 271 ]
5d70f82… lmata 272 edge_rows = [[0, 1]]
5d70f82… lmata 273 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 274 detector = CommunityDetector(store)
5d70f82… lmata 275
5d70f82… lmata 276 communities = detector.detect(min_size=2)
5d70f82… lmata 277 # Only the pair {a, b} passes; isolated node c gets size=1 (filtered)
5d70f82… lmata 278 assert all(c.size >= 2 for c in communities)
5d70f82… lmata 279
5d70f82… lmata 280 def test_empty_graph_returns_empty_list(self):
5d70f82… lmata 281 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 282
5d70f82… lmata 283 store = self._make_store([], [])
5d70f82… lmata 284 detector = CommunityDetector(store)
5d70f82… lmata 285 communities = detector.detect()
5d70f82… lmata 286 assert communities == []
5d70f82… lmata 287
5d70f82… lmata 288 def test_community_density_is_one_for_complete_graph(self):
5d70f82… lmata 289 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 290
5d70f82… lmata 291 # 3-node complete graph
5d70f82… lmata 292 node_rows = [
5d70f82… lmata 293 [0, "x", "", "Function"],
5d70f82… lmata 294 [1, "y", "", "Function"],
5d70f82… lmata 295 [2, "z", "", "Function"],
5d70f82… lmata 296 ]
5d70f82… lmata 297 edge_rows = [[0, 1], [1, 2], [0, 2]]
5d70f82… lmata 298 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 299 detector = CommunityDetector(store)
5d70f82… lmata 300 communities = detector.detect(min_size=3)
5d70f82… lmata 301
5d70f82… lmata 302 assert len(communities) == 1
5d70f82… lmata 303 assert communities[0].density == pytest.approx(1.0)
5d70f82… lmata 304
5d70f82… lmata 305 def test_community_members_are_strings(self):
5d70f82… lmata 306 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 307
5d70f82… lmata 308 node_rows = [
5d70f82… lmata 309 [0, "func_alpha", "f.py", "Function"],
5d70f82… lmata 310 [1, "func_beta", "f.py", "Function"],
5d70f82… lmata 311 ]
5d70f82… lmata 312 edge_rows = [[0, 1]]
5d70f82… lmata 313 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 314 detector = CommunityDetector(store)
5d70f82… lmata 315 communities = detector.detect(min_size=2)
5d70f82… lmata 316
5d70f82… lmata 317 members = communities[0].members
5d70f82… lmata 318 assert all(isinstance(m, str) for m in members)
5d70f82… lmata 319 assert set(members) == {"func_alpha", "func_beta"}
5d70f82… lmata 320
5d70f82… lmata 321 def test_store_communities_calls_query_for_each_node(self):
5d70f82… lmata 322 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 323
5d70f82… lmata 324 node_rows = [
5d70f82… lmata 325 [10, "n1", "", "Function"],
5d70f82… lmata 326 [11, "n2", "", "Function"],
5d70f82… lmata 327 ]
5d70f82… lmata 328 edge_rows = [[10, 11]]
5d70f82… lmata 329 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 330 detector = CommunityDetector(store)
5d70f82… lmata 331 detector.detect(min_size=2)
5d70f82… lmata 332
5d70f82… lmata 333 # Reset side_effect so store_communities calls work cleanly
5d70f82… lmata 334 store.query.side_effect = None
5d70f82… lmata 335 store.query.return_value = MagicMock(result_set=[])
5d70f82… lmata 336
5d70f82… lmata 337 updated = detector.store_communities()
5d70f82… lmata 338 assert updated == 2 # two nodes
5d70f82… lmata 339 assert store.query.call_count >= 2
5d70f82… lmata 340
5d70f82… lmata 341 def test_community_sorted_largest_first(self):
5d70f82… lmata 342 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 343
5d70f82… lmata 344 # 4-node clique + 2-node pair with a bridge → label propagation may merge
5d70f82… lmata 345 # Use two fully disconnected groups of sizes 4 and 2
5d70f82… lmata 346 node_rows = [
5d70f82… lmata 347 [0, "a", "", "F"], [1, "b", "", "F"], [2, "c", "", "F"], [3, "d", "", "F"],
5d70f82… lmata 348 [4, "e", "", "F"], [5, "f", "", "F"],
5d70f82… lmata 349 ]
5d70f82… lmata 350 edge_rows = [
5d70f82… lmata 351 [0, 1], [1, 2], [2, 3], [0, 3], # 4-cycle (all same community)
5d70f82… lmata 352 [4, 5], # pair
5d70f82… lmata 353 ]
5d70f82… lmata 354 store = self._make_store(node_rows, edge_rows)
5d70f82… lmata 355 detector = CommunityDetector(store)
5d70f82… lmata 356 communities = detector.detect(min_size=2)
5d70f82… lmata 357 sizes = [c.size for c in communities]
5d70f82… lmata 358 assert sizes == sorted(sizes, reverse=True)
5d70f82… lmata 359
5d70f82… lmata 360
5d70f82… lmata 361 # ── NLPEngine ─────────────────────────────────────────────────────────────────
5d70f82… lmata 362
5d70f82… lmata 363
5d70f82… lmata 364 class TestNLPEngine:
5d70f82… lmata 365 def test_natural_query_calls_complete_twice(self):
5d70f82… lmata 366 """Should call complete once for Cypher generation, once for formatting."""
5d70f82… lmata 367 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 368
5d70f82… lmata 369 cypher_response = "MATCH (n:Function) RETURN n.name LIMIT 5"
5d70f82… lmata 370 format_response = "There are 5 functions: ..."
5d70f82… lmata 371 provider = MagicMock()
5d70f82… lmata 372 provider.complete.side_effect = [cypher_response, format_response]
5d70f82… lmata 373
5d70f82… lmata 374 store = _mock_store([["func_a"], ["func_b"]])
5d70f82… lmata 375 engine = NLPEngine(store, provider)
5d70f82… lmata 376
5d70f82… lmata 377 result = engine.natural_query("List all functions")
5d70f82… lmata 378 assert result == format_response
5d70f82… lmata 379 assert provider.complete.call_count == 2
5d70f82… lmata 380
5d70f82… lmata 381 def test_natural_query_handles_query_error(self):
5d70f82… lmata 382 """When the generated Cypher fails, return an error message."""
5d70f82… lmata 383 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 384
5d70f82… lmata 385 provider = _mock_provider(complete_return="INVALID CYPHER !!!")
5d70f82… lmata 386 store = MagicMock()
5d70f82… lmata 387 store.query.side_effect = Exception("syntax error")
5d70f82… lmata 388
5d70f82… lmata 389 engine = NLPEngine(store, provider)
5d70f82… lmata 390 result = engine.natural_query("broken question")
5d70f82… lmata 391
5d70f82… lmata 392 assert "Failed" in result or "Error" in result or "syntax error" in result
5d70f82… lmata 393
5d70f82… lmata 394 def test_natural_query_strips_markdown_fences(self):
5d70f82… lmata 395 """LLM output with ```cypher fences should still execute."""
5d70f82… lmata 396 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 397
5d70f82… lmata 398 fenced_cypher = "```cypher\nMATCH (n) RETURN n.name LIMIT 1\n```"
5d70f82… lmata 399 provider = MagicMock()
5d70f82… lmata 400 provider.complete.side_effect = [fenced_cypher, "One node found."]
5d70f82… lmata 401
5d70f82… lmata 402 store = _mock_store([["some_node"]])
5d70f82… lmata 403 engine = NLPEngine(store, provider)
5d70f82… lmata 404 result = engine.natural_query("find a node")
5d70f82… lmata 405
5d70f82… lmata 406 assert result == "One node found."
5d70f82… lmata 407 # Verify the actual query executed was the clean Cypher (no fences)
5d70f82… lmata 408 executed_cypher = store.query.call_args[0][0]
5d70f82… lmata 409 assert "```" not in executed_cypher
5d70f82… lmata 410
5d70f82… lmata 411 def test_name_communities_returns_one_entry_per_community(self):
5d70f82… lmata 412 from navegador.intelligence.community import Community
5d70f82… lmata 413 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 414
5d70f82… lmata 415 store = _mock_store()
5d70f82… lmata 416 provider = _mock_provider(complete_return="Authentication Services")
5d70f82… lmata 417
5d70f82… lmata 418 comms = [
5d70f82… lmata 419 Community(name="community_1", members=["login", "logout", "verify_token"], size=3),
5d70f82… lmata 420 Community(name="community_2", members=["fetch_data", "store_record"], size=2),
5d70f82… lmata 421 ]
5d70f82… lmata 422 engine = NLPEngine(store, provider)
5d70f82… lmata 423 named = engine.name_communities(comms)
5d70f82… lmata 424
5d70f82… lmata 425 assert len(named) == 2
5d70f82… lmata 426 assert all("suggested_name" in n for n in named)
5d70f82… lmata 427 assert all("original_name" in n for n in named)
5d70f82… lmata 428 assert provider.complete.call_count == 2
5d70f82… lmata 429
5d70f82… lmata 430 def test_name_communities_fallback_on_llm_error(self):
5d70f82… lmata 431 """If LLM raises, the original name is used."""
5d70f82… lmata 432 from navegador.intelligence.community import Community
5d70f82… lmata 433 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 434
5d70f82… lmata 435 store = _mock_store()
5d70f82… lmata 436 provider = MagicMock()
5d70f82… lmata 437 provider.complete.side_effect = RuntimeError("API down")
5d70f82… lmata 438
5d70f82… lmata 439 comm = Community(name="community_0", members=["a", "b"], size=2)
5d70f82… lmata 440 engine = NLPEngine(store, provider)
5d70f82… lmata 441 named = engine.name_communities([comm])
5d70f82… lmata 442
5d70f82… lmata 443 assert named[0]["suggested_name"] == "community_0"
5d70f82… lmata 444
5d70f82… lmata 445 def test_generate_docs_returns_llm_string(self):
5d70f82… lmata 446 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 447
5d70f82… lmata 448 expected_docs = "## my_func\nDoes great things."
5d70f82… lmata 449 store = _mock_store([
5d70f82… lmata 450 ["Function", "my_func", "app.py", "Does great things.", "def my_func():"]
5d70f82… lmata 451 ])
5d70f82… lmata 452 # Make subsequent query calls (callers, callees) also return empty
5d70f82… lmata 453 store.query.side_effect = [
5d70f82… lmata 454 MagicMock(result_set=[["Function", "my_func", "app.py", "Does great things.", "def my_func():"]]),
5d70f82… lmata 455 MagicMock(result_set=[]),
5d70f82… lmata 456 MagicMock(result_set=[]),
5d70f82… lmata 457 ]
5d70f82… lmata 458 provider = _mock_provider(complete_return=expected_docs)
5d70f82… lmata 459
5d70f82… lmata 460 engine = NLPEngine(store, provider)
5d70f82… lmata 461 result = engine.generate_docs("my_func", file_path="app.py")
5d70f82… lmata 462
5d70f82… lmata 463 assert result == expected_docs
5d70f82… lmata 464 provider.complete.assert_called_once()
5d70f82… lmata 465
5d70f82… lmata 466 def test_generate_docs_works_when_node_not_found(self):
5d70f82… lmata 467 """When node doesn't exist, still calls LLM with empty context."""
5d70f82… lmata 468 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 469
5d70f82… lmata 470 store = MagicMock()
5d70f82… lmata 471 store.query.return_value = MagicMock(result_set=[])
5d70f82… lmata 472 provider = _mock_provider(complete_return="No docs available.")
5d70f82… lmata 473
5d70f82… lmata 474 engine = NLPEngine(store, provider)
5d70f82… lmata 475 result = engine.generate_docs("nonexistent_func")
5d70f82… lmata 476
5d70f82… lmata 477 assert "No docs available." in result
5d70f82… lmata 478
5d70f82… lmata 479
5d70f82… lmata 480 # ── DocGenerator (template mode) ─────────────────────────────────────────────
5d70f82… lmata 481
5d70f82… lmata 482
5d70f82… lmata 483 class TestDocGeneratorTemplateMode:
5d70f82… lmata 484 def test_generate_file_docs_returns_markdown_with_symbols(self):
5d70f82… lmata 485 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 486
5d70f82… lmata 487 rows = [
5d70f82… lmata 488 ["Function", "greet", "Does greeting", "def greet():", 10],
5d70f82… lmata 489 ["Class", "Greeter", "A greeter class", "class Greeter:", 20],
5d70f82… lmata 490 ]
5d70f82… lmata 491 store = _mock_store(rows)
5d70f82… lmata 492 gen = DocGenerator(store, provider=None)
5d70f82… lmata 493
5d70f82… lmata 494 docs = gen.generate_file_docs("app.py")
5d70f82… lmata 495
5d70f82… lmata 496 assert "app.py" in docs
5d70f82… lmata 497 assert "greet" in docs
5d70f82… lmata 498 assert "Greeter" in docs
5d70f82… lmata 499 assert "Does greeting" in docs
5d70f82… lmata 500
5d70f82… lmata 501 def test_generate_file_docs_handles_empty_file(self):
5d70f82… lmata 502 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 503
5d70f82… lmata 504 store = _mock_store([])
5d70f82… lmata 505 gen = DocGenerator(store, provider=None)
5d70f82… lmata 506
5d70f82… lmata 507 docs = gen.generate_file_docs("empty.py")
5d70f82… lmata 508 assert "No symbols" in docs
5d70f82… lmata 509
5d70f82… lmata 510 def test_generate_module_docs_groups_by_file(self):
5d70f82… lmata 511 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 512
5d70f82… lmata 513 rows = [
5d70f82… lmata 514 ["Function", "func_a", "nav/graph/store.py", "Store a node", "def func_a():"],
5d70f82… lmata 515 ["Class", "GraphStore", "nav/graph/store.py", "Wraps the graph.", "class GraphStore:"],
5d70f82… lmata 516 ["Function", "func_b", "nav/graph/queries.py", "Query helper", "def func_b():"],
5d70f82… lmata 517 ]
5d70f82… lmata 518 store = _mock_store(rows)
5d70f82… lmata 519 gen = DocGenerator(store, provider=None)
5d70f82… lmata 520
5d70f82… lmata 521 docs = gen.generate_module_docs("nav.graph")
5d70f82… lmata 522 assert "nav/graph/store.py" in docs
5d70f82… lmata 523 assert "nav/graph/queries.py" in docs
5d70f82… lmata 524 assert "func_a" in docs
5d70f82… lmata 525 assert "GraphStore" in docs
5d70f82… lmata 526
5d70f82… lmata 527 def test_generate_module_docs_handles_no_results(self):
5d70f82… lmata 528 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 529
5d70f82… lmata 530 store = _mock_store([])
5d70f82… lmata 531 gen = DocGenerator(store, provider=None)
5d70f82… lmata 532
5d70f82… lmata 533 docs = gen.generate_module_docs("empty.module")
5d70f82… lmata 534 assert "No symbols" in docs
5d70f82… lmata 535
5d70f82… lmata 536 def test_generate_project_docs_includes_stats_and_files(self):
5d70f82… lmata 537 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 538
5d70f82… lmata 539 store = MagicMock()
5d70f82… lmata 540
5d70f82… lmata 541 stats_result = MagicMock()
5d70f82… lmata 542 stats_result.result_set = [
5d70f82… lmata 543 ["Function", 42],
5d70f82… lmata 544 ["Class", 10],
5d70f82… lmata 545 ]
5d70f82… lmata 546 files_result = MagicMock()
5d70f82… lmata 547 files_result.result_set = [
5d70f82… lmata 548 ["navegador/graph/store.py"],
5d70f82… lmata 549 ["navegador/cli/commands.py"],
5d70f82… lmata 550 ]
5d70f82… lmata 551 sym_result = MagicMock()
5d70f82… lmata 552 sym_result.result_set = [
5d70f82… lmata 553 ["Function", "my_func", "navegador/graph/store.py", "Does things"],
5d70f82… lmata 554 ]
5d70f82… lmata 555 store.query.side_effect = [stats_result, files_result, sym_result]
5d70f82… lmata 556
5d70f82… lmata 557 gen = DocGenerator(store, provider=None)
5d70f82… lmata 558 docs = gen.generate_project_docs()
5d70f82… lmata 559
5d70f82… lmata 560 assert "Project Documentation" in docs
5d70f82… lmata 561 assert "Function" in docs
5d70f82… lmata 562 assert "42" in docs
5d70f82… lmata 563 assert "navegador/graph/store.py" in docs
5d70f82… lmata 564
5d70f82… lmata 565 def test_signature_included_when_present(self):
5d70f82… lmata 566 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 567
5d70f82… lmata 568 rows = [["Function", "my_func", "My doc", "def my_func(x: int) -> str:", 5]]
5d70f82… lmata 569 store = _mock_store(rows)
5d70f82… lmata 570 gen = DocGenerator(store, provider=None)
5d70f82… lmata 571
5d70f82… lmata 572 docs = gen.generate_file_docs("f.py")
5d70f82… lmata 573 assert "def my_func(x: int) -> str:" in docs
5d70f82… lmata 574
5d70f82… lmata 575
5d70f82… lmata 576 # ── DocGenerator (LLM mode) ───────────────────────────────────────────────────
5d70f82… lmata 577
5d70f82… lmata 578
5d70f82… lmata 579 class TestDocGeneratorLLMMode:
5d70f82… lmata 580 def test_generate_file_docs_uses_nlp_engine(self):
5d70f82… lmata 581 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 582
5d70f82… lmata 583 rows = [["Function", "my_func", "Generated docs for my_func", "def my_func():", 1]]
5d70f82… lmata 584 store = MagicMock()
5d70f82… lmata 585 # 1st call: _FILE_SYMBOLS 2nd+: NLPEngine internal calls
5d70f82… lmata 586 store.query.return_value = MagicMock(result_set=rows)
5d70f82… lmata 587
5d70f82… lmata 588 provider = _mock_provider(complete_return="## my_func\nLLM-generated content.")
5d70f82… lmata 589 gen = DocGenerator(store, provider=provider)
5d70f82… lmata 590 docs = gen.generate_file_docs("app.py")
5d70f82… lmata 591
5d70f82… lmata 592 assert "app.py" in docs
5d70f82… lmata 593 provider.complete.assert_called()
5d70f82… lmata 594
5d70f82… lmata 595 def test_generate_project_docs_uses_llm(self):
5d70f82… lmata 596 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 597
5d70f82… lmata 598 store = MagicMock()
5d70f82… lmata 599 # Return empty for template sub-calls
5d70f82… lmata 600 store.query.return_value = MagicMock(result_set=[])
5d70f82… lmata 601
5d70f82… lmata 602 provider = _mock_provider(complete_return="# Project README\nLLM wrote this.")
5d70f82… lmata 603 gen = DocGenerator(store, provider=provider)
5d70f82… lmata 604 docs = gen.generate_project_docs()
5d70f82… lmata 605
5d70f82… lmata 606 assert "Project README" in docs or "LLM wrote this" in docs
5d70f82… lmata 607 provider.complete.assert_called_once()
5d70f82… lmata 608
5d70f82… lmata 609
5d70f82… lmata 610 # ── CLI: semantic-search ──────────────────────────────────────────────────────
5d70f82… lmata 611
5d70f82… lmata 612
5d70f82… lmata 613 class TestSemanticSearchCLI:
5d70f82… lmata 614 def test_search_outputs_table(self):
5d70f82… lmata 615 runner = CliRunner()
5d70f82… lmata 616 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 617 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 618 store = _mock_store([])
5d70f82… lmata 619 mock_store_fn.return_value = store
5d70f82… lmata 620 mock_provider = _mock_provider(embed_return=[1.0, 0.0])
5d70f82… lmata 621 mock_auto.return_value = mock_provider
5d70f82… lmata 622
5d70f82… lmata 623 # search returns no results
5d70f82… lmata 624 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 625 with patch.object(SemanticSearch, "search", return_value=[]):
5d70f82… lmata 626 result = runner.invoke(main, ["semantic-search", "test query"])
5d70f82… lmata 627 assert result.exit_code == 0
5d70f82… lmata 628
5d70f82… lmata 629 def test_search_with_index_flag(self):
5d70f82… lmata 630 runner = CliRunner()
5d70f82… lmata 631 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 632 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 633 store = _mock_store([])
5d70f82… lmata 634 mock_store_fn.return_value = store
5d70f82… lmata 635 mock_provider = _mock_provider()
5d70f82… lmata 636 mock_auto.return_value = mock_provider
5d70f82… lmata 637
5d70f82… lmata 638 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 639 with patch.object(SemanticSearch, "index", return_value=5) as mock_index, \
5d70f82… lmata 640 patch.object(SemanticSearch, "search", return_value=[]):
5d70f82… lmata 641 result = runner.invoke(main, ["semantic-search", "test", "--index"])
5d70f82… lmata 642 assert result.exit_code == 0
5d70f82… lmata 643 mock_index.assert_called_once()
5d70f82… lmata 644
5d70f82… lmata 645 def test_search_json_output(self):
5d70f82… lmata 646 runner = CliRunner()
5d70f82… lmata 647 fake_results = [
5d70f82… lmata 648 {"type": "Function", "name": "foo", "file_path": "a.py", "text": "doc", "score": 0.95}
5d70f82… lmata 649 ]
5d70f82… lmata 650 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 651 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 652 store = _mock_store([])
5d70f82… lmata 653 mock_store_fn.return_value = store
5d70f82… lmata 654 mock_auto.return_value = _mock_provider()
5d70f82… lmata 655
5d70f82… lmata 656 from navegador.intelligence.search import SemanticSearch
5d70f82… lmata 657 with patch.object(SemanticSearch, "search", return_value=fake_results):
5d70f82… lmata 658 result = runner.invoke(main, ["semantic-search", "foo", "--json"])
5d70f82… lmata 659 assert result.exit_code == 0
5d70f82… lmata 660 data = json.loads(result.output)
5d70f82… lmata 661 assert isinstance(data, list)
5d70f82… lmata 662 assert data[0]["name"] == "foo"
5d70f82… lmata 663
5d70f82… lmata 664
5d70f82… lmata 665 # ── CLI: communities ──────────────────────────────────────────────────────────
5d70f82… lmata 666
5d70f82… lmata 667
5d70f82… lmata 668 class TestCommunitiesCLI:
5d70f82… lmata 669 def _make_communities(self):
5d70f82… lmata 670 from navegador.intelligence.community import Community
5d70f82… lmata 671
5d70f82… lmata 672 return [
5d70f82… lmata 673 Community(name="community_0", members=["a", "b", "c"], size=3, density=1.0),
5d70f82… lmata 674 Community(name="community_1", members=["x", "y"], size=2, density=1.0),
5d70f82… lmata 675 ]
5d70f82… lmata 676
5d70f82… lmata 677 def test_communities_outputs_table(self):
5d70f82… lmata 678 runner = CliRunner()
5d70f82… lmata 679 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 680 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 681 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 682 with patch.object(CommunityDetector, "detect", return_value=self._make_communities()):
5d70f82… lmata 683 result = runner.invoke(main, ["communities"])
5d70f82… lmata 684 assert result.exit_code == 0
5d70f82… lmata 685
5d70f82… lmata 686 def test_communities_json_output(self):
5d70f82… lmata 687 runner = CliRunner()
5d70f82… lmata 688 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 689 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 690 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 691 with patch.object(CommunityDetector, "detect", return_value=self._make_communities()):
5d70f82… lmata 692 result = runner.invoke(main, ["communities", "--json"])
5d70f82… lmata 693 assert result.exit_code == 0
5d70f82… lmata 694 data = json.loads(result.output)
5d70f82… lmata 695 assert len(data) == 2
5d70f82… lmata 696 assert data[0]["name"] == "community_0"
5d70f82… lmata 697
5d70f82… lmata 698 def test_communities_min_size_passed(self):
5d70f82… lmata 699 runner = CliRunner()
5d70f82… lmata 700 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 701 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 702 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 703 with patch.object(CommunityDetector, "detect", return_value=[]) as mock_detect:
5d70f82… lmata 704 runner.invoke(main, ["communities", "--min-size", "5"])
5d70f82… lmata 705 mock_detect.assert_called_once_with(min_size=5)
5d70f82… lmata 706
5d70f82… lmata 707 def test_communities_empty_graph_message(self):
5d70f82… lmata 708 runner = CliRunner()
5d70f82… lmata 709 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 710 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 711 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 712 with patch.object(CommunityDetector, "detect", return_value=[]):
5d70f82… lmata 713 result = runner.invoke(main, ["communities"])
5d70f82… lmata 714 assert result.exit_code == 0
5d70f82… lmata 715 assert "No communities" in result.output or result.exit_code == 0
5d70f82… lmata 716
5d70f82… lmata 717 def test_communities_store_labels_flag(self):
5d70f82… lmata 718 runner = CliRunner()
5d70f82… lmata 719 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 720 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 721 from navegador.intelligence.community import CommunityDetector
5d70f82… lmata 722 with patch.object(CommunityDetector, "detect", return_value=self._make_communities()), \
5d70f82… lmata 723 patch.object(CommunityDetector, "store_communities", return_value=5) as mock_store:
5d70f82… lmata 724 result = runner.invoke(main, ["communities", "--store-labels"])
5d70f82… lmata 725 assert result.exit_code == 0
5d70f82… lmata 726 mock_store.assert_called_once()
5d70f82… lmata 727
5d70f82… lmata 728
5d70f82… lmata 729 # ── CLI: ask ──────────────────────────────────────────────────────────────────
5d70f82… lmata 730
5d70f82… lmata 731
5d70f82… lmata 732 class TestAskCLI:
5d70f82… lmata 733 def test_ask_prints_answer(self):
5d70f82… lmata 734 runner = CliRunner()
5d70f82… lmata 735 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 736 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 737 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 738 mock_auto.return_value = _mock_provider()
5d70f82… lmata 739
5d70f82… lmata 740 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 741 with patch.object(NLPEngine, "natural_query", return_value="The answer is 42."):
5d70f82… lmata 742 result = runner.invoke(main, ["ask", "What is the answer?"])
5d70f82… lmata 743 assert result.exit_code == 0
5d70f82… lmata 744 assert "42" in result.output
5d70f82… lmata 745
5d70f82… lmata 746 def test_ask_with_explicit_provider(self):
5d70f82… lmata 747 runner = CliRunner()
5d70f82… lmata 748 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 749 patch("navegador.llm.get_provider") as mock_get:
5d70f82… lmata 750 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 751 mock_get.return_value = _mock_provider()
5d70f82… lmata 752
5d70f82… lmata 753 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 754 with patch.object(NLPEngine, "natural_query", return_value="Answer."):
5d70f82… lmata 755 result = runner.invoke(
5d70f82… lmata 756 main, ["ask", "question", "--provider", "openai"]
5d70f82… lmata 757 )
5d70f82… lmata 758 assert result.exit_code == 0
5d70f82… lmata 759 mock_get.assert_called_once_with("openai", model="")
5d70f82… lmata 760
5d70f82… lmata 761
5d70f82… lmata 762 # ── CLI: generate-docs ────────────────────────────────────────────────────────
5d70f82… lmata 763
5d70f82… lmata 764
5d70f82… lmata 765 class TestGenerateDocsCLI:
5d70f82… lmata 766 def test_generate_docs_prints_output(self):
5d70f82… lmata 767 runner = CliRunner()
5d70f82… lmata 768 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 769 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 770 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 771 mock_auto.return_value = _mock_provider()
5d70f82… lmata 772
5d70f82… lmata 773 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 774 with patch.object(NLPEngine, "generate_docs", return_value="## my_func\nDocs here."):
5d70f82… lmata 775 result = runner.invoke(main, ["generate-docs", "my_func"])
5d70f82… lmata 776 assert result.exit_code == 0
5d70f82… lmata 777 assert "my_func" in result.output or "Docs" in result.output
5d70f82… lmata 778
5d70f82… lmata 779 def test_generate_docs_with_file_option(self):
5d70f82… lmata 780 runner = CliRunner()
5d70f82… lmata 781 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 782 patch("navegador.llm.auto_provider") as mock_auto:
5d70f82… lmata 783 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 784 mock_auto.return_value = _mock_provider()
5d70f82… lmata 785
5d70f82… lmata 786 from navegador.intelligence.nlp import NLPEngine
5d70f82… lmata 787 with patch.object(NLPEngine, "generate_docs", return_value="Docs.") as mock_gd:
5d70f82… lmata 788 runner.invoke(
5d70f82… lmata 789 main, ["generate-docs", "my_func", "--file", "app.py"]
5d70f82… lmata 790 )
5d70f82… lmata 791 mock_gd.assert_called_once_with("my_func", file_path="app.py")
5d70f82… lmata 792
5d70f82… lmata 793
5d70f82… lmata 794 # ── CLI: docs ─────────────────────────────────────────────────────────────────
5d70f82… lmata 795
5d70f82… lmata 796
5d70f82… lmata 797 class TestDocsCLI:
5d70f82… lmata 798 def test_docs_file_path(self):
5d70f82… lmata 799 runner = CliRunner()
5d70f82… lmata 800 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 801 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 802 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 803 with patch.object(DocGenerator, "generate_file_docs", return_value="# File docs") as mock_fd:
5d70f82… lmata 804 result = runner.invoke(main, ["docs", "app/store.py"])
5d70f82… lmata 805 assert result.exit_code == 0
5d70f82… lmata 806 mock_fd.assert_called_once_with("app/store.py")
5d70f82… lmata 807
5d70f82… lmata 808 def test_docs_module_name(self):
5d70f82… lmata 809 runner = CliRunner()
5d70f82… lmata 810 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 811 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 812 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 813 with patch.object(DocGenerator, "generate_module_docs", return_value="# Module docs") as mock_md:
5d70f82… lmata 814 result = runner.invoke(main, ["docs", "navegador.graph"])
5d70f82… lmata 815 assert result.exit_code == 0
5d70f82… lmata 816 mock_md.assert_called_once_with("navegador.graph")
5d70f82… lmata 817
5d70f82… lmata 818 def test_docs_project_flag(self):
5d70f82… lmata 819 runner = CliRunner()
5d70f82… lmata 820 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 821 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 822 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 823 with patch.object(DocGenerator, "generate_project_docs", return_value="# Project") as mock_pd:
5d70f82… lmata 824 result = runner.invoke(main, ["docs", ".", "--project"])
5d70f82… lmata 825 assert result.exit_code == 0
5d70f82… lmata 826 mock_pd.assert_called_once()
5d70f82… lmata 827
5d70f82… lmata 828 def test_docs_json_output(self):
5d70f82… lmata 829 runner = CliRunner()
5d70f82… lmata 830 with patch("navegador.cli.commands._get_store") as mock_store_fn:
5d70f82… lmata 831 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 832 from navegador.intelligence.docgen import DocGenerator
5d70f82… lmata 833 with patch.object(DocGenerator, "generate_project_docs", return_value="# Project"):
5d70f82… lmata 834 result = runner.invoke(main, ["docs", ".", "--project", "--json"])
5d70f82… lmata 835 assert result.exit_code == 0
5d70f82… lmata 836 data = json.loads(result.output)
5d70f82… lmata 837 assert "docs" in data
5d70f82… lmata 838
5d70f82… lmata 839 def test_docs_with_llm_provider(self):
5d70f82… lmata 840 runner = CliRunner()
5d70f82… lmata 841 with patch("navegador.cli.commands._get_store") as mock_store_fn, \
5d70f82… lmata 842 patch("navegador.intelligence.docgen.DocGenerator.generate_file_docs", return_value="# Docs"):
5d70f82… lmata 843 mock_store_fn.return_value = _mock_store()
5d70f82… lmata 844 with patch("navegador.llm.get_provider") as mock_get:
5d70f82… lmata 845 mock_get.return_value = _mock_provider()
5d70f82… lmata 846 result = runner.invoke(
5d70f82… lmata 847 main, ["docs", "app/store.py", "--provider", "openai"]
5d70f82… lmata 848 )
5d70f82… lmata 849 assert result.exit_code == 0
5d70f82… lmata 850 mock_get.assert_called_once_with("openai", model="")

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button