Navegador

navegador / tests / test_coverage_boost.py
Source Blame History 2526 lines
c054b6b… lmata 1 """
c054b6b… lmata 2 Targeted tests to boost coverage from ~91% to 95%+.
c054b6b… lmata 3
c054b6b… lmata 4 One test class per module. All external dependencies (Redis, tree-sitter,
c054b6b… lmata 5 HTTP) are mocked; no real infrastructure is required.
c054b6b… lmata 6 """
c054b6b… lmata 7
c054b6b… lmata 8 from __future__ import annotations
c054b6b… lmata 9
c054b6b… lmata 10 import json
c054b6b… lmata 11 import tempfile
c054b6b… lmata 12 from pathlib import Path
c054b6b… lmata 13 from unittest.mock import MagicMock, patch
c054b6b… lmata 14
c054b6b… lmata 15 import pytest
c054b6b… lmata 16
c054b6b… lmata 17
c054b6b… lmata 18 # ── Helpers ───────────────────────────────────────────────────────────────────
c054b6b… lmata 19
c054b6b… lmata 20
c054b6b… lmata 21 def _make_store():
c054b6b… lmata 22 store = MagicMock()
c054b6b… lmata 23 store.query.return_value = MagicMock(result_set=[])
c054b6b… lmata 24 store.node_count.return_value = 0
c054b6b… lmata 25 store.edge_count.return_value = 0
c054b6b… lmata 26 return store
c054b6b… lmata 27
c054b6b… lmata 28
c054b6b… lmata 29 def _write(path: Path, content: str) -> None:
c054b6b… lmata 30 path.parent.mkdir(parents=True, exist_ok=True)
c054b6b… lmata 31 path.write_text(content, encoding="utf-8")
c054b6b… lmata 32
c054b6b… lmata 33
c054b6b… lmata 34 # ── MockNode for AST tests ────────────────────────────────────────────────────
c054b6b… lmata 35
c054b6b… lmata 36
c054b6b… lmata 37 class MockNode:
c054b6b… lmata 38 def __init__(
c054b6b… lmata 39 self,
c054b6b… lmata 40 type_: str,
c054b6b… lmata 41 text: bytes = b"",
c054b6b… lmata 42 children: list = None,
c054b6b… lmata 43 start_byte: int = 0,
c054b6b… lmata 44 end_byte: int = 0,
c054b6b… lmata 45 start_point: tuple = (0, 0),
c054b6b… lmata 46 end_point: tuple = (5, 0),
c054b6b… lmata 47 ):
c054b6b… lmata 48 self.type = type_
c054b6b… lmata 49 self.children = children or []
c054b6b… lmata 50 self.start_byte = start_byte
c054b6b… lmata 51 self.end_byte = end_byte
c054b6b… lmata 52 self.start_point = start_point
c054b6b… lmata 53 self.end_point = end_point
c054b6b… lmata 54 self._fields: dict = {}
c054b6b… lmata 55
c054b6b… lmata 56 def child_by_field_name(self, name: str):
c054b6b… lmata 57 return self._fields.get(name)
c054b6b… lmata 58
c054b6b… lmata 59 def set_field(self, name: str, node: "MockNode") -> "MockNode":
c054b6b… lmata 60 self._fields[name] = node
c054b6b… lmata 61 return self
c054b6b… lmata 62
c054b6b… lmata 63
c054b6b… lmata 64 def _text_node(text: bytes, type_: str = "identifier") -> MockNode:
c054b6b… lmata 65 return MockNode(type_, text, start_byte=0, end_byte=len(text))
c054b6b… lmata 66
c054b6b… lmata 67
c054b6b… lmata 68 def _make_mock_tree(root_node: MockNode):
c054b6b… lmata 69 tree = MagicMock()
c054b6b… lmata 70 tree.root_node = root_node
c054b6b… lmata 71 return tree
c054b6b… lmata 72
c054b6b… lmata 73
c054b6b… lmata 74 def _mock_ts(lang_module_name: str):
c054b6b… lmata 75 mock_lang_module = MagicMock()
c054b6b… lmata 76 mock_ts = MagicMock()
c054b6b… lmata 77 return patch.dict("sys.modules", {lang_module_name: mock_lang_module, "tree_sitter": mock_ts})
c054b6b… lmata 78
c054b6b… lmata 79
c054b6b… lmata 80 # ===========================================================================
c054b6b… lmata 81 # navegador.api_schema (57% → target ~90%)
c054b6b… lmata 82 # ===========================================================================
c054b6b… lmata 83
c054b6b… lmata 84
c054b6b… lmata 85 class TestAPISchemaIngesterOpenAPI:
c054b6b… lmata 86 """Cover lines 72, 105, 213, 217-225, 234-241, 255-294, 299-319."""
c054b6b… lmata 87
c054b6b… lmata 88 def _make(self):
c054b6b… lmata 89 from navegador.api_schema import APISchemaIngester
c054b6b… lmata 90
c054b6b… lmata 91 return APISchemaIngester(_make_store())
c054b6b… lmata 92
c054b6b… lmata 93 def test_ingest_openapi_yaml_with_paths(self, tmp_path):
c054b6b… lmata 94 content = """
c054b6b… lmata 95 openapi: 3.0.0
c054b6b… lmata 96 info:
c054b6b… lmata 97 title: Test
c054b6b… lmata 98 paths:
c054b6b… lmata 99 /users:
c054b6b… lmata 100 get:
c054b6b… lmata 101 operationId: listUsers
c054b6b… lmata 102 summary: List users
c054b6b… lmata 103 tags:
c054b6b… lmata 104 - users
c054b6b… lmata 105 post:
c054b6b… lmata 106 summary: Create user
c054b6b… lmata 107 components:
c054b6b… lmata 108 schemas:
c054b6b… lmata 109 User:
c054b6b… lmata 110 type: object
c054b6b… lmata 111 description: A user object
c054b6b… lmata 112 """
c054b6b… lmata 113 f = tmp_path / "api.yaml"
c054b6b… lmata 114 f.write_text(content)
c054b6b… lmata 115 ingester = self._make()
c054b6b… lmata 116 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 117 assert stats["endpoints"] >= 2
c054b6b… lmata 118 assert stats["schemas"] >= 1
c054b6b… lmata 119
c054b6b… lmata 120 def test_ingest_openapi_json(self, tmp_path):
c054b6b… lmata 121 spec = {
c054b6b… lmata 122 "openapi": "3.0.0",
c054b6b… lmata 123 "paths": {
c054b6b… lmata 124 "/items": {
c054b6b… lmata 125 "get": {"operationId": "listItems", "summary": "List"},
c054b6b… lmata 126 "delete": {"summary": "Delete"},
c054b6b… lmata 127 }
c054b6b… lmata 128 },
c054b6b… lmata 129 "components": {"schemas": {"Item": {"description": "item"}}},
c054b6b… lmata 130 }
c054b6b… lmata 131 f = tmp_path / "api.json"
c054b6b… lmata 132 f.write_text(json.dumps(spec))
c054b6b… lmata 133 ingester = self._make()
c054b6b… lmata 134 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 135 assert stats["endpoints"] >= 2
c054b6b… lmata 136 assert stats["schemas"] >= 1
c054b6b… lmata 137
c054b6b… lmata 138 def test_ingest_openapi_missing_file(self, tmp_path):
c054b6b… lmata 139 ingester = self._make()
c054b6b… lmata 140 stats = ingester.ingest_openapi(str(tmp_path / "missing.yaml"))
c054b6b… lmata 141 assert stats == {"endpoints": 0, "schemas": 0}
c054b6b… lmata 142
c054b6b… lmata 143 def test_ingest_openapi_swagger2_definitions(self, tmp_path):
c054b6b… lmata 144 spec = {
c054b6b… lmata 145 "swagger": "2.0",
c054b6b… lmata 146 "paths": {
c054b6b… lmata 147 "/pets": {"get": {"summary": "List pets"}}
c054b6b… lmata 148 },
c054b6b… lmata 149 "definitions": {"Pet": {"description": "pet"}},
c054b6b… lmata 150 }
c054b6b… lmata 151 f = tmp_path / "swagger.json"
c054b6b… lmata 152 f.write_text(json.dumps(spec))
c054b6b… lmata 153 ingester = self._make()
c054b6b… lmata 154 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 155 assert stats["schemas"] >= 1
c054b6b… lmata 156
c054b6b… lmata 157 def test_ingest_openapi_operation_no_id(self, tmp_path):
c054b6b… lmata 158 # operationId absent → synthesised as "METHOD /path"
c054b6b… lmata 159 spec = {
c054b6b… lmata 160 "paths": {"/x": {"put": {"summary": "update"}}},
c054b6b… lmata 161 }
c054b6b… lmata 162 f = tmp_path / "api.json"
c054b6b… lmata 163 f.write_text(json.dumps(spec))
c054b6b… lmata 164 ingester = self._make()
c054b6b… lmata 165 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 166 assert stats["endpoints"] == 1
c054b6b… lmata 167
c054b6b… lmata 168 def test_ingest_openapi_invalid_json(self, tmp_path):
c054b6b… lmata 169 f = tmp_path / "bad.json"
c054b6b… lmata 170 f.write_text("{not valid json")
c054b6b… lmata 171 ingester = self._make()
c054b6b… lmata 172 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 173 assert stats == {"endpoints": 0, "schemas": 0}
c054b6b… lmata 174
c054b6b… lmata 175 def test_ingest_openapi_unknown_extension_tries_json(self, tmp_path):
c054b6b… lmata 176 spec = {"paths": {"/x": {"get": {"summary": "hi"}}}}
c054b6b… lmata 177 f = tmp_path / "api.txt"
c054b6b… lmata 178 f.write_text(json.dumps(spec))
c054b6b… lmata 179 ingester = self._make()
c054b6b… lmata 180 stats = ingester.ingest_openapi(str(f))
c054b6b… lmata 181 assert stats["endpoints"] == 1
c054b6b… lmata 182
c054b6b… lmata 183 def test_ingest_openapi_unknown_extension_falls_back_to_yaml(self, tmp_path):
c054b6b… lmata 184 # Not valid JSON → falls back to _parse_yaml
c054b6b… lmata 185 f = tmp_path / "api.txt"
c054b6b… lmata 186 f.write_text("paths:\n /x:\n get:\n summary: hi\n")
c054b6b… lmata 187 ingester = self._make()
c054b6b… lmata 188 # Should not raise
c054b6b… lmata 189 ingester.ingest_openapi(str(f))
c054b6b… lmata 190
c054b6b… lmata 191
c054b6b… lmata 192 class TestAPISchemaIngesterGraphQL:
c054b6b… lmata 193 def _make(self):
c054b6b… lmata 194 from navegador.api_schema import APISchemaIngester
c054b6b… lmata 195
c054b6b… lmata 196 return APISchemaIngester(_make_store())
c054b6b… lmata 197
c054b6b… lmata 198 def test_ingest_graphql_types_and_fields(self, tmp_path):
c054b6b… lmata 199 sdl = """
c054b6b… lmata 200 type Query {
c054b6b… lmata 201 user(id: ID!): User
c054b6b… lmata 202 users: [User]
c054b6b… lmata 203 }
c054b6b… lmata 204 type Mutation {
c054b6b… lmata 205 createUser(name: String!): User
c054b6b… lmata 206 }
c054b6b… lmata 207 type User {
c054b6b… lmata 208 id: ID
c054b6b… lmata 209 name: String
c054b6b… lmata 210 }
c054b6b… lmata 211 input CreateUserInput {
c054b6b… lmata 212 name: String!
c054b6b… lmata 213 }
c054b6b… lmata 214 """
c054b6b… lmata 215 f = tmp_path / "schema.graphql"
c054b6b… lmata 216 f.write_text(sdl)
c054b6b… lmata 217 ingester = self._make()
c054b6b… lmata 218 stats = ingester.ingest_graphql(str(f))
c054b6b… lmata 219 assert stats["fields"] >= 2 # Query fields
c054b6b… lmata 220 assert stats["types"] >= 2 # User + CreateUserInput
c054b6b… lmata 221
c054b6b… lmata 222 def test_ingest_graphql_missing_file(self, tmp_path):
c054b6b… lmata 223 ingester = self._make()
c054b6b… lmata 224 stats = ingester.ingest_graphql(str(tmp_path / "missing.graphql"))
c054b6b… lmata 225 assert stats == {"types": 0, "fields": 0}
c054b6b… lmata 226
c054b6b… lmata 227 def test_ingest_graphql_empty_schema(self, tmp_path):
c054b6b… lmata 228 f = tmp_path / "empty.graphql"
c054b6b… lmata 229 f.write_text("# just a comment")
c054b6b… lmata 230 ingester = self._make()
c054b6b… lmata 231 stats = ingester.ingest_graphql(str(f))
c054b6b… lmata 232 assert stats["types"] == 0
c054b6b… lmata 233 assert stats["fields"] == 0
c054b6b… lmata 234
c054b6b… lmata 235
c054b6b… lmata 236 class TestMinimalYamlLoad:
c054b6b… lmata 237 def test_simple_key_value(self):
c054b6b… lmata 238 from navegador.api_schema import _minimal_yaml_load
c054b6b… lmata 239
c054b6b… lmata 240 result = _minimal_yaml_load("title: My API\nversion: 3")
c054b6b… lmata 241 assert result.get("title") == "My API"
c054b6b… lmata 242
c054b6b… lmata 243 def test_boolean_scalars(self):
c054b6b… lmata 244 from navegador.api_schema import _yaml_scalar
c054b6b… lmata 245
c054b6b… lmata 246 assert _yaml_scalar("true") is True
c054b6b… lmata 247 assert _yaml_scalar("false") is False
c054b6b… lmata 248 assert _yaml_scalar("yes") is True
c054b6b… lmata 249 assert _yaml_scalar("no") is False
c054b6b… lmata 250
c054b6b… lmata 251 def test_null_scalars(self):
c054b6b… lmata 252 from navegador.api_schema import _yaml_scalar
c054b6b… lmata 253
c054b6b… lmata 254 assert _yaml_scalar("null") is None
c054b6b… lmata 255 assert _yaml_scalar("~") is None
c054b6b… lmata 256 assert _yaml_scalar("") is None
c054b6b… lmata 257
c054b6b… lmata 258 def test_quoted_strings(self):
c054b6b… lmata 259 from navegador.api_schema import _yaml_scalar
c054b6b… lmata 260
c054b6b… lmata 261 assert _yaml_scalar('"hello"') == "hello"
c054b6b… lmata 262 assert _yaml_scalar("'world'") == "world"
c054b6b… lmata 263
c054b6b… lmata 264 def test_int_float(self):
c054b6b… lmata 265 from navegador.api_schema import _yaml_scalar
c054b6b… lmata 266
c054b6b… lmata 267 assert _yaml_scalar("42") == 42
c054b6b… lmata 268 assert _yaml_scalar("3.14") == pytest.approx(3.14)
c054b6b… lmata 269
c054b6b… lmata 270 def test_bare_string(self):
c054b6b… lmata 271 from navegador.api_schema import _yaml_scalar
c054b6b… lmata 272
c054b6b… lmata 273 assert _yaml_scalar("application/json") == "application/json"
c054b6b… lmata 274
c054b6b… lmata 275 def test_list_items(self):
c054b6b… lmata 276 from navegador.api_schema import _minimal_yaml_load
c054b6b… lmata 277
c054b6b… lmata 278 text = "tags:\n - users\n - admin\n"
c054b6b… lmata 279 # Should not raise even if list parsing is minimal
c054b6b… lmata 280 result = _minimal_yaml_load(text)
c054b6b… lmata 281 assert isinstance(result, dict)
c054b6b… lmata 282
c054b6b… lmata 283 def test_comments_skipped(self):
c054b6b… lmata 284 from navegador.api_schema import _minimal_yaml_load
c054b6b… lmata 285
c054b6b… lmata 286 text = "# comment\ntitle: test\n"
c054b6b… lmata 287 result = _minimal_yaml_load(text)
c054b6b… lmata 288 assert result.get("title") == "test"
c054b6b… lmata 289
c054b6b… lmata 290 def test_block_scalar_placeholder(self):
c054b6b… lmata 291 from navegador.api_schema import _minimal_yaml_load
c054b6b… lmata 292
c054b6b… lmata 293 text = "description: |\n some text\ntitle: test\n"
c054b6b… lmata 294 result = _minimal_yaml_load(text)
c054b6b… lmata 295 # Should have a nested dict for the block scalar key
c054b6b… lmata 296 assert "description" in result
c054b6b… lmata 297
c054b6b… lmata 298 def test_parse_yaml_uses_pyyaml_if_available(self, tmp_path):
c054b6b… lmata 299 from navegador.api_schema import APISchemaIngester
c054b6b… lmata 300
c054b6b… lmata 301 ingester = APISchemaIngester(_make_store())
c054b6b… lmata 302 mock_yaml = MagicMock()
c054b6b… lmata 303 mock_yaml.safe_load.return_value = {"openapi": "3.0.0", "paths": {}}
c054b6b… lmata 304 with patch.dict("sys.modules", {"yaml": mock_yaml}):
c054b6b… lmata 305 result = ingester._parse_yaml("openapi: 3.0.0")
c054b6b… lmata 306 assert result == {"openapi": "3.0.0", "paths": {}}
c054b6b… lmata 307
c054b6b… lmata 308 def test_parse_yaml_falls_back_when_no_pyyaml(self):
c054b6b… lmata 309 from navegador.api_schema import APISchemaIngester
c054b6b… lmata 310
c054b6b… lmata 311 ingester = APISchemaIngester(_make_store())
c054b6b… lmata 312 import sys
c054b6b… lmata 313
c054b6b… lmata 314 original = sys.modules.pop("yaml", None)
c054b6b… lmata 315 try:
c054b6b… lmata 316 # Simulate no yaml installed
c054b6b… lmata 317 with patch.dict("sys.modules", {"yaml": None}):
c054b6b… lmata 318 result = ingester._parse_yaml("title: test\n")
c054b6b… lmata 319 assert isinstance(result, dict)
c054b6b… lmata 320 finally:
c054b6b… lmata 321 if original is not None:
c054b6b… lmata 322 sys.modules["yaml"] = original
c054b6b… lmata 323
c054b6b… lmata 324
c054b6b… lmata 325 # ===========================================================================
c054b6b… lmata 326 # navegador.cluster.core (50% → target ~85%)
c054b6b… lmata 327 # ===========================================================================
c054b6b… lmata 328
c054b6b… lmata 329
c054b6b… lmata 330 class TestClusterManagerLocalVersion:
c054b6b… lmata 331 """Cover _local_version, _set_local_version, snapshot_to_local, push_to_shared, sync."""
c054b6b… lmata 332
c054b6b… lmata 333 def _make(self, tmp_path):
c054b6b… lmata 334 from navegador.cluster.core import ClusterManager
c054b6b… lmata 335
c054b6b… lmata 336 r = MagicMock()
c054b6b… lmata 337 pipe = MagicMock()
c054b6b… lmata 338 pipe.execute.return_value = [True, True, True]
c054b6b… lmata 339 r.pipeline.return_value = pipe
c054b6b… lmata 340 r.get.return_value = b"3"
c054b6b… lmata 341 return ClusterManager(
c054b6b… lmata 342 "redis://localhost:6379",
c054b6b… lmata 343 local_db_path=str(tmp_path / "graph.db"),
c054b6b… lmata 344 redis_client=r,
c054b6b… lmata 345 ), r, pipe
c054b6b… lmata 346
c054b6b… lmata 347 def test_local_version_zero_when_no_meta(self, tmp_path):
c054b6b… lmata 348 mgr, _, _ = self._make(tmp_path)
c054b6b… lmata 349 assert mgr._local_version() == 0
c054b6b… lmata 350
c054b6b… lmata 351 def test_set_and_read_local_version(self, tmp_path):
c054b6b… lmata 352 mgr, _, _ = self._make(tmp_path)
c054b6b… lmata 353 mgr._set_local_version(7)
c054b6b… lmata 354 assert mgr._local_version() == 7
c054b6b… lmata 355
c054b6b… lmata 356 def test_set_local_version_merges_existing_keys(self, tmp_path):
c054b6b… lmata 357 mgr, _, _ = self._make(tmp_path)
c054b6b… lmata 358 mgr._set_local_version(1)
c054b6b… lmata 359 mgr._set_local_version(2)
c054b6b… lmata 360 assert mgr._local_version() == 2
c054b6b… lmata 361
c054b6b… lmata 362 def test_local_version_handles_corrupt_json(self, tmp_path):
c054b6b… lmata 363 mgr, _, _ = self._make(tmp_path)
c054b6b… lmata 364 meta = tmp_path / ".navegador" / "cluster_meta.json"
c054b6b… lmata 365 meta.parent.mkdir(parents=True, exist_ok=True)
c054b6b… lmata 366 meta.write_text("{bad json")
c054b6b… lmata 367 # Should fall back to 0
c054b6b… lmata 368 # local_db_path is tmp_path/graph.db, so meta is tmp_path/cluster_meta.json
c054b6b… lmata 369 mgr2, _, _ = self._make(tmp_path)
c054b6b… lmata 370 assert mgr2._local_version() == 0
c054b6b… lmata 371
c054b6b… lmata 372 def test_snapshot_to_local_no_snapshot(self, tmp_path):
c054b6b… lmata 373 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 374 r.get.return_value = None # no snapshot key
c054b6b… lmata 375 # Should log warning and return without error
c054b6b… lmata 376 with patch("navegador.cluster.core.logger") as mock_log:
c054b6b… lmata 377 mgr.snapshot_to_local()
c054b6b… lmata 378 mock_log.warning.assert_called_once()
c054b6b… lmata 379
c054b6b… lmata 380 def test_snapshot_to_local_imports_data(self, tmp_path):
c054b6b… lmata 381 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 382 snapshot_data = {"nodes": [], "edges": []}
c054b6b… lmata 383 # First call returns version key, subsequent get returns snapshot
c054b6b… lmata 384 r.get.side_effect = [json.dumps(snapshot_data).encode(), b"5"]
c054b6b… lmata 385
c054b6b… lmata 386 with patch.object(mgr, "_import_to_local_graph") as mock_import:
c054b6b… lmata 387 with patch.object(mgr, "_set_local_version") as mock_set:
c054b6b… lmata 388 with patch.object(mgr, "_redis_version", return_value=5):
c054b6b… lmata 389 r.get.side_effect = None
c054b6b… lmata 390 r.get.return_value = json.dumps(snapshot_data).encode()
c054b6b… lmata 391 mgr.snapshot_to_local()
c054b6b… lmata 392 mock_import.assert_called_once_with(snapshot_data)
c054b6b… lmata 393
c054b6b… lmata 394 def test_push_to_shared(self, tmp_path):
c054b6b… lmata 395 mgr, r, pipe = self._make(tmp_path)
c054b6b… lmata 396 r.get.return_value = b"2"
c054b6b… lmata 397
c054b6b… lmata 398 with patch.object(mgr, "_export_local_graph", return_value={"nodes": [], "edges": []}):
c054b6b… lmata 399 mgr.push_to_shared()
c054b6b… lmata 400
c054b6b… lmata 401 pipe.set.assert_called()
c054b6b… lmata 402 pipe.execute.assert_called_once()
c054b6b… lmata 403
c054b6b… lmata 404 def test_sync_pulls_when_shared_newer(self, tmp_path):
c054b6b… lmata 405 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 406 with patch.object(mgr, "_local_version", return_value=1):
c054b6b… lmata 407 with patch.object(mgr, "_redis_version", return_value=5):
c054b6b… lmata 408 with patch.object(mgr, "snapshot_to_local") as mock_pull:
c054b6b… lmata 409 mgr.sync()
c054b6b… lmata 410 mock_pull.assert_called_once()
c054b6b… lmata 411
c054b6b… lmata 412 def test_sync_pushes_when_local_current(self, tmp_path):
c054b6b… lmata 413 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 414 with patch.object(mgr, "_local_version", return_value=5):
c054b6b… lmata 415 with patch.object(mgr, "_redis_version", return_value=3):
c054b6b… lmata 416 with patch.object(mgr, "push_to_shared") as mock_push:
c054b6b… lmata 417 mgr.sync()
c054b6b… lmata 418 mock_push.assert_called_once()
c054b6b… lmata 419
c054b6b… lmata 420 def test_connect_redis_raises_on_missing_dep(self):
c054b6b… lmata 421 from navegador.cluster.core import ClusterManager
c054b6b… lmata 422
c054b6b… lmata 423 with patch.dict("sys.modules", {"redis": None}):
c054b6b… lmata 424 with pytest.raises(ImportError, match="redis"):
c054b6b… lmata 425 ClusterManager._connect_redis("redis://localhost")
c054b6b… lmata 426
c054b6b… lmata 427 def test_redis_version_returns_zero_when_none(self, tmp_path):
c054b6b… lmata 428 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 429 r.get.return_value = None
c054b6b… lmata 430 assert mgr._redis_version() == 0
c054b6b… lmata 431
c054b6b… lmata 432 def test_export_local_graph_calls_store(self, tmp_path):
c054b6b… lmata 433 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 434 mock_store = MagicMock()
c054b6b… lmata 435 nodes_result = MagicMock()
c054b6b… lmata 436 nodes_result.result_set = []
c054b6b… lmata 437 edges_result = MagicMock()
c054b6b… lmata 438 edges_result.result_set = []
c054b6b… lmata 439 mock_store.query.side_effect = [nodes_result, edges_result]
c054b6b… lmata 440
c054b6b… lmata 441 with patch("navegador.graph.store.GraphStore.sqlite", return_value=mock_store):
c054b6b… lmata 442 data = mgr._export_local_graph()
c054b6b… lmata 443 assert data == {"nodes": [], "edges": []}
c054b6b… lmata 444
c054b6b… lmata 445 def test_import_to_local_graph_creates_nodes(self, tmp_path):
c054b6b… lmata 446 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 447 mock_store = MagicMock()
c054b6b… lmata 448
c054b6b… lmata 449 data = {
c054b6b… lmata 450 "nodes": [{"labels": ["Function"], "properties": {"name": "foo"}}],
c054b6b… lmata 451 "edges": [
c054b6b… lmata 452 {
c054b6b… lmata 453 "src_labels": ["Function"],
c054b6b… lmata 454 "src_props": {"name": "foo", "file_path": "f.py"},
c054b6b… lmata 455 "rel_type": "CALLS",
c054b6b… lmata 456 "dst_labels": ["Function"],
c054b6b… lmata 457 "dst_props": {"name": "bar", "file_path": "f.py"},
c054b6b… lmata 458 "rel_props": {},
c054b6b… lmata 459 }
c054b6b… lmata 460 ],
c054b6b… lmata 461 }
c054b6b… lmata 462 with patch("navegador.graph.store.GraphStore.sqlite", return_value=mock_store):
c054b6b… lmata 463 mgr._import_to_local_graph(data)
c054b6b… lmata 464 mock_store.create_node.assert_called_once()
c054b6b… lmata 465 mock_store.create_edge.assert_called_once()
c054b6b… lmata 466
c054b6b… lmata 467 def test_import_to_local_graph_skips_edge_without_src_key(self, tmp_path):
c054b6b… lmata 468 mgr, r, _ = self._make(tmp_path)
c054b6b… lmata 469 mock_store = MagicMock()
c054b6b… lmata 470
c054b6b… lmata 471 data = {
c054b6b… lmata 472 "nodes": [],
c054b6b… lmata 473 "edges": [
c054b6b… lmata 474 {
c054b6b… lmata 475 "src_labels": ["Function"],
c054b6b… lmata 476 "src_props": {}, # no name/file_path → no key
c054b6b… lmata 477 "rel_type": "CALLS",
c054b6b… lmata 478 "dst_labels": ["Function"],
c054b6b… lmata 479 "dst_props": {"name": "bar", "file_path": "f.py"},
c054b6b… lmata 480 "rel_props": {},
c054b6b… lmata 481 }
c054b6b… lmata 482 ],
c054b6b… lmata 483 }
c054b6b… lmata 484 with patch("navegador.graph.store.GraphStore.sqlite", return_value=mock_store):
c054b6b… lmata 485 mgr._import_to_local_graph(data)
c054b6b… lmata 486 mock_store.create_edge.assert_not_called()
c054b6b… lmata 487
c054b6b… lmata 488
c054b6b… lmata 489 # ===========================================================================
c054b6b… lmata 490 # navegador.monorepo (76% → target ~90%)
c054b6b… lmata 491 # ===========================================================================
c054b6b… lmata 492
c054b6b… lmata 493
c054b6b… lmata 494 class TestWorkspaceDetectorEdgeCases:
c054b6b… lmata 495 """Cover lines 89-90, 124-125, 128, 142-143, 165-166, 180-181, 193,
c054b6b… lmata 496 223, 235-236, 253-255, 270-274, 288-289."""
c054b6b… lmata 497
c054b6b… lmata 498 def test_yarn_workspaces_berry_format(self, tmp_path):
c054b6b… lmata 499 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 500
c054b6b… lmata 501 pkg_json = {
c054b6b… lmata 502 "name": "root",
c054b6b… lmata 503 "workspaces": {"packages": ["packages/*"]},
c054b6b… lmata 504 }
c054b6b… lmata 505 _write(tmp_path / "package.json", json.dumps(pkg_json))
c054b6b… lmata 506 (tmp_path / "packages" / "app").mkdir(parents=True)
c054b6b… lmata 507 _write(tmp_path / "packages" / "app" / "package.json", '{"name":"app"}')
c054b6b… lmata 508 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 509 assert config is not None
c054b6b… lmata 510 assert config.type == "yarn"
c054b6b… lmata 511
c054b6b… lmata 512 def test_yarn_package_json_parse_error(self, tmp_path):
c054b6b… lmata 513 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 514
c054b6b… lmata 515 _write(tmp_path / "package.json", "{bad json")
c054b6b… lmata 516 # No workspaces key (parse failed) → no yarn config returned
c054b6b… lmata 517 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 518 assert config is None
c054b6b… lmata 519
c054b6b… lmata 520 def test_js_workspace_packages_berry_patterns(self, tmp_path):
c054b6b… lmata 521 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 522
c054b6b… lmata 523 pkg_json = {"workspaces": {"packages": ["packages/*"]}}
c054b6b… lmata 524 _write(tmp_path / "package.json", json.dumps(pkg_json))
c054b6b… lmata 525 (tmp_path / "packages" / "a").mkdir(parents=True)
c054b6b… lmata 526 det = WorkspaceDetector()
c054b6b… lmata 527 packages = det._js_workspace_packages(tmp_path)
c054b6b… lmata 528 assert any(p.name == "a" for p in packages)
c054b6b… lmata 529
c054b6b… lmata 530 def test_js_workspace_packages_no_package_json(self, tmp_path):
c054b6b… lmata 531 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 532
c054b6b… lmata 533 # fallback: scan for package.json one level down
c054b6b… lmata 534 (tmp_path / "pkg_a").mkdir()
c054b6b… lmata 535 _write(tmp_path / "pkg_a" / "package.json", '{"name":"pkg_a"}')
c054b6b… lmata 536 det = WorkspaceDetector()
c054b6b… lmata 537 packages = det._js_workspace_packages(tmp_path)
c054b6b… lmata 538 assert any(p.name == "pkg_a" for p in packages)
c054b6b… lmata 539
c054b6b… lmata 540 def test_js_workspace_packages_parse_error(self, tmp_path):
c054b6b… lmata 541 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 542
c054b6b… lmata 543 _write(tmp_path / "package.json", "{bad json")
c054b6b… lmata 544 # Falls back to _fallback_packages
c054b6b… lmata 545 det = WorkspaceDetector()
c054b6b… lmata 546 packages = det._js_workspace_packages(tmp_path)
c054b6b… lmata 547 assert isinstance(packages, list)
c054b6b… lmata 548
c054b6b… lmata 549 def test_nx_packages_from_subdirs(self, tmp_path):
c054b6b… lmata 550 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 551
c054b6b… lmata 552 _write(tmp_path / "nx.json", '{}')
c054b6b… lmata 553 (tmp_path / "apps" / "app1").mkdir(parents=True)
c054b6b… lmata 554 (tmp_path / "libs" / "lib1").mkdir(parents=True)
c054b6b… lmata 555 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 556 assert config is not None
c054b6b… lmata 557 assert config.type == "nx"
c054b6b… lmata 558 pkg_names = [p.name for p in config.packages]
c054b6b… lmata 559 assert "app1" in pkg_names
c054b6b… lmata 560 assert "lib1" in pkg_names
c054b6b… lmata 561
c054b6b… lmata 562 def test_nx_packages_fallback_to_js_workspaces(self, tmp_path):
c054b6b… lmata 563 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 564
c054b6b… lmata 565 # nx.json exists but no apps/libs/packages dirs
c054b6b… lmata 566 _write(tmp_path / "nx.json", '{}')
c054b6b… lmata 567 # fallback triggers _js_workspace_packages → _fallback_packages
c054b6b… lmata 568 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 569 assert config is not None
c054b6b… lmata 570 assert config.type == "nx"
c054b6b… lmata 571
c054b6b… lmata 572 def test_pnpm_workspace_parse(self, tmp_path):
c054b6b… lmata 573 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 574
c054b6b… lmata 575 _write(
c054b6b… lmata 576 tmp_path / "pnpm-workspace.yaml",
c054b6b… lmata 577 "packages:\n - 'packages/*'\n - 'apps/*'\n",
c054b6b… lmata 578 )
c054b6b… lmata 579 (tmp_path / "packages" / "core").mkdir(parents=True)
c054b6b… lmata 580 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 581 assert config is not None
c054b6b… lmata 582 assert config.type == "pnpm"
c054b6b… lmata 583
c054b6b… lmata 584 def test_pnpm_workspace_read_error(self, tmp_path):
c054b6b… lmata 585 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 586
c054b6b… lmata 587 # pnpm-workspace.yaml exists but cannot be read → IOError path
c054b6b… lmata 588 yaml_path = tmp_path / "pnpm-workspace.yaml"
c054b6b… lmata 589 yaml_path.touch()
c054b6b… lmata 590 det = WorkspaceDetector()
c054b6b… lmata 591 with patch.object(Path, "read_text", side_effect=OSError("perm")):
c054b6b… lmata 592 packages = det._pnpm_packages(tmp_path)
c054b6b… lmata 593 assert isinstance(packages, list)
c054b6b… lmata 594
c054b6b… lmata 595 def test_pnpm_no_patterns_fallback(self, tmp_path):
c054b6b… lmata 596 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 597
c054b6b… lmata 598 _write(tmp_path / "pnpm-workspace.yaml", "# empty\n")
c054b6b… lmata 599 (tmp_path / "sub").mkdir()
c054b6b… lmata 600 _write(tmp_path / "sub" / "package.json", '{"name":"sub"}')
c054b6b… lmata 601 det = WorkspaceDetector()
c054b6b… lmata 602 packages = det._pnpm_packages(tmp_path)
c054b6b… lmata 603 assert any(p.name == "sub" for p in packages)
c054b6b… lmata 604
c054b6b… lmata 605 def test_cargo_workspace_parse(self, tmp_path):
c054b6b… lmata 606 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 607
c054b6b… lmata 608 cargo_toml = """
c054b6b… lmata 609 [workspace]
c054b6b… lmata 610 members = [
c054b6b… lmata 611 "crates/core",
c054b6b… lmata 612 "crates/cli",
c054b6b… lmata 613 ]
c054b6b… lmata 614 """
c054b6b… lmata 615 _write(tmp_path / "Cargo.toml", cargo_toml)
c054b6b… lmata 616 (tmp_path / "crates" / "core").mkdir(parents=True)
c054b6b… lmata 617 (tmp_path / "crates" / "cli").mkdir(parents=True)
c054b6b… lmata 618 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 619 assert config is not None
c054b6b… lmata 620 assert config.type == "cargo"
c054b6b… lmata 621 assert len(config.packages) == 2
c054b6b… lmata 622
c054b6b… lmata 623 def test_cargo_workspace_not_workspace(self, tmp_path):
c054b6b… lmata 624 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 625
c054b6b… lmata 626 _write(tmp_path / "Cargo.toml", "[package]\nname = \"myapp\"\n")
c054b6b… lmata 627 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 628 assert config is None
c054b6b… lmata 629
c054b6b… lmata 630 def test_cargo_read_error(self, tmp_path):
c054b6b… lmata 631 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 632
c054b6b… lmata 633 cargo_toml = tmp_path / "Cargo.toml"
c054b6b… lmata 634 cargo_toml.touch()
c054b6b… lmata 635 det = WorkspaceDetector()
c054b6b… lmata 636 with patch.object(Path, "read_text", side_effect=OSError("perm")):
c054b6b… lmata 637 result = det._cargo_packages(tmp_path, cargo_toml)
c054b6b… lmata 638 assert result is None
c054b6b… lmata 639
c054b6b… lmata 640 def test_cargo_wildcard_members(self, tmp_path):
c054b6b… lmata 641 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 642
c054b6b… lmata 643 cargo_toml = "[workspace]\nmembers = [\"crates/*\"]\n"
c054b6b… lmata 644 _write(tmp_path / "Cargo.toml", cargo_toml)
c054b6b… lmata 645 (tmp_path / "crates" / "a").mkdir(parents=True)
c054b6b… lmata 646 (tmp_path / "crates" / "b").mkdir(parents=True)
c054b6b… lmata 647 det = WorkspaceDetector()
c054b6b… lmata 648 pkgs = det._cargo_packages(tmp_path, tmp_path / "Cargo.toml")
c054b6b… lmata 649 assert pkgs is not None
c054b6b… lmata 650 assert len(pkgs) == 2
c054b6b… lmata 651
c054b6b… lmata 652 def test_go_workspace_parse(self, tmp_path):
c054b6b… lmata 653 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 654
c054b6b… lmata 655 (tmp_path / "cmd").mkdir()
c054b6b… lmata 656 (tmp_path / "pkg").mkdir()
c054b6b… lmata 657 _write(tmp_path / "go.work", "go 1.21\nuse ./cmd\nuse ./pkg\n")
c054b6b… lmata 658 config = WorkspaceDetector().detect(tmp_path)
c054b6b… lmata 659 assert config is not None
c054b6b… lmata 660 assert config.type == "go"
c054b6b… lmata 661
c054b6b… lmata 662 def test_go_workspace_read_error(self, tmp_path):
c054b6b… lmata 663 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 664
c054b6b… lmata 665 go_work = tmp_path / "go.work"
c054b6b… lmata 666 go_work.touch()
c054b6b… lmata 667 det = WorkspaceDetector()
c054b6b… lmata 668 with patch.object(Path, "read_text", side_effect=OSError("perm")):
c054b6b… lmata 669 packages = det._go_packages(tmp_path)
c054b6b… lmata 670 assert isinstance(packages, list)
c054b6b… lmata 671
c054b6b… lmata 672 def test_glob_packages_negation_skipped(self, tmp_path):
c054b6b… lmata 673 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 674
c054b6b… lmata 675 (tmp_path / "packages" / "a").mkdir(parents=True)
c054b6b… lmata 676 det = WorkspaceDetector()
c054b6b… lmata 677 pkgs = det._glob_packages(tmp_path, ["!packages/*"])
c054b6b… lmata 678 assert pkgs == []
c054b6b… lmata 679
c054b6b… lmata 680 def test_glob_packages_literal_path(self, tmp_path):
c054b6b… lmata 681 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 682
c054b6b… lmata 683 (tmp_path / "myapp").mkdir()
c054b6b… lmata 684 det = WorkspaceDetector()
c054b6b… lmata 685 pkgs = det._glob_packages(tmp_path, ["myapp"])
c054b6b… lmata 686 assert any(p.name == "myapp" for p in pkgs)
c054b6b… lmata 687
c054b6b… lmata 688 def test_fallback_packages_skips_dotdirs(self, tmp_path):
c054b6b… lmata 689 from navegador.monorepo import WorkspaceDetector
c054b6b… lmata 690
c054b6b… lmata 691 (tmp_path / ".git").mkdir()
c054b6b… lmata 692 _write(tmp_path / ".git" / "package.json", '{}')
c054b6b… lmata 693 (tmp_path / "real").mkdir()
c054b6b… lmata 694 _write(tmp_path / "real" / "package.json", '{}')
c054b6b… lmata 695 det = WorkspaceDetector()
c054b6b… lmata 696 pkgs = det._fallback_packages(tmp_path)
c054b6b… lmata 697 names = [p.name for p in pkgs]
c054b6b… lmata 698 assert ".git" not in names
c054b6b… lmata 699 assert "real" in names
c054b6b… lmata 700
c054b6b… lmata 701
c054b6b… lmata 702 class TestMonorepoIngesterEdgeCases:
c054b6b… lmata 703 """Cover lines 373, 404-405, 451-452, 466, 471, 474-475, 485-503, 509-531."""
c054b6b… lmata 704
c054b6b… lmata 705 def test_ingest_fallback_when_no_workspace(self, tmp_path):
c054b6b… lmata 706 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 707
c054b6b… lmata 708 store = _make_store()
c054b6b… lmata 709 ingester = MonorepoIngester(store)
c054b6b… lmata 710 with patch("navegador.monorepo.RepoIngester") as MockRI:
c054b6b… lmata 711 instance = MockRI.return_value
c054b6b… lmata 712 instance.ingest.return_value = {
c054b6b… lmata 713 "files": 1, "functions": 2, "classes": 0, "edges": 0, "skipped": 0
c054b6b… lmata 714 }
c054b6b… lmata 715 stats = ingester.ingest(str(tmp_path))
c054b6b… lmata 716 assert stats["packages"] == 0
c054b6b… lmata 717 assert stats["workspace_type"] == "none"
c054b6b… lmata 718
c054b6b… lmata 719 def test_ingest_raises_on_missing_path(self, tmp_path):
c054b6b… lmata 720 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 721
c054b6b… lmata 722 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 723 with pytest.raises(FileNotFoundError):
c054b6b… lmata 724 ingester.ingest(str(tmp_path / "does_not_exist"))
c054b6b… lmata 725
c054b6b… lmata 726 def test_ingest_package_exception_logged(self, tmp_path):
c054b6b… lmata 727 from navegador.monorepo import MonorepoIngester, WorkspaceConfig
c054b6b… lmata 728
c054b6b… lmata 729 store = _make_store()
c054b6b… lmata 730 pkg_dir = tmp_path / "pkg"
c054b6b… lmata 731 pkg_dir.mkdir()
c054b6b… lmata 732 config = WorkspaceConfig(type="yarn", root=tmp_path, packages=[pkg_dir])
c054b6b… lmata 733
c054b6b… lmata 734 with patch("navegador.monorepo.WorkspaceDetector") as MockDet:
c054b6b… lmata 735 MockDet.return_value.detect.return_value = config
c054b6b… lmata 736 with patch("navegador.monorepo.RepoIngester") as MockRI:
c054b6b… lmata 737 MockRI.return_value.ingest.side_effect = RuntimeError("parse fail")
c054b6b… lmata 738 stats = MonorepoIngester(store).ingest(str(tmp_path))
c054b6b… lmata 739 assert stats["packages"] == 0
c054b6b… lmata 740
c054b6b… lmata 741 def test_js_deps(self, tmp_path):
c054b6b… lmata 742 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 743
c054b6b… lmata 744 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 745 pkg_json = {
c054b6b… lmata 746 "dependencies": {"react": "^18"},
c054b6b… lmata 747 "devDependencies": {"jest": "^29"},
c054b6b… lmata 748 "peerDependencies": {"typescript": ">=5"},
c054b6b… lmata 749 }
c054b6b… lmata 750 _write(tmp_path / "package.json", json.dumps(pkg_json))
c054b6b… lmata 751 deps = ingester._js_deps(tmp_path)
c054b6b… lmata 752 assert "react" in deps
c054b6b… lmata 753 assert "jest" in deps
c054b6b… lmata 754 assert "typescript" in deps
c054b6b… lmata 755
c054b6b… lmata 756 def test_js_deps_no_file(self, tmp_path):
c054b6b… lmata 757 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 758
c054b6b… lmata 759 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 760 assert ingester._js_deps(tmp_path) == []
c054b6b… lmata 761
c054b6b… lmata 762 def test_js_deps_parse_error(self, tmp_path):
c054b6b… lmata 763 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 764
c054b6b… lmata 765 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 766 _write(tmp_path / "package.json", "{bad json")
c054b6b… lmata 767 assert ingester._js_deps(tmp_path) == []
c054b6b… lmata 768
c054b6b… lmata 769 def test_cargo_deps(self, tmp_path):
c054b6b… lmata 770 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 771
c054b6b… lmata 772 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 773 cargo = "[dependencies]\nserde = \"1.0\"\ntokio = { version = \"1\" }\n[dev-dependencies]\ntempfile = \"3\"\n"
c054b6b… lmata 774 _write(tmp_path / "Cargo.toml", cargo)
c054b6b… lmata 775 deps = ingester._cargo_deps(tmp_path)
c054b6b… lmata 776 assert "serde" in deps
c054b6b… lmata 777 assert "tokio" in deps
c054b6b… lmata 778 assert "tempfile" in deps
c054b6b… lmata 779
c054b6b… lmata 780 def test_cargo_deps_no_file(self, tmp_path):
c054b6b… lmata 781 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 782
c054b6b… lmata 783 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 784 assert ingester._cargo_deps(tmp_path) == []
c054b6b… lmata 785
c054b6b… lmata 786 def test_cargo_deps_read_error(self, tmp_path):
c054b6b… lmata 787 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 788
c054b6b… lmata 789 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 790 cargo = tmp_path / "Cargo.toml"
c054b6b… lmata 791 cargo.touch()
c054b6b… lmata 792 with patch.object(Path, "read_text", side_effect=OSError("perm")):
c054b6b… lmata 793 result = ingester._cargo_deps(tmp_path)
c054b6b… lmata 794 assert result == []
c054b6b… lmata 795
c054b6b… lmata 796 def test_go_deps(self, tmp_path):
c054b6b… lmata 797 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 798
c054b6b… lmata 799 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 800 go_mod = "module example.com/myapp\ngo 1.21\n\nrequire (\n github.com/pkg/errors v0.9.1\n golang.org/x/net v0.17.0\n)\n\nrequire github.com/single/dep v1.0.0\n"
c054b6b… lmata 801 _write(tmp_path / "go.mod", go_mod)
c054b6b… lmata 802 deps = ingester._go_deps(tmp_path)
c054b6b… lmata 803 assert "github.com/pkg/errors" in deps
c054b6b… lmata 804 assert "golang.org/x/net" in deps
c054b6b… lmata 805 assert "github.com/single/dep" in deps
c054b6b… lmata 806
c054b6b… lmata 807 def test_go_deps_no_file(self, tmp_path):
c054b6b… lmata 808 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 809
c054b6b… lmata 810 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 811 assert ingester._go_deps(tmp_path) == []
c054b6b… lmata 812
c054b6b… lmata 813 def test_go_deps_read_error(self, tmp_path):
c054b6b… lmata 814 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 815
c054b6b… lmata 816 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 817 go_mod = tmp_path / "go.mod"
c054b6b… lmata 818 go_mod.touch()
c054b6b… lmata 819 with patch.object(Path, "read_text", side_effect=OSError("perm")):
c054b6b… lmata 820 result = ingester._go_deps(tmp_path)
c054b6b… lmata 821 assert result == []
c054b6b… lmata 822
c054b6b… lmata 823 def test_read_package_deps_unknown_type(self, tmp_path):
c054b6b… lmata 824 from navegador.monorepo import MonorepoIngester
c054b6b… lmata 825
c054b6b… lmata 826 ingester = MonorepoIngester(_make_store())
c054b6b… lmata 827 assert ingester._read_package_deps("unknown", tmp_path) == []
c054b6b… lmata 828
c054b6b… lmata 829 def test_dependency_edges_scoped_package(self, tmp_path):
c054b6b… lmata 830 from navegador.monorepo import MonorepoIngester, WorkspaceConfig
c054b6b… lmata 831
c054b6b… lmata 832 store = _make_store()
c054b6b… lmata 833 ingester = MonorepoIngester(store)
c054b6b… lmata 834
c054b6b… lmata 835 pkg_a = tmp_path / "pkg_a"
c054b6b… lmata 836 pkg_b = tmp_path / "pkg_b"
c054b6b… lmata 837 pkg_a.mkdir()
c054b6b… lmata 838 pkg_b.mkdir()
c054b6b… lmata 839
c054b6b… lmata 840 _write(pkg_a / "package.json", json.dumps({
c054b6b… lmata 841 "dependencies": {"@scope/pkg_b": "^1.0"}
c054b6b… lmata 842 }))
c054b6b… lmata 843
c054b6b… lmata 844 config = WorkspaceConfig(type="yarn", root=tmp_path, packages=[pkg_a, pkg_b])
c054b6b… lmata 845 packages = [("pkg_a", pkg_a), ("pkg_b", pkg_b)]
c054b6b… lmata 846
c054b6b… lmata 847 ingester._create_dependency_edges(config, packages)
c054b6b… lmata 848 # store.create_edge should have been called at least once for the dependency
c054b6b… lmata 849 # (pkg_b matches the bare name)
c054b6b… lmata 850 store.create_edge.assert_called()
c054b6b… lmata 851
c054b6b… lmata 852 def test_dependency_edges_exception_logged(self, tmp_path):
c054b6b… lmata 853 from navegador.monorepo import MonorepoIngester, WorkspaceConfig
c054b6b… lmata 854
c054b6b… lmata 855 store = _make_store()
c054b6b… lmata 856 store.create_edge.side_effect = Exception("DB error")
c054b6b… lmata 857 ingester = MonorepoIngester(store)
c054b6b… lmata 858
c054b6b… lmata 859 pkg_a = tmp_path / "pkg_a"
c054b6b… lmata 860 pkg_b = tmp_path / "pkg_b"
c054b6b… lmata 861 pkg_a.mkdir()
c054b6b… lmata 862 pkg_b.mkdir()
c054b6b… lmata 863 _write(pkg_a / "package.json", json.dumps({"dependencies": {"pkg_b": "^1"}}))
c054b6b… lmata 864
c054b6b… lmata 865 config = WorkspaceConfig(type="yarn", root=tmp_path, packages=[pkg_a, pkg_b])
c054b6b… lmata 866 packages = [("pkg_a", pkg_a), ("pkg_b", pkg_b)]
c054b6b… lmata 867 # Should not raise
c054b6b… lmata 868 count = ingester._create_dependency_edges(config, packages)
c054b6b… lmata 869 assert count == 0
c054b6b… lmata 870
c054b6b… lmata 871
c054b6b… lmata 872 # ===========================================================================
c054b6b… lmata 873 # navegador.pm (79% → target ~90%)
c054b6b… lmata 874 # ===========================================================================
c054b6b… lmata 875
c054b6b… lmata 876
c054b6b… lmata 877 class TestTicketIngester:
c054b6b… lmata 878 """Cover lines 243-245, 261-287."""
c054b6b… lmata 879
c054b6b… lmata 880 def _make(self):
c054b6b… lmata 881 from navegador.pm import TicketIngester
c054b6b… lmata 882
c054b6b… lmata 883 return TicketIngester(_make_store())
c054b6b… lmata 884
c054b6b… lmata 885 def test_ingest_linear_raises(self):
c054b6b… lmata 886 ing = self._make()
c054b6b… lmata 887 with pytest.raises(NotImplementedError, match="Linear"):
c054b6b… lmata 888 ing.ingest_linear(api_key="lin_xxx")
c054b6b… lmata 889
c054b6b… lmata 890 def test_ingest_jira_raises(self):
c054b6b… lmata 891 ing = self._make()
c054b6b… lmata 892 with pytest.raises(NotImplementedError, match="Jira"):
c054b6b… lmata 893 ing.ingest_jira(url="https://co.atlassian.net", token="tok")
c054b6b… lmata 894
c054b6b… lmata 895 def test_github_severity_critical(self):
c054b6b… lmata 896 from navegador.pm import TicketIngester
c054b6b… lmata 897
c054b6b… lmata 898 assert TicketIngester._github_severity(["critical"]) == "critical"
c054b6b… lmata 899 assert TicketIngester._github_severity(["blocker"]) == "critical"
c054b6b… lmata 900 assert TicketIngester._github_severity(["p0", "other"]) == "critical"
c054b6b… lmata 901
c054b6b… lmata 902 def test_github_severity_warning(self):
c054b6b… lmata 903 from navegador.pm import TicketIngester
c054b6b… lmata 904
c054b6b… lmata 905 assert TicketIngester._github_severity(["bug"]) == "warning"
c054b6b… lmata 906 assert TicketIngester._github_severity(["high"]) == "warning"
c054b6b… lmata 907 assert TicketIngester._github_severity(["important"]) == "warning"
c054b6b… lmata 908
c054b6b… lmata 909 def test_github_severity_info(self):
c054b6b… lmata 910 from navegador.pm import TicketIngester
c054b6b… lmata 911
c054b6b… lmata 912 assert TicketIngester._github_severity([]) == "info"
c054b6b… lmata 913 assert TicketIngester._github_severity(["enhancement"]) == "info"
c054b6b… lmata 914
c054b6b… lmata 915 def test_link_to_code_returns_zero_on_empty_graph(self):
c054b6b… lmata 916 from navegador.pm import TicketIngester
c054b6b… lmata 917
c054b6b… lmata 918 store = _make_store()
c054b6b… lmata 919 store.query.return_value = MagicMock(result_set=[])
c054b6b… lmata 920 ing = TicketIngester(store)
c054b6b… lmata 921 result = ing._link_to_code("myrepo")
c054b6b… lmata 922 assert result == 0
c054b6b… lmata 923
c054b6b… lmata 924 def test_link_to_code_returns_zero_on_query_failure(self):
c054b6b… lmata 925 from navegador.pm import TicketIngester
c054b6b… lmata 926
c054b6b… lmata 927 store = _make_store()
c054b6b… lmata 928 store.query.side_effect = Exception("DB down")
c054b6b… lmata 929 ing = TicketIngester(store)
c054b6b… lmata 930 result = ing._link_to_code("myrepo")
c054b6b… lmata 931 assert result == 0
c054b6b… lmata 932
c054b6b… lmata 933 def test_link_to_code_matches_tokens(self):
c054b6b… lmata 934 from navegador.pm import TicketIngester
c054b6b… lmata 935
c054b6b… lmata 936 store = _make_store()
c054b6b… lmata 937 # First call: tickets
c054b6b… lmata 938 ticket_result = MagicMock()
c054b6b… lmata 939 ticket_result.result_set = [("#1: authenticate user", "fix auth flow")]
c054b6b… lmata 940 # Second call: code nodes
c054b6b… lmata 941 code_result = MagicMock()
c054b6b… lmata 942 code_result.result_set = [("Function", "authenticate"), ("Function", "unrelated")]
c054b6b… lmata 943 store.query.side_effect = [ticket_result, code_result, None]
c054b6b… lmata 944
c054b6b… lmata 945 ing = TicketIngester(store)
c054b6b… lmata 946 result = ing._link_to_code("myrepo")
c054b6b… lmata 947 assert result >= 1
c054b6b… lmata 948
c054b6b… lmata 949 def test_link_to_code_skips_short_tokens(self):
c054b6b… lmata 950 from navegador.pm import TicketIngester
c054b6b… lmata 951
c054b6b… lmata 952 store = _make_store()
c054b6b… lmata 953 ticket_result = MagicMock()
c054b6b… lmata 954 # Only short words (< 4 chars)
c054b6b… lmata 955 ticket_result.result_set = [("#1: fix", "x")]
c054b6b… lmata 956 code_result = MagicMock()
c054b6b… lmata 957 code_result.result_set = [("Function", "fix")]
c054b6b… lmata 958 store.query.side_effect = [ticket_result, code_result]
c054b6b… lmata 959
c054b6b… lmata 960 ing = TicketIngester(store)
c054b6b… lmata 961 # "fix" is exactly 3 chars → skipped as a token
c054b6b… lmata 962 result = ing._link_to_code("myrepo")
c054b6b… lmata 963 assert result == 0
c054b6b… lmata 964
c054b6b… lmata 965 def test_ingest_github_issues_http_error(self):
c054b6b… lmata 966 from navegador.pm import TicketIngester
c054b6b… lmata 967
c054b6b… lmata 968 store = _make_store()
c054b6b… lmata 969 ing = TicketIngester(store)
c054b6b… lmata 970 with patch("urllib.request.urlopen", side_effect=Exception("network err")):
c054b6b… lmata 971 with pytest.raises(RuntimeError, match="Failed to fetch"):
c054b6b… lmata 972 ing.ingest_github_issues("owner/repo", token="tok")
c054b6b… lmata 973
c054b6b… lmata 974 def test_ingest_github_issues_success(self):
c054b6b… lmata 975 from navegador.pm import TicketIngester
c054b6b… lmata 976
c054b6b… lmata 977 store = _make_store()
c054b6b… lmata 978 ing = TicketIngester(store)
c054b6b… lmata 979 issues = [
c054b6b… lmata 980 {"number": 1, "title": "Fix auth", "body": "desc", "html_url": "http://x",
c054b6b… lmata 981 "labels": [{"name": "bug"}], "assignees": [{"login": "alice"}]},
c054b6b… lmata 982 ]
c054b6b… lmata 983 mock_resp = MagicMock()
c054b6b… lmata 984 mock_resp.__enter__ = MagicMock(return_value=mock_resp)
c054b6b… lmata 985 mock_resp.__exit__ = MagicMock(return_value=False)
c054b6b… lmata 986 mock_resp.read.return_value = json.dumps(issues).encode()
c054b6b… lmata 987
c054b6b… lmata 988 with patch("urllib.request.urlopen", return_value=mock_resp):
c054b6b… lmata 989 stats = ing.ingest_github_issues("owner/repo")
c054b6b… lmata 990 assert stats["tickets"] == 1
c054b6b… lmata 991
c054b6b… lmata 992
c054b6b… lmata 993 # ===========================================================================
c054b6b… lmata 994 # navegador.completions — install path (lines 66-70)
c054b6b… lmata 995 # ===========================================================================
c054b6b… lmata 996
c054b6b… lmata 997
c054b6b… lmata 998 class TestGetInstallInstruction:
c054b6b… lmata 999 def test_bash(self):
c054b6b… lmata 1000 from navegador.completions import get_install_instruction
c054b6b… lmata 1001
c054b6b… lmata 1002 instruction = get_install_instruction("bash")
c054b6b… lmata 1003 assert "~/.bashrc" in instruction
c054b6b… lmata 1004 assert "bash_source" in instruction
c054b6b… lmata 1005
c054b6b… lmata 1006 def test_zsh(self):
c054b6b… lmata 1007 from navegador.completions import get_install_instruction
c054b6b… lmata 1008
c054b6b… lmata 1009 instruction = get_install_instruction("zsh")
c054b6b… lmata 1010 assert "~/.zshrc" in instruction
c054b6b… lmata 1011 assert "zsh_source" in instruction
c054b6b… lmata 1012
c054b6b… lmata 1013 def test_fish(self):
c054b6b… lmata 1014 from navegador.completions import get_install_instruction
c054b6b… lmata 1015
c054b6b… lmata 1016 instruction = get_install_instruction("fish")
c054b6b… lmata 1017 assert "config.fish" in instruction
c054b6b… lmata 1018 assert "fish_source" in instruction
c054b6b… lmata 1019
c054b6b… lmata 1020 def test_invalid_raises(self):
c054b6b… lmata 1021 from navegador.completions import get_install_instruction
c054b6b… lmata 1022
c054b6b… lmata 1023 with pytest.raises(ValueError, match="Unsupported"):
c054b6b… lmata 1024 get_install_instruction("pwsh")
c054b6b… lmata 1025
c054b6b… lmata 1026
c054b6b… lmata 1027 # ===========================================================================
c054b6b… lmata 1028 # navegador.cli.commands — watch callback (lines ~179-185)
c054b6b… lmata 1029 # ===========================================================================
c054b6b… lmata 1030
c054b6b… lmata 1031
c054b6b… lmata 1032 class TestIngestWatchCallback:
c054b6b… lmata 1033 """Exercise the _on_cycle callback inside the watch branch of ingest."""
c054b6b… lmata 1034
c054b6b… lmata 1035 def test_watch_callback_with_changed_files(self, tmp_path):
c054b6b… lmata 1036 from click.testing import CliRunner
c054b6b… lmata 1037
c054b6b… lmata 1038 from navegador.cli.commands import main
c054b6b… lmata 1039
c054b6b… lmata 1040 runner = CliRunner()
c054b6b… lmata 1041 repo = str(tmp_path)
c054b6b… lmata 1042
c054b6b… lmata 1043 cycle_calls = []
c054b6b… lmata 1044
c054b6b… lmata 1045 def fake_watch(path, interval=2.0, callback=None):
c054b6b… lmata 1046 if callback:
c054b6b… lmata 1047 # Simulate a cycle with changed files
c054b6b… lmata 1048 result = callback({"files": 3, "skipped": 10})
c054b6b… lmata 1049 cycle_calls.append(result)
c054b6b… lmata 1050 # Second call: simulate KeyboardInterrupt to exit
c054b6b… lmata 1051 raise KeyboardInterrupt
c054b6b… lmata 1052
c054b6b… lmata 1053 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1054 mock_gs.return_value = _make_store()
c054b6b… lmata 1055 with patch("navegador.ingestion.RepoIngester") as MockRI:
c054b6b… lmata 1056 MockRI.return_value.watch.side_effect = fake_watch
c054b6b… lmata 1057 result = runner.invoke(main, ["ingest", repo, "--watch", "--interval", "1"])
c054b6b… lmata 1058
c054b6b… lmata 1059 assert result.exit_code == 0
c054b6b… lmata 1060 assert cycle_calls == [True]
c054b6b… lmata 1061
c054b6b… lmata 1062 def test_watch_callback_no_changed_files(self, tmp_path):
c054b6b… lmata 1063 """Callback with 0 changed files should still return True."""
c054b6b… lmata 1064 from click.testing import CliRunner
c054b6b… lmata 1065
c054b6b… lmata 1066 from navegador.cli.commands import main
c054b6b… lmata 1067
c054b6b… lmata 1068 runner = CliRunner()
c054b6b… lmata 1069
c054b6b… lmata 1070 def fake_watch(path, interval=2.0, callback=None):
c054b6b… lmata 1071 if callback:
c054b6b… lmata 1072 result = callback({"files": 0, "skipped": 5})
c054b6b… lmata 1073 assert result is True
c054b6b… lmata 1074 raise KeyboardInterrupt
c054b6b… lmata 1075
c054b6b… lmata 1076 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1077 mock_gs.return_value = _make_store()
c054b6b… lmata 1078 with patch("navegador.ingestion.RepoIngester") as MockRI:
c054b6b… lmata 1079 MockRI.return_value.watch.side_effect = fake_watch
c054b6b… lmata 1080 result = runner.invoke(main, ["ingest", str(tmp_path), "--watch"])
c054b6b… lmata 1081
c054b6b… lmata 1082 assert result.exit_code == 0
c054b6b… lmata 1083
c054b6b… lmata 1084
c054b6b… lmata 1085 # ===========================================================================
c054b6b… lmata 1086 # navegador.cli.commands — additional uncovered CLI branches
c054b6b… lmata 1087 # ===========================================================================
c054b6b… lmata 1088
c054b6b… lmata 1089
c054b6b… lmata 1090 class TestCLIBranchesDeadcode:
c054b6b… lmata 1091 """Cover lines 1395-1407 (unreachable_classes and orphan_files branches)."""
c054b6b… lmata 1092
c054b6b… lmata 1093 def test_deadcode_shows_unreachable_classes(self, tmp_path):
c054b6b… lmata 1094 from click.testing import CliRunner
c054b6b… lmata 1095
c054b6b… lmata 1096 from navegador.cli.commands import main
c054b6b… lmata 1097 from navegador.analysis.deadcode import DeadCodeReport
c054b6b… lmata 1098
c054b6b… lmata 1099 runner = CliRunner()
c054b6b… lmata 1100 report = DeadCodeReport(
c054b6b… lmata 1101 unreachable_functions=[],
c054b6b… lmata 1102 unreachable_classes=[
c054b6b… lmata 1103 {"name": "OldClass", "file_path": "old.py", "line_start": 1, "type": "Class"}
c054b6b… lmata 1104 ],
c054b6b… lmata 1105 orphan_files=["orphan.py"],
c054b6b… lmata 1106 )
c054b6b… lmata 1107 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1108 mock_gs.return_value = _make_store()
c054b6b… lmata 1109 with patch("navegador.analysis.deadcode.DeadCodeDetector") as MockDC:
c054b6b… lmata 1110 MockDC.return_value.detect.return_value = report
c054b6b… lmata 1111 result = runner.invoke(main, ["deadcode"])
c054b6b… lmata 1112 assert result.exit_code == 0
c054b6b… lmata 1113 assert "OldClass" in result.output
c054b6b… lmata 1114 assert "orphan.py" in result.output
c054b6b… lmata 1115
c054b6b… lmata 1116 def test_deadcode_no_dead_code_message(self, tmp_path):
c054b6b… lmata 1117 from click.testing import CliRunner
c054b6b… lmata 1118
c054b6b… lmata 1119 from navegador.cli.commands import main
c054b6b… lmata 1120 from navegador.analysis.deadcode import DeadCodeReport
c054b6b… lmata 1121
c054b6b… lmata 1122 runner = CliRunner()
c054b6b… lmata 1123 report = DeadCodeReport(
c054b6b… lmata 1124 unreachable_functions=[], unreachable_classes=[], orphan_files=[]
c054b6b… lmata 1125 )
c054b6b… lmata 1126 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1127 mock_gs.return_value = _make_store()
c054b6b… lmata 1128 with patch("navegador.analysis.deadcode.DeadCodeDetector") as MockDC:
c054b6b… lmata 1129 MockDC.return_value.detect.return_value = report
c054b6b… lmata 1130 result = runner.invoke(main, ["deadcode"])
c054b6b… lmata 1131 assert result.exit_code == 0
c054b6b… lmata 1132 assert "No dead code" in result.output
c054b6b… lmata 1133
c054b6b… lmata 1134
c054b6b… lmata 1135 class TestCLIBranchesTestmap:
c054b6b… lmata 1136 """Cover lines 1439-1452 (testmap table and unmatched branches)."""
c054b6b… lmata 1137
c054b6b… lmata 1138 def test_testmap_shows_table_and_unmatched(self):
c054b6b… lmata 1139 from click.testing import CliRunner
c054b6b… lmata 1140
c054b6b… lmata 1141 from navegador.cli.commands import main
c054b6b… lmata 1142 from navegador.analysis.testmap import TestMapResult, TestLink
c054b6b… lmata 1143
c054b6b… lmata 1144 runner = CliRunner()
c054b6b… lmata 1145 link = TestLink(
c054b6b… lmata 1146 test_name="test_foo",
c054b6b… lmata 1147 test_file="test_foo.py",
c054b6b… lmata 1148 prod_name="foo",
c054b6b… lmata 1149 prod_file="foo.py",
c054b6b… lmata 1150 prod_type="Function",
c054b6b… lmata 1151 source="name",
c054b6b… lmata 1152 )
c054b6b… lmata 1153 result_obj = TestMapResult(
c054b6b… lmata 1154 links=[link],
c054b6b… lmata 1155 unmatched_tests=[{"name": "test_orphan", "file_path": "test_x.py"}],
c054b6b… lmata 1156 edges_created=1,
c054b6b… lmata 1157 )
c054b6b… lmata 1158 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1159 mock_gs.return_value = _make_store()
c054b6b… lmata 1160 with patch("navegador.analysis.testmap.TestMapper") as MockTM:
c054b6b… lmata 1161 MockTM.return_value.map_tests.return_value = result_obj
c054b6b… lmata 1162 result = runner.invoke(main, ["testmap"])
c054b6b… lmata 1163 assert result.exit_code == 0
c054b6b… lmata 1164 assert "test_foo" in result.output
c054b6b… lmata 1165 assert "test_orphan" in result.output
c054b6b… lmata 1166
c054b6b… lmata 1167
c054b6b… lmata 1168 class TestCLIBranchesRename:
c054b6b… lmata 1169 """Cover lines 1640-1650 (rename non-JSON output)."""
c054b6b… lmata 1170
c054b6b… lmata 1171 def test_rename_preview_output(self):
c054b6b… lmata 1172 from click.testing import CliRunner
c054b6b… lmata 1173
c054b6b… lmata 1174 from navegador.cli.commands import main
c054b6b… lmata 1175 from navegador.refactor import RenameResult
c054b6b… lmata 1176
c054b6b… lmata 1177 runner = CliRunner()
c054b6b… lmata 1178 rename_result = RenameResult(
c054b6b… lmata 1179 old_name="old_func",
c054b6b… lmata 1180 new_name="new_func",
c054b6b… lmata 1181 affected_nodes=[{"name": "old_func", "file_path": "f.py", "type": "Function", "line_start": 1}],
c054b6b… lmata 1182 affected_files=["f.py", "g.py"],
c054b6b… lmata 1183 edges_updated=3,
c054b6b… lmata 1184 )
c054b6b… lmata 1185 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1186 mock_gs.return_value = _make_store()
c054b6b… lmata 1187 with patch("navegador.refactor.SymbolRenamer") as MockSR:
c054b6b… lmata 1188 MockSR.return_value.preview_rename.return_value = rename_result
c054b6b… lmata 1189 result = runner.invoke(main, ["rename", "old_func", "new_func", "--preview"])
c054b6b… lmata 1190 assert result.exit_code == 0
c054b6b… lmata 1191 assert "old_func" in result.output
c054b6b… lmata 1192 assert "f.py" in result.output
c054b6b… lmata 1193
c054b6b… lmata 1194 def test_rename_apply_output(self):
c054b6b… lmata 1195 from click.testing import CliRunner
c054b6b… lmata 1196
c054b6b… lmata 1197 from navegador.cli.commands import main
c054b6b… lmata 1198 from navegador.refactor import RenameResult
c054b6b… lmata 1199
c054b6b… lmata 1200 runner = CliRunner()
c054b6b… lmata 1201 rename_result = RenameResult(
c054b6b… lmata 1202 old_name="old_func",
c054b6b… lmata 1203 new_name="new_func",
c054b6b… lmata 1204 affected_nodes=[],
c054b6b… lmata 1205 affected_files=[],
c054b6b… lmata 1206 edges_updated=0,
c054b6b… lmata 1207 )
c054b6b… lmata 1208 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1209 mock_gs.return_value = _make_store()
c054b6b… lmata 1210 with patch("navegador.refactor.SymbolRenamer") as MockSR:
c054b6b… lmata 1211 MockSR.return_value.apply_rename.return_value = rename_result
c054b6b… lmata 1212 result = runner.invoke(main, ["rename", "old_func", "new_func"])
c054b6b… lmata 1213 assert result.exit_code == 0
c054b6b… lmata 1214 assert "Renamed" in result.output
c054b6b… lmata 1215
c054b6b… lmata 1216
c054b6b… lmata 1217 class TestCLIBranchesSemantic:
c054b6b… lmata 1218 """Cover lines 2068-2080 (semantic-search table output)."""
c054b6b… lmata 1219
c054b6b… lmata 1220 def test_semantic_search_table_output(self):
c054b6b… lmata 1221 from click.testing import CliRunner
c054b6b… lmata 1222
c054b6b… lmata 1223 from navegador.cli.commands import main
c054b6b… lmata 1224
c054b6b… lmata 1225 runner = CliRunner()
c054b6b… lmata 1226 search_results = [
c054b6b… lmata 1227 {"score": 0.95, "type": "Function", "name": "authenticate", "file_path": "auth.py"},
c054b6b… lmata 1228 ]
c054b6b… lmata 1229 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1230 mock_gs.return_value = _make_store()
c054b6b… lmata 1231 with patch("navegador.intelligence.search.SemanticSearch") as MockSS:
c054b6b… lmata 1232 MockSS.return_value.search.return_value = search_results
c054b6b… lmata 1233 with patch("navegador.llm.auto_provider") as mock_ap:
c054b6b… lmata 1234 mock_ap.return_value = MagicMock()
c054b6b… lmata 1235 result = runner.invoke(main, ["semantic-search", "auth tokens"])
c054b6b… lmata 1236 assert result.exit_code == 0
c054b6b… lmata 1237 assert "authenticate" in result.output
c054b6b… lmata 1238
c054b6b… lmata 1239 def test_semantic_search_no_results(self):
c054b6b… lmata 1240 from click.testing import CliRunner
c054b6b… lmata 1241
c054b6b… lmata 1242 from navegador.cli.commands import main
c054b6b… lmata 1243
c054b6b… lmata 1244 runner = CliRunner()
c054b6b… lmata 1245 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1246 mock_gs.return_value = _make_store()
c054b6b… lmata 1247 with patch("navegador.intelligence.search.SemanticSearch") as MockSS:
c054b6b… lmata 1248 MockSS.return_value.search.return_value = []
c054b6b… lmata 1249 with patch("navegador.llm.auto_provider") as mock_ap:
c054b6b… lmata 1250 mock_ap.return_value = MagicMock()
c054b6b… lmata 1251 result = runner.invoke(main, ["semantic-search", "nothing"])
c054b6b… lmata 1252 assert result.exit_code == 0
c054b6b… lmata 1253 assert "--index" in result.output
c054b6b… lmata 1254
c054b6b… lmata 1255 def test_semantic_search_with_index_flag(self):
c054b6b… lmata 1256 from click.testing import CliRunner
c054b6b… lmata 1257
c054b6b… lmata 1258 from navegador.cli.commands import main
c054b6b… lmata 1259
c054b6b… lmata 1260 runner = CliRunner()
c054b6b… lmata 1261 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1262 mock_gs.return_value = _make_store()
c054b6b… lmata 1263 with patch("navegador.intelligence.search.SemanticSearch") as MockSS:
c054b6b… lmata 1264 inst = MockSS.return_value
c054b6b… lmata 1265 inst.index.return_value = 42
c054b6b… lmata 1266 inst.search.return_value = []
c054b6b… lmata 1267 with patch("navegador.llm.auto_provider") as mock_ap:
c054b6b… lmata 1268 mock_ap.return_value = MagicMock()
c054b6b… lmata 1269 result = runner.invoke(main, ["semantic-search", "auth", "--index"])
c054b6b… lmata 1270 assert result.exit_code == 0
c054b6b… lmata 1271 assert "42" in result.output
c054b6b… lmata 1272
c054b6b… lmata 1273
c054b6b… lmata 1274 class TestCLIBranchesRepoCommands:
c054b6b… lmata 1275 """Cover lines 1539-1572 (repo list/ingest-all table output)."""
c054b6b… lmata 1276
c054b6b… lmata 1277 def test_repo_list_table_output(self):
c054b6b… lmata 1278 from click.testing import CliRunner
c054b6b… lmata 1279
c054b6b… lmata 1280 from navegador.cli.commands import main
c054b6b… lmata 1281
c054b6b… lmata 1282 runner = CliRunner()
c054b6b… lmata 1283 repos = [{"name": "myrepo", "path": "/path/to/myrepo"}]
c054b6b… lmata 1284 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1285 mock_gs.return_value = _make_store()
c054b6b… lmata 1286 with patch("navegador.multirepo.MultiRepoManager") as MockMRM:
c054b6b… lmata 1287 MockMRM.return_value.list_repos.return_value = repos
c054b6b… lmata 1288 result = runner.invoke(main, ["repo", "list"])
c054b6b… lmata 1289 assert result.exit_code == 0
c054b6b… lmata 1290 assert "myrepo" in result.output
c054b6b… lmata 1291
c054b6b… lmata 1292 def test_repo_list_empty(self):
c054b6b… lmata 1293 from click.testing import CliRunner
c054b6b… lmata 1294
c054b6b… lmata 1295 from navegador.cli.commands import main
c054b6b… lmata 1296
c054b6b… lmata 1297 runner = CliRunner()
c054b6b… lmata 1298 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1299 mock_gs.return_value = _make_store()
c054b6b… lmata 1300 with patch("navegador.multirepo.MultiRepoManager") as MockMRM:
c054b6b… lmata 1301 MockMRM.return_value.list_repos.return_value = []
c054b6b… lmata 1302 result = runner.invoke(main, ["repo", "list"])
c054b6b… lmata 1303 assert result.exit_code == 0
c054b6b… lmata 1304 assert "No repositories" in result.output
c054b6b… lmata 1305
c054b6b… lmata 1306 def test_repo_ingest_all_table(self):
c054b6b… lmata 1307 from click.testing import CliRunner
c054b6b… lmata 1308
c054b6b… lmata 1309 from navegador.cli.commands import main
c054b6b… lmata 1310
c054b6b… lmata 1311 runner = CliRunner()
c054b6b… lmata 1312 summary = {"myrepo": {"files": 5, "functions": 10, "classes": 2}}
c054b6b… lmata 1313 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1314 mock_gs.return_value = _make_store()
c054b6b… lmata 1315 with patch("navegador.multirepo.MultiRepoManager") as MockMRM:
c054b6b… lmata 1316 MockMRM.return_value.ingest_all.return_value = summary
c054b6b… lmata 1317 result = runner.invoke(main, ["repo", "ingest-all"])
c054b6b… lmata 1318 assert result.exit_code == 0
c054b6b… lmata 1319 assert "myrepo" in result.output
c054b6b… lmata 1320
c054b6b… lmata 1321 def test_repo_search_table(self):
c054b6b… lmata 1322 from click.testing import CliRunner
c054b6b… lmata 1323
c054b6b… lmata 1324 from navegador.cli.commands import main
c054b6b… lmata 1325
c054b6b… lmata 1326 runner = CliRunner()
c054b6b… lmata 1327 results = [{"label": "Function", "name": "foo", "file_path": "foo.py"}]
c054b6b… lmata 1328 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1329 mock_gs.return_value = _make_store()
c054b6b… lmata 1330 with patch("navegador.multirepo.MultiRepoManager") as MockMRM:
c054b6b… lmata 1331 MockMRM.return_value.cross_repo_search.return_value = results
c054b6b… lmata 1332 result = runner.invoke(main, ["repo", "search", "foo"])
c054b6b… lmata 1333 assert result.exit_code == 0
c054b6b… lmata 1334 assert "foo" in result.output
c054b6b… lmata 1335
c054b6b… lmata 1336 def test_repo_search_empty(self):
c054b6b… lmata 1337 from click.testing import CliRunner
c054b6b… lmata 1338
c054b6b… lmata 1339 from navegador.cli.commands import main
c054b6b… lmata 1340
c054b6b… lmata 1341 runner = CliRunner()
c054b6b… lmata 1342 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1343 mock_gs.return_value = _make_store()
c054b6b… lmata 1344 with patch("navegador.multirepo.MultiRepoManager") as MockMRM:
c054b6b… lmata 1345 MockMRM.return_value.cross_repo_search.return_value = []
c054b6b… lmata 1346 result = runner.invoke(main, ["repo", "search", "nothing"])
c054b6b… lmata 1347 assert result.exit_code == 0
c054b6b… lmata 1348 assert "No results" in result.output
c054b6b… lmata 1349
c054b6b… lmata 1350
c054b6b… lmata 1351 class TestCLIBranchesPM:
c054b6b… lmata 1352 """Cover lines 1793-1806 (pm ingest output)."""
c054b6b… lmata 1353
c054b6b… lmata 1354 def test_pm_ingest_no_github_raises(self):
c054b6b… lmata 1355 from click.testing import CliRunner
c054b6b… lmata 1356
c054b6b… lmata 1357 from navegador.cli.commands import main
c054b6b… lmata 1358
c054b6b… lmata 1359 runner = CliRunner()
c054b6b… lmata 1360 result = runner.invoke(main, ["pm", "ingest"])
c054b6b… lmata 1361 assert result.exit_code != 0
c054b6b… lmata 1362
c054b6b… lmata 1363 def test_pm_ingest_table_output(self):
c054b6b… lmata 1364 from click.testing import CliRunner
c054b6b… lmata 1365
c054b6b… lmata 1366 from navegador.cli.commands import main
c054b6b… lmata 1367
c054b6b… lmata 1368 runner = CliRunner()
c054b6b… lmata 1369 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1370 mock_gs.return_value = _make_store()
c054b6b… lmata 1371 with patch("navegador.pm.TicketIngester") as MockTI:
c054b6b… lmata 1372 MockTI.return_value.ingest_github_issues.return_value = {
c054b6b… lmata 1373 "tickets": 5, "linked": 2
c054b6b… lmata 1374 }
c054b6b… lmata 1375 result = runner.invoke(main, ["pm", "ingest", "--github", "owner/repo"])
c054b6b… lmata 1376 assert result.exit_code == 0
c054b6b… lmata 1377 assert "5" in result.output
c054b6b… lmata 1378
c054b6b… lmata 1379 def test_pm_ingest_json(self):
c054b6b… lmata 1380 from click.testing import CliRunner
c054b6b… lmata 1381
c054b6b… lmata 1382 from navegador.cli.commands import main
c054b6b… lmata 1383
c054b6b… lmata 1384 runner = CliRunner()
c054b6b… lmata 1385 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1386 mock_gs.return_value = _make_store()
c054b6b… lmata 1387 with patch("navegador.pm.TicketIngester") as MockTI:
c054b6b… lmata 1388 MockTI.return_value.ingest_github_issues.return_value = {
c054b6b… lmata 1389 "tickets": 3, "linked": 1
c054b6b… lmata 1390 }
c054b6b… lmata 1391 result = runner.invoke(
c054b6b… lmata 1392 main, ["pm", "ingest", "--github", "owner/repo", "--json"]
c054b6b… lmata 1393 )
c054b6b… lmata 1394 assert result.exit_code == 0
c054b6b… lmata 1395 data = json.loads(result.output)
c054b6b… lmata 1396 assert data["tickets"] == 3
c054b6b… lmata 1397
c054b6b… lmata 1398
c054b6b… lmata 1399 class TestCLIBranchesIngest:
c054b6b… lmata 1400 """Cover lines 179-185 (ingest --monorepo table output)."""
c054b6b… lmata 1401
c054b6b… lmata 1402 def test_ingest_monorepo_table(self, tmp_path):
c054b6b… lmata 1403 from click.testing import CliRunner
c054b6b… lmata 1404
c054b6b… lmata 1405 from navegador.cli.commands import main
c054b6b… lmata 1406
c054b6b… lmata 1407 runner = CliRunner()
c054b6b… lmata 1408 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1409 mock_gs.return_value = _make_store()
c054b6b… lmata 1410 with patch("navegador.monorepo.MonorepoIngester") as MockMI:
c054b6b… lmata 1411 MockMI.return_value.ingest.return_value = {
c054b6b… lmata 1412 "files": 10, "functions": 20, "packages": 3, "workspace_type": "yarn"
c054b6b… lmata 1413 }
c054b6b… lmata 1414 result = runner.invoke(
c054b6b… lmata 1415 main, ["ingest", str(tmp_path), "--monorepo"]
c054b6b… lmata 1416 )
c054b6b… lmata 1417 assert result.exit_code == 0
c054b6b… lmata 1418
c054b6b… lmata 1419 def test_ingest_monorepo_json(self, tmp_path):
c054b6b… lmata 1420 from click.testing import CliRunner
c054b6b… lmata 1421
c054b6b… lmata 1422 from navegador.cli.commands import main
c054b6b… lmata 1423
c054b6b… lmata 1424 runner = CliRunner()
c054b6b… lmata 1425 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1426 mock_gs.return_value = _make_store()
c054b6b… lmata 1427 with patch("navegador.monorepo.MonorepoIngester") as MockMI:
c054b6b… lmata 1428 MockMI.return_value.ingest.return_value = {
c054b6b… lmata 1429 "files": 5, "packages": 2
c054b6b… lmata 1430 }
c054b6b… lmata 1431 result = runner.invoke(
c054b6b… lmata 1432 main, ["ingest", str(tmp_path), "--monorepo", "--json"]
c054b6b… lmata 1433 )
c054b6b… lmata 1434 assert result.exit_code == 0
c054b6b… lmata 1435 data = json.loads(result.output)
c054b6b… lmata 1436 assert data["files"] == 5
c054b6b… lmata 1437
c054b6b… lmata 1438
c054b6b… lmata 1439 class TestCLIBranchesSubmodulesIngest:
c054b6b… lmata 1440 """Cover lines 1901-1916 (submodules ingest output)."""
c054b6b… lmata 1441
c054b6b… lmata 1442 def test_submodules_ingest_output(self, tmp_path):
c054b6b… lmata 1443 from click.testing import CliRunner
c054b6b… lmata 1444
c054b6b… lmata 1445 from navegador.cli.commands import main
c054b6b… lmata 1446
c054b6b… lmata 1447 runner = CliRunner()
c054b6b… lmata 1448 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1449 mock_gs.return_value = _make_store()
c054b6b… lmata 1450 with patch("navegador.submodules.SubmoduleIngester") as MockSI:
c054b6b… lmata 1451 MockSI.return_value.ingest_with_submodules.return_value = {
c054b6b… lmata 1452 "total_files": 10,
c054b6b… lmata 1453 "submodules": {"sub1": {}, "sub2": {}},
c054b6b… lmata 1454 }
c054b6b… lmata 1455 result = runner.invoke(main, ["submodules", "ingest", str(tmp_path)])
c054b6b… lmata 1456 assert result.exit_code == 0
c054b6b… lmata 1457 assert "sub1" in result.output
c054b6b… lmata 1458
c054b6b… lmata 1459 def test_submodules_ingest_json(self, tmp_path):
c054b6b… lmata 1460 from click.testing import CliRunner
c054b6b… lmata 1461
c054b6b… lmata 1462 from navegador.cli.commands import main
c054b6b… lmata 1463
c054b6b… lmata 1464 runner = CliRunner()
c054b6b… lmata 1465 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1466 mock_gs.return_value = _make_store()
c054b6b… lmata 1467 with patch("navegador.submodules.SubmoduleIngester") as MockSI:
c054b6b… lmata 1468 MockSI.return_value.ingest_with_submodules.return_value = {
c054b6b… lmata 1469 "total_files": 5,
c054b6b… lmata 1470 "submodules": {},
c054b6b… lmata 1471 }
c054b6b… lmata 1472 result = runner.invoke(
c054b6b… lmata 1473 main, ["submodules", "ingest", str(tmp_path), "--json"]
c054b6b… lmata 1474 )
c054b6b… lmata 1475 assert result.exit_code == 0
c054b6b… lmata 1476 data = json.loads(result.output)
c054b6b… lmata 1477 assert data["total_files"] == 5
c054b6b… lmata 1478
c054b6b… lmata 1479
c054b6b… lmata 1480 class TestCLIBranchesCommunities:
c054b6b… lmata 1481 """Cover lines 2105-2141 (communities store-labels + table)."""
c054b6b… lmata 1482
c054b6b… lmata 1483 def test_communities_store_labels(self):
c054b6b… lmata 1484 from click.testing import CliRunner
c054b6b… lmata 1485
c054b6b… lmata 1486 from navegador.cli.commands import main
c054b6b… lmata 1487 from navegador.intelligence.community import Community
c054b6b… lmata 1488
c054b6b… lmata 1489 runner = CliRunner()
c054b6b… lmata 1490 comm = Community(name="c1", members=["a", "b", "c"], density=0.5)
c054b6b… lmata 1491 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1492 mock_gs.return_value = _make_store()
c054b6b… lmata 1493 with patch("navegador.intelligence.community.CommunityDetector") as MockCD:
c054b6b… lmata 1494 inst = MockCD.return_value
c054b6b… lmata 1495 inst.detect.return_value = [comm]
c054b6b… lmata 1496 inst.store_communities.return_value = 3
c054b6b… lmata 1497 result = runner.invoke(main, ["communities", "--store-labels"])
c054b6b… lmata 1498 assert result.exit_code == 0
c054b6b… lmata 1499 assert "3" in result.output
c054b6b… lmata 1500
c054b6b… lmata 1501 def test_communities_empty(self):
c054b6b… lmata 1502 from click.testing import CliRunner
c054b6b… lmata 1503
c054b6b… lmata 1504 from navegador.cli.commands import main
c054b6b… lmata 1505
c054b6b… lmata 1506 runner = CliRunner()
c054b6b… lmata 1507 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1508 mock_gs.return_value = _make_store()
c054b6b… lmata 1509 with patch("navegador.intelligence.community.CommunityDetector") as MockCD:
c054b6b… lmata 1510 MockCD.return_value.detect.return_value = []
c054b6b… lmata 1511 result = runner.invoke(main, ["communities"])
c054b6b… lmata 1512 assert result.exit_code == 0
c054b6b… lmata 1513 assert "No communities" in result.output
c054b6b… lmata 1514
c054b6b… lmata 1515 def test_communities_large_preview(self):
c054b6b… lmata 1516 from click.testing import CliRunner
c054b6b… lmata 1517
c054b6b… lmata 1518 from navegador.cli.commands import main
c054b6b… lmata 1519 from navegador.intelligence.community import Community
c054b6b… lmata 1520
c054b6b… lmata 1521 runner = CliRunner()
c054b6b… lmata 1522 comm = Community(name="big", members=list("abcdefgh"), density=0.8)
c054b6b… lmata 1523 with patch("navegador.cli.commands._get_store") as mock_gs:
c054b6b… lmata 1524 mock_gs.return_value = _make_store()
c054b6b… lmata 1525 with patch("navegador.intelligence.community.CommunityDetector") as MockCD:
c054b6b… lmata 1526 MockCD.return_value.detect.return_value = [comm]
c054b6b… lmata 1527 result = runner.invoke(main, ["communities"])
c054b6b… lmata 1528 assert result.exit_code == 0
c054b6b… lmata 1529 assert "+" in result.output # preview truncation
c054b6b… lmata 1530
c054b6b… lmata 1531
c054b6b… lmata 1532 # ===========================================================================
c054b6b… lmata 1533 # navegador.cluster.messaging (86% → target ~95%)
c054b6b… lmata 1534 # ===========================================================================
c054b6b… lmata 1535
c054b6b… lmata 1536
c054b6b… lmata 1537 class TestMessageBus:
c054b6b… lmata 1538 """Cover lines 74-78, 154, 178-182, 208."""
c054b6b… lmata 1539
c054b6b… lmata 1540 def _make(self):
c054b6b… lmata 1541 from navegador.cluster.messaging import MessageBus
c054b6b… lmata 1542
c054b6b… lmata 1543 r = MagicMock()
c054b6b… lmata 1544 r.smembers.return_value = set()
c054b6b… lmata 1545 r.lrange.return_value = []
c054b6b… lmata 1546 return MessageBus("redis://localhost:6379", _redis_client=r), r
c054b6b… lmata 1547
c054b6b… lmata 1548 def test_send_returns_message_id(self):
c054b6b… lmata 1549 bus, r = self._make()
c054b6b… lmata 1550 msg_id = bus.send("agent1", "agent2", "task", {"k": "v"})
c054b6b… lmata 1551 assert isinstance(msg_id, str) and len(msg_id) == 36
c054b6b… lmata 1552 r.rpush.assert_called_once()
c054b6b… lmata 1553
c054b6b… lmata 1554 def test_receive_returns_unacked_messages(self):
c054b6b… lmata 1555 import json as _json
c054b6b… lmata 1556 from navegador.cluster.messaging import Message
c054b6b… lmata 1557 import time
c054b6b… lmata 1558
c054b6b… lmata 1559 bus, r = self._make()
c054b6b… lmata 1560 msg = Message(
c054b6b… lmata 1561 id="abc123", from_agent="a1", to_agent="a2",
c054b6b… lmata 1562 type="task", payload={}, timestamp=time.time()
c054b6b… lmata 1563 )
c054b6b… lmata 1564 r.lrange.return_value = [_json.dumps(msg.to_dict()).encode()]
c054b6b… lmata 1565 r.smembers.return_value = set()
c054b6b… lmata 1566 messages = bus.receive("a2")
c054b6b… lmata 1567 assert len(messages) == 1
c054b6b… lmata 1568 assert messages[0].id == "abc123"
c054b6b… lmata 1569
c054b6b… lmata 1570 def test_receive_filters_acked(self):
c054b6b… lmata 1571 import json as _json
c054b6b… lmata 1572 from navegador.cluster.messaging import Message
c054b6b… lmata 1573 import time
c054b6b… lmata 1574
c054b6b… lmata 1575 bus, r = self._make()
c054b6b… lmata 1576 msg = Message(
c054b6b… lmata 1577 id="acked_id", from_agent="a1", to_agent="a2",
c054b6b… lmata 1578 type="task", payload={}, timestamp=time.time()
c054b6b… lmata 1579 )
c054b6b… lmata 1580 r.lrange.return_value = [_json.dumps(msg.to_dict()).encode()]
c054b6b… lmata 1581 r.smembers.return_value = {b"acked_id"}
c054b6b… lmata 1582 messages = bus.receive("a2")
c054b6b… lmata 1583 assert messages == []
c054b6b… lmata 1584
c054b6b… lmata 1585 def test_acknowledge_with_agent_id(self):
c054b6b… lmata 1586 bus, r = self._make()
c054b6b… lmata 1587 bus.acknowledge("msg123", agent_id="a2")
c054b6b… lmata 1588 r.sadd.assert_called()
c054b6b… lmata 1589
c054b6b… lmata 1590 def test_acknowledge_without_agent_id_broadcasts(self):
c054b6b… lmata 1591 bus, r = self._make()
c054b6b… lmata 1592 r.smembers.return_value = {b"agent1", b"agent2"}
c054b6b… lmata 1593 bus.acknowledge("msg123")
c054b6b… lmata 1594 assert r.sadd.call_count == 2
c054b6b… lmata 1595
c054b6b… lmata 1596 def test_broadcast_skips_sender(self):
c054b6b… lmata 1597 bus, r = self._make()
c054b6b… lmata 1598 r.smembers.return_value = {b"sender", b"agent2", b"agent3"}
c054b6b… lmata 1599 with patch.object(bus, "send", return_value="mid") as mock_send:
c054b6b… lmata 1600 ids = bus.broadcast("sender", "task", {})
c054b6b… lmata 1601 assert len(ids) == 2
c054b6b… lmata 1602 for call_args in mock_send.call_args_list:
c054b6b… lmata 1603 assert call_args[0][0] == "sender"
c054b6b… lmata 1604 assert call_args[0][1] != "sender"
c054b6b… lmata 1605
c054b6b… lmata 1606 def test_client_lazy_init_raises_on_missing_redis(self):
c054b6b… lmata 1607 from navegador.cluster.messaging import MessageBus
c054b6b… lmata 1608
c054b6b… lmata 1609 bus = MessageBus("redis://localhost:6379")
c054b6b… lmata 1610 with patch.dict("sys.modules", {"redis": None}):
c054b6b… lmata 1611 with pytest.raises(ImportError, match="redis"):
c054b6b… lmata 1612 bus._client()
c054b6b… lmata 1613
c054b6b… lmata 1614
c054b6b… lmata 1615 # ===========================================================================
c054b6b… lmata 1616 # navegador.cluster.locking (90% → target ~97%)
c054b6b… lmata 1617 # ===========================================================================
c054b6b… lmata 1618
c054b6b… lmata 1619
c054b6b… lmata 1620 class TestDistributedLock:
c054b6b… lmata 1621 """Cover lines 72-76, 120."""
c054b6b… lmata 1622
c054b6b… lmata 1623 def _make(self):
c054b6b… lmata 1624 from navegador.cluster.locking import DistributedLock
c054b6b… lmata 1625
c054b6b… lmata 1626 r = MagicMock()
c054b6b… lmata 1627 return DistributedLock("redis://localhost:6379", "test-lock", _redis_client=r), r
c054b6b… lmata 1628
c054b6b… lmata 1629 def test_acquire_success(self):
c054b6b… lmata 1630 lock, r = self._make()
c054b6b… lmata 1631 r.set.return_value = True
c054b6b… lmata 1632 assert lock.acquire() is True
c054b6b… lmata 1633 assert lock._token is not None
c054b6b… lmata 1634
c054b6b… lmata 1635 def test_acquire_failure(self):
c054b6b… lmata 1636 lock, r = self._make()
c054b6b… lmata 1637 r.set.return_value = None
c054b6b… lmata 1638 assert lock.acquire() is False
c054b6b… lmata 1639
c054b6b… lmata 1640 def test_release_when_token_matches(self):
c054b6b… lmata 1641 lock, r = self._make()
c054b6b… lmata 1642 lock._token = "mytoken"
c054b6b… lmata 1643 r.get.return_value = b"mytoken"
c054b6b… lmata 1644 lock.release()
c054b6b… lmata 1645 r.delete.assert_called_once()
c054b6b… lmata 1646 assert lock._token is None
c054b6b… lmata 1647
c054b6b… lmata 1648 def test_release_when_token_not_held(self):
c054b6b… lmata 1649 lock, r = self._make()
c054b6b… lmata 1650 lock._token = None
c054b6b… lmata 1651 lock.release()
c054b6b… lmata 1652 r.delete.assert_not_called()
c054b6b… lmata 1653
c054b6b… lmata 1654 def test_release_when_stored_token_differs(self):
c054b6b… lmata 1655 lock, r = self._make()
c054b6b… lmata 1656 lock._token = "mytoken"
c054b6b… lmata 1657 r.get.return_value = b"other_token"
c054b6b… lmata 1658 lock.release()
c054b6b… lmata 1659 r.delete.assert_not_called()
c054b6b… lmata 1660
c054b6b… lmata 1661 def test_context_manager_acquires_and_releases(self):
c054b6b… lmata 1662 lock, r = self._make()
c054b6b… lmata 1663 r.set.return_value = True
c054b6b… lmata 1664 r.get.return_value = None # simulate already released
c054b6b… lmata 1665 with lock:
c054b6b… lmata 1666 pass
c054b6b… lmata 1667 assert lock._token is None
c054b6b… lmata 1668
c054b6b… lmata 1669 def test_context_manager_raises_on_timeout(self):
c054b6b… lmata 1670 import time
c054b6b… lmata 1671 from navegador.cluster.locking import LockTimeout
c054b6b… lmata 1672
c054b6b… lmata 1673 lock, r = self._make()
c054b6b… lmata 1674 r.set.return_value = None # never acquired
c054b6b… lmata 1675 lock._timeout = 0
c054b6b… lmata 1676 lock._retry_interval = 0
c054b6b… lmata 1677
c054b6b… lmata 1678 with pytest.raises(LockTimeout):
c054b6b… lmata 1679 with lock:
c054b6b… lmata 1680 pass
c054b6b… lmata 1681
c054b6b… lmata 1682 def test_client_lazy_init_raises_on_missing_redis(self):
c054b6b… lmata 1683 from navegador.cluster.locking import DistributedLock
c054b6b… lmata 1684
c054b6b… lmata 1685 lock = DistributedLock("redis://localhost:6379", "x")
c054b6b… lmata 1686 with patch.dict("sys.modules", {"redis": None}):
c054b6b… lmata 1687 with pytest.raises(ImportError, match="redis"):
c054b6b… lmata 1688 lock._client()
c054b6b… lmata 1689
c054b6b… lmata 1690
c054b6b… lmata 1691 # ===========================================================================
c054b6b… lmata 1692 # navegador.cluster.observability (89% → target ~97%)
c054b6b… lmata 1693 # ===========================================================================
c054b6b… lmata 1694
c054b6b… lmata 1695
c054b6b… lmata 1696 class TestSwarmDashboard:
c054b6b… lmata 1697 """Cover lines 44-48, 93, 108, 160."""
c054b6b… lmata 1698
c054b6b… lmata 1699 def _make(self):
c054b6b… lmata 1700 from navegador.cluster.observability import SwarmDashboard
c054b6b… lmata 1701
c054b6b… lmata 1702 r = MagicMock()
c054b6b… lmata 1703 r.keys.return_value = []
c054b6b… lmata 1704 r.get.return_value = None
c054b6b… lmata 1705 return SwarmDashboard("redis://localhost:6379", _redis_client=r), r
c054b6b… lmata 1706
c054b6b… lmata 1707 def test_register_agent(self):
c054b6b… lmata 1708 dash, r = self._make()
c054b6b… lmata 1709 dash.register_agent("agent1", metadata={"role": "ingester"})
c054b6b… lmata 1710 r.setex.assert_called_once()
c054b6b… lmata 1711
c054b6b… lmata 1712 def test_register_agent_no_metadata(self):
c054b6b… lmata 1713 dash, r = self._make()
c054b6b… lmata 1714 dash.register_agent("agent1")
c054b6b… lmata 1715 r.setex.assert_called_once()
c054b6b… lmata 1716
c054b6b… lmata 1717 def test_agent_status_empty(self):
c054b6b… lmata 1718 dash, r = self._make()
c054b6b… lmata 1719 r.keys.return_value = []
c054b6b… lmata 1720 agents = dash.agent_status()
c054b6b… lmata 1721 assert agents == []
c054b6b… lmata 1722
c054b6b… lmata 1723 def test_agent_status_returns_active_agents(self):
c054b6b… lmata 1724 import json as _json
c054b6b… lmata 1725 dash, r = self._make()
c054b6b… lmata 1726 payload = {"agent_id": "a1", "last_seen": 12345, "state": "active"}
c054b6b… lmata 1727 r.keys.return_value = [b"navegador:obs:agent:a1"]
c054b6b… lmata 1728 r.get.return_value = _json.dumps(payload).encode()
c054b6b… lmata 1729 agents = dash.agent_status()
c054b6b… lmata 1730 assert len(agents) == 1
c054b6b… lmata 1731 assert agents[0]["agent_id"] == "a1"
c054b6b… lmata 1732
c054b6b… lmata 1733 def test_task_metrics_default(self):
c054b6b… lmata 1734 dash, r = self._make()
c054b6b… lmata 1735 r.get.return_value = None
c054b6b… lmata 1736 metrics = dash.task_metrics()
c054b6b… lmata 1737 assert metrics == {"pending": 0, "active": 0, "completed": 0, "failed": 0}
c054b6b… lmata 1738
c054b6b… lmata 1739 def test_task_metrics_from_redis(self):
c054b6b… lmata 1740 import json as _json
c054b6b… lmata 1741 dash, r = self._make()
c054b6b… lmata 1742 stored = {"pending": 3, "active": 1, "completed": 10, "failed": 0}
c054b6b… lmata 1743 r.get.return_value = _json.dumps(stored).encode()
c054b6b… lmata 1744 metrics = dash.task_metrics()
c054b6b… lmata 1745 assert metrics["pending"] == 3
c054b6b… lmata 1746
c054b6b… lmata 1747 def test_update_task_metrics(self):
c054b6b… lmata 1748 import json as _json
c054b6b… lmata 1749 dash, r = self._make()
c054b6b… lmata 1750 r.get.return_value = _json.dumps(
c054b6b… lmata 1751 {"pending": 0, "active": 0, "completed": 0, "failed": 0}
c054b6b… lmata 1752 ).encode()
c054b6b… lmata 1753 dash.update_task_metrics(pending=5, active=2)
c054b6b… lmata 1754 r.set.assert_called_once()
c054b6b… lmata 1755
c054b6b… lmata 1756 def test_graph_metrics(self):
c054b6b… lmata 1757 store = _make_store()
c054b6b… lmata 1758 store.node_count.return_value = 42
c054b6b… lmata 1759 store.edge_count.return_value = 100
c054b6b… lmata 1760 dash, r = self._make()
c054b6b… lmata 1761 result = dash.graph_metrics(store)
c054b6b… lmata 1762 assert result["node_count"] == 42
c054b6b… lmata 1763 assert result["edge_count"] == 100
c054b6b… lmata 1764
c054b6b… lmata 1765 def test_to_json_structure(self):
c054b6b… lmata 1766 import json as _json
c054b6b… lmata 1767 dash, r = self._make()
c054b6b… lmata 1768 r.keys.return_value = []
c054b6b… lmata 1769 r.get.side_effect = [None, None] # graph_meta + task_metrics
c054b6b… lmata 1770 snapshot = _json.loads(dash.to_json())
c054b6b… lmata 1771 assert "agents" in snapshot
c054b6b… lmata 1772 assert "task_metrics" in snapshot
c054b6b… lmata 1773
c054b6b… lmata 1774 def test_client_lazy_init_raises_on_missing_redis(self):
c054b6b… lmata 1775 from navegador.cluster.observability import SwarmDashboard
c054b6b… lmata 1776
c054b6b… lmata 1777 dash = SwarmDashboard("redis://localhost:6379")
c054b6b… lmata 1778 with patch.dict("sys.modules", {"redis": None}):
c054b6b… lmata 1779 with pytest.raises(ImportError, match="redis"):
c054b6b… lmata 1780 dash._client()
c054b6b… lmata 1781
c054b6b… lmata 1782
c054b6b… lmata 1783 # ===========================================================================
c054b6b… lmata 1784 # navegador.cluster.pubsub (86% → target ~97%)
c054b6b… lmata 1785 # ===========================================================================
c054b6b… lmata 1786
c054b6b… lmata 1787
c054b6b… lmata 1788 class TestGraphNotifier:
c054b6b… lmata 1789 """Cover lines 72-76, 159-162."""
c054b6b… lmata 1790
c054b6b… lmata 1791 def _make(self):
c054b6b… lmata 1792 from navegador.cluster.pubsub import GraphNotifier
c054b6b… lmata 1793
c054b6b… lmata 1794 r = MagicMock()
c054b6b… lmata 1795 pubsub_mock = MagicMock()
c054b6b… lmata 1796 pubsub_mock.listen.return_value = iter([])
c054b6b… lmata 1797 r.pubsub.return_value = pubsub_mock
c054b6b… lmata 1798 r.publish.return_value = 1
c054b6b… lmata 1799 return GraphNotifier("redis://localhost:6379", redis_client=r), r
c054b6b… lmata 1800
c054b6b… lmata 1801 def test_publish_returns_receiver_count(self):
c054b6b… lmata 1802 from navegador.cluster.pubsub import EventType
c054b6b… lmata 1803
c054b6b… lmata 1804 notifier, r = self._make()
c054b6b… lmata 1805 count = notifier.publish(EventType.NODE_CREATED, {"name": "foo"})
c054b6b… lmata 1806 assert count == 1
c054b6b… lmata 1807 r.publish.assert_called_once()
c054b6b… lmata 1808
c054b6b… lmata 1809 def test_publish_with_string_event_type(self):
c054b6b… lmata 1810 notifier, r = self._make()
c054b6b… lmata 1811 count = notifier.publish("custom_event", {"key": "val"})
c054b6b… lmata 1812 assert count == 1
c054b6b… lmata 1813
c054b6b… lmata 1814 def test_subscribe_run_in_thread(self):
c054b6b… lmata 1815 from navegador.cluster.pubsub import EventType
c054b6b… lmata 1816
c054b6b… lmata 1817 notifier, r = self._make()
c054b6b… lmata 1818 import json as _json
c054b6b… lmata 1819 import threading
c054b6b… lmata 1820
c054b6b… lmata 1821 # Return one message then stop
c054b6b… lmata 1822 msg = {
c054b6b… lmata 1823 "type": "message",
c054b6b… lmata 1824 "data": _json.dumps({"event_type": "node_created", "data": {"k": "v"}}).encode(),
c054b6b… lmata 1825 }
c054b6b… lmata 1826 r.pubsub.return_value.listen.return_value = iter([msg])
c054b6b… lmata 1827
c054b6b… lmata 1828 received = []
c054b6b… lmata 1829
c054b6b… lmata 1830 def callback(et, data):
c054b6b… lmata 1831 received.append((et, data))
c054b6b… lmata 1832
c054b6b… lmata 1833 t = notifier.subscribe([EventType.NODE_CREATED], callback, run_in_thread=True)
c054b6b… lmata 1834 assert isinstance(t, threading.Thread)
c054b6b… lmata 1835 t.join(timeout=2)
c054b6b… lmata 1836 assert received == [("node_created", {"k": "v"})]
c054b6b… lmata 1837
c054b6b… lmata 1838 def test_close(self):
c054b6b… lmata 1839 notifier, r = self._make()
c054b6b… lmata 1840 notifier.close()
c054b6b… lmata 1841 r.close.assert_called_once()
c054b6b… lmata 1842
c054b6b… lmata 1843 def test_close_ignores_exception(self):
c054b6b… lmata 1844 notifier, r = self._make()
c054b6b… lmata 1845 r.close.side_effect = Exception("closed")
c054b6b… lmata 1846 notifier.close() # should not raise
c054b6b… lmata 1847
c054b6b… lmata 1848 def test_connect_redis_raises_on_missing_dep(self):
c054b6b… lmata 1849 from navegador.cluster.pubsub import GraphNotifier
c054b6b… lmata 1850
c054b6b… lmata 1851 with patch.dict("sys.modules", {"redis": None}):
c054b6b… lmata 1852 with pytest.raises(ImportError, match="redis"):
c054b6b… lmata 1853 GraphNotifier._connect_redis("redis://localhost")
c054b6b… lmata 1854
c054b6b… lmata 1855
c054b6b… lmata 1856 # ===========================================================================
c054b6b… lmata 1857 # navegador.ingestion.ruby — extra branches (66% → target ~85%)
c054b6b… lmata 1858 # ===========================================================================
c054b6b… lmata 1859
c054b6b… lmata 1860
c054b6b… lmata 1861 class TestRubyParserBranches:
c054b6b… lmata 1862 """Exercise _handle_class superclass, _handle_module body, _maybe_handle_require,
c054b6b… lmata 1863 _extract_calls and fallback paths."""
c054b6b… lmata 1864
c054b6b… lmata 1865 def _make_parser(self):
c054b6b… lmata 1866 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1867 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1868 return RubyParser()
c054b6b… lmata 1869
c054b6b… lmata 1870 def test_handle_class_with_superclass(self):
c054b6b… lmata 1871 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1872 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1873
c054b6b… lmata 1874 parser = RubyParser()
c054b6b… lmata 1875 store = _make_store()
c054b6b… lmata 1876
c054b6b… lmata 1877 name_node = _text_node(b"MyClass")
c054b6b… lmata 1878 superclass_node = _text_node(b"< BaseClass", "constant")
c054b6b… lmata 1879 class_node = MockNode("class", children=[name_node, superclass_node])
c054b6b… lmata 1880 class_node.set_field("name", name_node)
c054b6b… lmata 1881 class_node.set_field("superclass", superclass_node)
c054b6b… lmata 1882
c054b6b… lmata 1883 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1884 parser._handle_class(class_node, b"class MyClass < BaseClass\nend", "f.rb", store, stats)
c054b6b… lmata 1885 assert stats["classes"] == 1
c054b6b… lmata 1886 assert stats["edges"] >= 2 # CONTAINS + INHERITS
c054b6b… lmata 1887
c054b6b… lmata 1888 def test_handle_class_no_name_node(self):
c054b6b… lmata 1889 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1890 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1891
c054b6b… lmata 1892 parser = RubyParser()
c054b6b… lmata 1893 store = _make_store()
c054b6b… lmata 1894 anon_class = MockNode("class")
c054b6b… lmata 1895 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1896 parser._handle_class(anon_class, b"class; end", "f.rb", store, stats)
c054b6b… lmata 1897 assert stats["classes"] == 0
c054b6b… lmata 1898
c054b6b… lmata 1899 def test_handle_module_with_body(self):
c054b6b… lmata 1900 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1901 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1902
c054b6b… lmata 1903 parser = RubyParser()
c054b6b… lmata 1904 store = _make_store()
c054b6b… lmata 1905
c054b6b… lmata 1906 name_node = _text_node(b"MyModule")
c054b6b… lmata 1907 method_name = _text_node(b"my_method")
c054b6b… lmata 1908 method_node = MockNode("method", children=[method_name])
c054b6b… lmata 1909 method_node.set_field("name", method_name)
c054b6b… lmata 1910 body_node = MockNode("body_statement", children=[method_node])
c054b6b… lmata 1911 mod_node = MockNode("module", children=[name_node, body_node])
c054b6b… lmata 1912 mod_node.set_field("name", name_node)
c054b6b… lmata 1913 # body found via body_statement child (no "body" field)
c054b6b… lmata 1914
c054b6b… lmata 1915 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1916 src = b"module MyModule\n def my_method; end\nend"
c054b6b… lmata 1917 parser._handle_module(mod_node, src, "f.rb", store, stats)
c054b6b… lmata 1918 assert stats["classes"] == 1
c054b6b… lmata 1919
c054b6b… lmata 1920 def test_handle_method_standalone_function(self):
c054b6b… lmata 1921 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1922 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1923
c054b6b… lmata 1924 parser = RubyParser()
c054b6b… lmata 1925 store = _make_store()
c054b6b… lmata 1926
c054b6b… lmata 1927 name_node = _text_node(b"standalone_fn")
c054b6b… lmata 1928 fn_node = MockNode("method", children=[name_node])
c054b6b… lmata 1929 fn_node.set_field("name", name_node)
c054b6b… lmata 1930
c054b6b… lmata 1931 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1932 parser._handle_method(fn_node, b"def standalone_fn; end", "f.rb", store, stats, class_name=None)
c054b6b… lmata 1933 assert stats["functions"] == 1
c054b6b… lmata 1934 # No class → Function node
c054b6b… lmata 1935 create_call = store.create_node.call_args_list[-1]
c054b6b… lmata 1936 from navegador.graph.schema import NodeLabel
c054b6b… lmata 1937 assert create_call[0][0] == NodeLabel.Function
c054b6b… lmata 1938
c054b6b… lmata 1939 def test_handle_method_no_name(self):
c054b6b… lmata 1940 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1941 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1942
c054b6b… lmata 1943 parser = RubyParser()
c054b6b… lmata 1944 store = _make_store()
c054b6b… lmata 1945 anon_method = MockNode("method")
c054b6b… lmata 1946 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1947 parser._handle_method(anon_method, b"", "f.rb", store, stats, class_name=None)
c054b6b… lmata 1948 assert stats["functions"] == 0
c054b6b… lmata 1949
c054b6b… lmata 1950 def test_maybe_handle_require(self):
c054b6b… lmata 1951 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1952 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1953
c054b6b… lmata 1954 parser = RubyParser()
c054b6b… lmata 1955 store = _make_store()
c054b6b… lmata 1956
c054b6b… lmata 1957 method_node = _text_node(b"require", "identifier")
c054b6b… lmata 1958 string_node = _text_node(b"'json'", "string")
c054b6b… lmata 1959 args_node = MockNode("argument_list", children=[string_node])
c054b6b… lmata 1960 call_node = MockNode("call", children=[method_node, args_node])
c054b6b… lmata 1961 call_node.set_field("method", method_node)
c054b6b… lmata 1962 call_node.set_field("arguments", args_node)
c054b6b… lmata 1963
c054b6b… lmata 1964 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1965 src = b"require 'json'"
c054b6b… lmata 1966 parser._maybe_handle_require(call_node, src, "f.rb", store, stats)
c054b6b… lmata 1967 store.create_node.assert_called_once()
c054b6b… lmata 1968
c054b6b… lmata 1969 def test_maybe_handle_require_skips_non_require(self):
c054b6b… lmata 1970 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1971 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1972
c054b6b… lmata 1973 parser = RubyParser()
c054b6b… lmata 1974 store = _make_store()
c054b6b… lmata 1975
c054b6b… lmata 1976 method_node = _text_node(b"puts", "identifier")
c054b6b… lmata 1977 call_node = MockNode("call", children=[method_node])
c054b6b… lmata 1978 call_node.set_field("method", method_node)
c054b6b… lmata 1979
c054b6b… lmata 1980 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1981 parser._maybe_handle_require(call_node, b"puts 'hi'", "f.rb", store, stats)
c054b6b… lmata 1982 store.create_node.assert_not_called()
c054b6b… lmata 1983
c054b6b… lmata 1984 def test_extract_calls(self):
c054b6b… lmata 1985 with _mock_ts("tree_sitter_ruby"):
c054b6b… lmata 1986 from navegador.ingestion.ruby import RubyParser
c054b6b… lmata 1987 from navegador.graph.schema import NodeLabel
c054b6b… lmata 1988
c054b6b… lmata 1989 parser = RubyParser()
c054b6b… lmata 1990 store = _make_store()
c054b6b… lmata 1991
c054b6b… lmata 1992 callee_node = _text_node(b"helper", "identifier")
c054b6b… lmata 1993 call_node = MockNode("call", children=[callee_node])
c054b6b… lmata 1994 call_node.set_field("method", callee_node)
c054b6b… lmata 1995 body_node = MockNode("body_statement", children=[call_node])
c054b6b… lmata 1996 fn_node = MockNode("method", children=[body_node])
c054b6b… lmata 1997
c054b6b… lmata 1998 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 1999 parser._extract_calls(fn_node, b"def foo; helper; end", "f.rb", "foo", NodeLabel.Function, store, stats)
c054b6b… lmata 2000 store.create_edge.assert_called()
c054b6b… lmata 2001
c054b6b… lmata 2002
c054b6b… lmata 2003 # ===========================================================================
c054b6b… lmata 2004 # navegador.ingestion.cpp — extra branches (73% → target ~90%)
c054b6b… lmata 2005 # ===========================================================================
c054b6b… lmata 2006
c054b6b… lmata 2007
c054b6b… lmata 2008 class TestCppParserBranches:
c054b6b… lmata 2009 def _make_parser(self):
c054b6b… lmata 2010 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2011 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2012 return CppParser()
c054b6b… lmata 2013
c054b6b… lmata 2014 def test_handle_class_with_inheritance(self):
c054b6b… lmata 2015 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2016 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2017
c054b6b… lmata 2018 parser = CppParser()
c054b6b… lmata 2019 store = _make_store()
c054b6b… lmata 2020
c054b6b… lmata 2021 name_node = _text_node(b"MyClass", "type_identifier")
c054b6b… lmata 2022 parent_node = _text_node(b"BaseClass", "type_identifier")
c054b6b… lmata 2023 base_clause_node = MockNode("base_class_clause", children=[parent_node])
c054b6b… lmata 2024 class_node = MockNode("class_specifier", children=[name_node, base_clause_node])
c054b6b… lmata 2025 class_node.set_field("name", name_node)
c054b6b… lmata 2026 class_node.set_field("base_clause", base_clause_node)
c054b6b… lmata 2027
c054b6b… lmata 2028 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2029 parser._handle_class(class_node, b"class MyClass : public BaseClass {};", "f.cpp", store, stats)
c054b6b… lmata 2030 assert stats["classes"] == 1
c054b6b… lmata 2031 assert stats["edges"] >= 2
c054b6b… lmata 2032
c054b6b… lmata 2033 def test_handle_class_no_name(self):
c054b6b… lmata 2034 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2035 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2036
c054b6b… lmata 2037 parser = CppParser()
c054b6b… lmata 2038 store = _make_store()
c054b6b… lmata 2039 anon_class = MockNode("class_specifier")
c054b6b… lmata 2040 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2041 parser._handle_class(anon_class, b"struct {};", "f.cpp", store, stats)
c054b6b… lmata 2042 assert stats["classes"] == 0
c054b6b… lmata 2043
c054b6b… lmata 2044 def test_handle_function_with_class_name(self):
c054b6b… lmata 2045 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2046 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2047
c054b6b… lmata 2048 parser = CppParser()
c054b6b… lmata 2049 store = _make_store()
c054b6b… lmata 2050
c054b6b… lmata 2051 fn_name_node = _text_node(b"myMethod", "identifier")
c054b6b… lmata 2052 declarator = MockNode("function_declarator", children=[fn_name_node])
c054b6b… lmata 2053 declarator.set_field("declarator", fn_name_node)
c054b6b… lmata 2054 body = MockNode("compound_statement")
c054b6b… lmata 2055 fn_node = MockNode("function_definition", children=[declarator, body])
c054b6b… lmata 2056 fn_node.set_field("declarator", declarator)
c054b6b… lmata 2057 fn_node.set_field("body", body)
c054b6b… lmata 2058
c054b6b… lmata 2059 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2060 parser._handle_function(fn_node, b"void myMethod() {}", "f.cpp", store, stats, class_name="MyClass")
c054b6b… lmata 2061 assert stats["functions"] == 1
c054b6b… lmata 2062 from navegador.graph.schema import NodeLabel
c054b6b… lmata 2063 assert store.create_node.call_args[0][0] == NodeLabel.Method
c054b6b… lmata 2064
c054b6b… lmata 2065 def test_extract_function_name_qualified(self):
c054b6b… lmata 2066 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2067 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2068
c054b6b… lmata 2069 parser = CppParser()
c054b6b… lmata 2070 src = b"method"
c054b6b… lmata 2071 name_node = MockNode("identifier", start_byte=0, end_byte=len(src))
c054b6b… lmata 2072 qualified = MockNode("qualified_identifier", children=[name_node])
c054b6b… lmata 2073 qualified.set_field("name", name_node)
c054b6b… lmata 2074 result = parser._extract_function_name(qualified, src)
c054b6b… lmata 2075 assert result == "method"
c054b6b… lmata 2076
c054b6b… lmata 2077 def test_extract_function_name_qualified_fallback(self):
c054b6b… lmata 2078 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2079 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2080
c054b6b… lmata 2081 parser = CppParser()
c054b6b… lmata 2082 src = b"method"
c054b6b… lmata 2083 id_node = MockNode("identifier", start_byte=0, end_byte=len(src))
c054b6b… lmata 2084 qualified = MockNode("qualified_identifier", children=[id_node])
c054b6b… lmata 2085 # No name field → fallback to last identifier child
c054b6b… lmata 2086 result = parser._extract_function_name(qualified, src)
c054b6b… lmata 2087 assert result == "method"
c054b6b… lmata 2088
c054b6b… lmata 2089 def test_extract_function_name_pointer_declarator(self):
c054b6b… lmata 2090 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2091 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2092
c054b6b… lmata 2093 parser = CppParser()
c054b6b… lmata 2094 src = b"fp"
c054b6b… lmata 2095 inner = MockNode("identifier", start_byte=0, end_byte=len(src))
c054b6b… lmata 2096 ptr_decl = MockNode("pointer_declarator", children=[inner])
c054b6b… lmata 2097 ptr_decl.set_field("declarator", inner)
c054b6b… lmata 2098 result = parser._extract_function_name(ptr_decl, src)
c054b6b… lmata 2099 assert result == "fp"
c054b6b… lmata 2100
c054b6b… lmata 2101 def test_extract_function_name_fallback_child(self):
c054b6b… lmata 2102 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2103 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2104
c054b6b… lmata 2105 parser = CppParser()
c054b6b… lmata 2106 src = b"fallbackFn"
c054b6b… lmata 2107 id_node = MockNode("identifier", start_byte=0, end_byte=len(src))
c054b6b… lmata 2108 unknown_decl = MockNode("unknown_declarator", children=[id_node])
c054b6b… lmata 2109 result = parser._extract_function_name(unknown_decl, src)
c054b6b… lmata 2110 assert result == "fallbackFn"
c054b6b… lmata 2111
c054b6b… lmata 2112 def test_extract_function_name_none(self):
c054b6b… lmata 2113 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2114 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2115
c054b6b… lmata 2116 parser = CppParser()
c054b6b… lmata 2117 assert parser._extract_function_name(None, b"") is None
c054b6b… lmata 2118
c054b6b… lmata 2119 def test_handle_include(self):
c054b6b… lmata 2120 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2121 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2122
c054b6b… lmata 2123 parser = CppParser()
c054b6b… lmata 2124 store = _make_store()
c054b6b… lmata 2125
c054b6b… lmata 2126 path_node = _text_node(b'"vector"', "string_literal")
c054b6b… lmata 2127 include_node = MockNode("preproc_include", children=[path_node])
c054b6b… lmata 2128 include_node.set_field("path", path_node)
c054b6b… lmata 2129
c054b6b… lmata 2130 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2131 parser._handle_include(include_node, b'#include "vector"', "f.cpp", store, stats)
c054b6b… lmata 2132 store.create_node.assert_called_once()
c054b6b… lmata 2133
c054b6b… lmata 2134 def test_namespace_recurse(self):
c054b6b… lmata 2135 with _mock_ts("tree_sitter_cpp"):
c054b6b… lmata 2136 from navegador.ingestion.cpp import CppParser
c054b6b… lmata 2137
c054b6b… lmata 2138 parser = CppParser()
c054b6b… lmata 2139 store = _make_store()
c054b6b… lmata 2140
c054b6b… lmata 2141 # namespace body contains a function
c054b6b… lmata 2142 fn_name = _text_node(b"inner_fn", "identifier")
c054b6b… lmata 2143 fn_decl = MockNode("function_declarator", children=[fn_name])
c054b6b… lmata 2144 fn_decl.set_field("declarator", fn_name)
c054b6b… lmata 2145 fn_body = MockNode("compound_statement")
c054b6b… lmata 2146 fn_def = MockNode("function_definition", children=[fn_decl, fn_body])
c054b6b… lmata 2147 fn_def.set_field("declarator", fn_decl)
c054b6b… lmata 2148
c054b6b… lmata 2149 decl_list = MockNode("declaration_list", children=[fn_def])
c054b6b… lmata 2150 ns_node = MockNode("namespace_definition", children=[decl_list])
c054b6b… lmata 2151
c054b6b… lmata 2152 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2153 parser._walk(ns_node, b"namespace ns { void inner_fn(){} }", "f.cpp", store, stats, class_name=None)
c054b6b… lmata 2154 assert stats["functions"] == 1
c054b6b… lmata 2155
c054b6b… lmata 2156
c054b6b… lmata 2157 # ===========================================================================
c054b6b… lmata 2158 # navegador.ingestion.csharp — extra branches (79% → target ~90%)
c054b6b… lmata 2159 # ===========================================================================
c054b6b… lmata 2160
c054b6b… lmata 2161
c054b6b… lmata 2162 class TestCSharpParserBranches:
c054b6b… lmata 2163 def test_handle_class_with_bases(self):
c054b6b… lmata 2164 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2165 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2166
c054b6b… lmata 2167 parser = CSharpParser()
c054b6b… lmata 2168 store = _make_store()
c054b6b… lmata 2169
c054b6b… lmata 2170 name_node = _text_node(b"MyService", "identifier")
c054b6b… lmata 2171 base_id = _text_node(b"IService", "identifier")
c054b6b… lmata 2172 bases_node = MockNode("base_list", children=[base_id])
c054b6b… lmata 2173 class_node = MockNode("class_declaration", children=[name_node, bases_node])
c054b6b… lmata 2174 class_node.set_field("name", name_node)
c054b6b… lmata 2175 class_node.set_field("bases", bases_node)
c054b6b… lmata 2176
c054b6b… lmata 2177 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2178 parser._handle_class(class_node, b"class MyService : IService {}", "f.cs", store, stats)
c054b6b… lmata 2179 assert stats["classes"] == 1
c054b6b… lmata 2180 assert stats["edges"] >= 2
c054b6b… lmata 2181
c054b6b… lmata 2182 def test_handle_class_no_name(self):
c054b6b… lmata 2183 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2184 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2185
c054b6b… lmata 2186 parser = CSharpParser()
c054b6b… lmata 2187 store = _make_store()
c054b6b… lmata 2188 anon = MockNode("class_declaration")
c054b6b… lmata 2189 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2190 parser._handle_class(anon, b"", "f.cs", store, stats)
c054b6b… lmata 2191 assert stats["classes"] == 0
c054b6b… lmata 2192
c054b6b… lmata 2193 def test_handle_method_standalone(self):
c054b6b… lmata 2194 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2195 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2196
c054b6b… lmata 2197 parser = CSharpParser()
c054b6b… lmata 2198 store = _make_store()
c054b6b… lmata 2199
c054b6b… lmata 2200 name_node = _text_node(b"DoWork", "identifier")
c054b6b… lmata 2201 fn_node = MockNode("method_declaration", children=[name_node])
c054b6b… lmata 2202 fn_node.set_field("name", name_node)
c054b6b… lmata 2203
c054b6b… lmata 2204 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2205 parser._handle_method(fn_node, b"void DoWork() {}", "f.cs", store, stats, class_name=None)
c054b6b… lmata 2206 assert stats["functions"] == 1
c054b6b… lmata 2207
c054b6b… lmata 2208 def test_handle_method_no_name(self):
c054b6b… lmata 2209 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2210 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2211
c054b6b… lmata 2212 parser = CSharpParser()
c054b6b… lmata 2213 store = _make_store()
c054b6b… lmata 2214 anon = MockNode("method_declaration")
c054b6b… lmata 2215 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2216 parser._handle_method(anon, b"", "f.cs", store, stats, class_name=None)
c054b6b… lmata 2217 assert stats["functions"] == 0
c054b6b… lmata 2218
c054b6b… lmata 2219 def test_handle_using(self):
c054b6b… lmata 2220 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2221 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2222
c054b6b… lmata 2223 parser = CSharpParser()
c054b6b… lmata 2224 store = _make_store()
c054b6b… lmata 2225
c054b6b… lmata 2226 src = b"using System.Collections.Generic;"
c054b6b… lmata 2227 using_node = MockNode(
c054b6b… lmata 2228 "using_directive",
c054b6b… lmata 2229 start_byte=0,
c054b6b… lmata 2230 end_byte=len(src),
c054b6b… lmata 2231 start_point=(0, 0),
c054b6b… lmata 2232 end_point=(0, len(src)),
c054b6b… lmata 2233 )
c054b6b… lmata 2234 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2235 parser._handle_using(using_node, src, "f.cs", store, stats)
c054b6b… lmata 2236 store.create_node.assert_called_once()
c054b6b… lmata 2237
c054b6b… lmata 2238 def test_extract_calls(self):
c054b6b… lmata 2239 with _mock_ts("tree_sitter_c_sharp"):
c054b6b… lmata 2240 from navegador.ingestion.csharp import CSharpParser
c054b6b… lmata 2241 from navegador.graph.schema import NodeLabel
c054b6b… lmata 2242
c054b6b… lmata 2243 parser = CSharpParser()
c054b6b… lmata 2244 store = _make_store()
c054b6b… lmata 2245
c054b6b… lmata 2246 callee_node = _text_node(b"DoWork", "identifier")
c054b6b… lmata 2247 invoke_node = MockNode("invocation_expression", children=[callee_node])
c054b6b… lmata 2248 invoke_node.set_field("function", callee_node)
c054b6b… lmata 2249 block_node = MockNode("block", children=[invoke_node])
c054b6b… lmata 2250 fn_node = MockNode("method_declaration", children=[block_node])
c054b6b… lmata 2251 fn_node.set_field("body", block_node)
c054b6b… lmata 2252
c054b6b… lmata 2253 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2254 parser._extract_calls(fn_node, b"DoWork()", "f.cs", "Run", NodeLabel.Method, store, stats)
c054b6b… lmata 2255 store.create_edge.assert_called()
c054b6b… lmata 2256
c054b6b… lmata 2257
c054b6b… lmata 2258 # ===========================================================================
c054b6b… lmata 2259 # navegador.ingestion.kotlin — extra branches (79% → target ~90%)
c054b6b… lmata 2260 # ===========================================================================
c054b6b… lmata 2261
c054b6b… lmata 2262
c054b6b… lmata 2263 class TestKotlinParserBranches:
c054b6b… lmata 2264 def test_handle_class_no_name(self):
c054b6b… lmata 2265 with _mock_ts("tree_sitter_kotlin"):
c054b6b… lmata 2266 from navegador.ingestion.kotlin import KotlinParser
c054b6b… lmata 2267
c054b6b… lmata 2268 parser = KotlinParser()
c054b6b… lmata 2269 store = _make_store()
c054b6b… lmata 2270 anon = MockNode("class_declaration")
c054b6b… lmata 2271 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2272 parser._handle_class(anon, b"", "f.kt", store, stats)
c054b6b… lmata 2273 assert stats["classes"] == 0
c054b6b… lmata 2274
c054b6b… lmata 2275 def test_handle_class_with_body(self):
c054b6b… lmata 2276 with _mock_ts("tree_sitter_kotlin"):
c054b6b… lmata 2277 from navegador.ingestion.kotlin import KotlinParser
c054b6b… lmata 2278
c054b6b… lmata 2279 parser = KotlinParser()
c054b6b… lmata 2280 store = _make_store()
c054b6b… lmata 2281
c054b6b… lmata 2282 name_node = _text_node(b"MyClass", "simple_identifier")
c054b6b… lmata 2283 fn_name = _text_node(b"doSomething", "simple_identifier")
c054b6b… lmata 2284 fn_node = MockNode("function_declaration", children=[fn_name])
c054b6b… lmata 2285 fn_node.set_field("name", fn_name)
c054b6b… lmata 2286 body_node = MockNode("class_body", children=[fn_node])
c054b6b… lmata 2287 class_node = MockNode("class_declaration", children=[name_node, body_node])
c054b6b… lmata 2288 class_node.set_field("name", name_node)
c054b6b… lmata 2289 class_node.set_field("body", body_node)
c054b6b… lmata 2290
c054b6b… lmata 2291 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2292 src = b"class MyClass { fun doSomething() {} }"
c054b6b… lmata 2293 parser._handle_class(class_node, src, "f.kt", store, stats)
c054b6b… lmata 2294 assert stats["classes"] == 1
c054b6b… lmata 2295 assert stats["functions"] == 1
c054b6b… lmata 2296
c054b6b… lmata 2297 def test_handle_function_no_name(self):
c054b6b… lmata 2298 with _mock_ts("tree_sitter_kotlin"):
c054b6b… lmata 2299 from navegador.ingestion.kotlin import KotlinParser
c054b6b… lmata 2300
c054b6b… lmata 2301 parser = KotlinParser()
c054b6b… lmata 2302 store = _make_store()
c054b6b… lmata 2303 anon = MockNode("function_declaration")
c054b6b… lmata 2304 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2305 parser._handle_function(anon, b"", "f.kt", store, stats, class_name=None)
c054b6b… lmata 2306 assert stats["functions"] == 0
c054b6b… lmata 2307
c054b6b… lmata 2308 def test_handle_import(self):
c054b6b… lmata 2309 with _mock_ts("tree_sitter_kotlin"):
c054b6b… lmata 2310 from navegador.ingestion.kotlin import KotlinParser
c054b6b… lmata 2311
c054b6b… lmata 2312 parser = KotlinParser()
c054b6b… lmata 2313 store = _make_store()
c054b6b… lmata 2314
c054b6b… lmata 2315 src = b"import kotlin.collections.List"
c054b6b… lmata 2316 import_node = MockNode(
c054b6b… lmata 2317 "import_header",
c054b6b… lmata 2318 start_byte=0,
c054b6b… lmata 2319 end_byte=len(src),
c054b6b… lmata 2320 start_point=(0, 0),
c054b6b… lmata 2321 end_point=(0, len(src)),
c054b6b… lmata 2322 )
c054b6b… lmata 2323 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2324 parser._handle_import(import_node, src, "f.kt", store, stats)
c054b6b… lmata 2325 store.create_node.assert_called_once()
c054b6b… lmata 2326
c054b6b… lmata 2327
c054b6b… lmata 2328 # ===========================================================================
c054b6b… lmata 2329 # navegador.ingestion.php — extra branches (79% → target ~90%)
c054b6b… lmata 2330 # ===========================================================================
c054b6b… lmata 2331
c054b6b… lmata 2332
c054b6b… lmata 2333 class TestPHPParserBranches:
c054b6b… lmata 2334 def test_handle_class_no_name(self):
c054b6b… lmata 2335 with _mock_ts("tree_sitter_php"):
c054b6b… lmata 2336 from navegador.ingestion.php import PHPParser
c054b6b… lmata 2337
c054b6b… lmata 2338 parser = PHPParser()
c054b6b… lmata 2339 store = _make_store()
c054b6b… lmata 2340 anon = MockNode("class_declaration")
c054b6b… lmata 2341 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2342 parser._handle_class(anon, b"", "f.php", store, stats)
c054b6b… lmata 2343 assert stats["classes"] == 0
c054b6b… lmata 2344
c054b6b… lmata 2345 def test_handle_function_no_name(self):
c054b6b… lmata 2346 with _mock_ts("tree_sitter_php"):
c054b6b… lmata 2347 from navegador.ingestion.php import PHPParser
c054b6b… lmata 2348
c054b6b… lmata 2349 parser = PHPParser()
c054b6b… lmata 2350 store = _make_store()
c054b6b… lmata 2351 anon = MockNode("function_definition")
c054b6b… lmata 2352 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2353 parser._handle_function(anon, b"", "f.php", store, stats, class_name=None)
c054b6b… lmata 2354 assert stats["functions"] == 0
c054b6b… lmata 2355
c054b6b… lmata 2356 def test_handle_class_with_body_methods(self):
c054b6b… lmata 2357 with _mock_ts("tree_sitter_php"):
c054b6b… lmata 2358 from navegador.ingestion.php import PHPParser
c054b6b… lmata 2359
c054b6b… lmata 2360 parser = PHPParser()
c054b6b… lmata 2361 store = _make_store()
c054b6b… lmata 2362
c054b6b… lmata 2363 name_node = _text_node(b"MyController", "name")
c054b6b… lmata 2364 fn_name = _text_node(b"index", "name")
c054b6b… lmata 2365 method_node = MockNode("method_declaration", children=[fn_name])
c054b6b… lmata 2366 method_node.set_field("name", fn_name)
c054b6b… lmata 2367 body_node = MockNode("declaration_list", children=[method_node])
c054b6b… lmata 2368 class_node = MockNode("class_declaration", children=[name_node, body_node])
c054b6b… lmata 2369 class_node.set_field("name", name_node)
c054b6b… lmata 2370 class_node.set_field("body", body_node)
c054b6b… lmata 2371
c054b6b… lmata 2372 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2373 src = b"class MyController { public function index() {} }"
c054b6b… lmata 2374 parser._handle_class(class_node, src, "f.php", store, stats)
c054b6b… lmata 2375 assert stats["classes"] == 1
c054b6b… lmata 2376 assert stats["functions"] >= 1
c054b6b… lmata 2377
c054b6b… lmata 2378
c054b6b… lmata 2379 # ===========================================================================
c054b6b… lmata 2380 # navegador.ingestion.swift — extra branches (79% → target ~90%)
c054b6b… lmata 2381 # ===========================================================================
c054b6b… lmata 2382
c054b6b… lmata 2383
c054b6b… lmata 2384 class TestSwiftParserBranches:
c054b6b… lmata 2385 def test_handle_class_no_name(self):
c054b6b… lmata 2386 with _mock_ts("tree_sitter_swift"):
c054b6b… lmata 2387 from navegador.ingestion.swift import SwiftParser
c054b6b… lmata 2388
c054b6b… lmata 2389 parser = SwiftParser()
c054b6b… lmata 2390 store = _make_store()
c054b6b… lmata 2391 anon = MockNode("class_declaration")
c054b6b… lmata 2392 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2393 parser._handle_class(anon, b"", "f.swift", store, stats)
c054b6b… lmata 2394 assert stats["classes"] == 0
c054b6b… lmata 2395
c054b6b… lmata 2396 def test_handle_function_no_name(self):
c054b6b… lmata 2397 with _mock_ts("tree_sitter_swift"):
c054b6b… lmata 2398 from navegador.ingestion.swift import SwiftParser
c054b6b… lmata 2399
c054b6b… lmata 2400 parser = SwiftParser()
c054b6b… lmata 2401 store = _make_store()
c054b6b… lmata 2402 anon = MockNode("function_declaration")
c054b6b… lmata 2403 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2404 parser._handle_function(anon, b"", "f.swift", store, stats, class_name=None)
c054b6b… lmata 2405 assert stats["functions"] == 0
c054b6b… lmata 2406
c054b6b… lmata 2407 def test_handle_import(self):
c054b6b… lmata 2408 with _mock_ts("tree_sitter_swift"):
c054b6b… lmata 2409 from navegador.ingestion.swift import SwiftParser
c054b6b… lmata 2410
c054b6b… lmata 2411 parser = SwiftParser()
c054b6b… lmata 2412 store = _make_store()
c054b6b… lmata 2413
c054b6b… lmata 2414 src = b"import Foundation"
c054b6b… lmata 2415 import_node = MockNode(
c054b6b… lmata 2416 "import_declaration",
c054b6b… lmata 2417 start_byte=0,
c054b6b… lmata 2418 end_byte=len(src),
c054b6b… lmata 2419 start_point=(0, 0),
c054b6b… lmata 2420 end_point=(0, len(src)),
c054b6b… lmata 2421 )
c054b6b… lmata 2422 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2423 parser._handle_import(import_node, src, "f.swift", store, stats)
c054b6b… lmata 2424 store.create_node.assert_called_once()
c054b6b… lmata 2425
c054b6b… lmata 2426 def test_handle_class_with_body(self):
c054b6b… lmata 2427 with _mock_ts("tree_sitter_swift"):
c054b6b… lmata 2428 from navegador.ingestion.swift import SwiftParser
c054b6b… lmata 2429
c054b6b… lmata 2430 parser = SwiftParser()
c054b6b… lmata 2431 store = _make_store()
c054b6b… lmata 2432
c054b6b… lmata 2433 name_node = _text_node(b"MyView", "type_identifier")
c054b6b… lmata 2434 fn_name = _text_node(b"body", "simple_identifier")
c054b6b… lmata 2435 fn_node = MockNode("function_declaration", children=[fn_name])
c054b6b… lmata 2436 fn_node.set_field("name", fn_name)
c054b6b… lmata 2437 body_node = MockNode("class_body", children=[fn_node])
c054b6b… lmata 2438 class_node = MockNode("class_declaration", children=[name_node, body_node])
c054b6b… lmata 2439 class_node.set_field("name", name_node)
c054b6b… lmata 2440 class_node.set_field("body", body_node)
c054b6b… lmata 2441
c054b6b… lmata 2442 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2443 src = b"class MyView { func body() {} }"
c054b6b… lmata 2444 parser._handle_class(class_node, src, "f.swift", store, stats)
c054b6b… lmata 2445 assert stats["classes"] == 1
c054b6b… lmata 2446 assert stats["functions"] == 1
c054b6b… lmata 2447
c054b6b… lmata 2448
c054b6b… lmata 2449 # ===========================================================================
c054b6b… lmata 2450 # navegador.ingestion.c — extra branches (76% → target ~90%)
c054b6b… lmata 2451 # ===========================================================================
c054b6b… lmata 2452
c054b6b… lmata 2453
c054b6b… lmata 2454 class TestCParserBranches:
c054b6b… lmata 2455 def test_handle_function(self):
c054b6b… lmata 2456 with _mock_ts("tree_sitter_c"):
c054b6b… lmata 2457 from navegador.ingestion.c import CParser
c054b6b… lmata 2458
c054b6b… lmata 2459 parser = CParser()
c054b6b… lmata 2460 store = _make_store()
c054b6b… lmata 2461
c054b6b… lmata 2462 fn_name_node = _text_node(b"myFunc", "identifier")
c054b6b… lmata 2463 declarator = MockNode("function_declarator", children=[fn_name_node])
c054b6b… lmata 2464 declarator.set_field("declarator", fn_name_node)
c054b6b… lmata 2465 body = MockNode("compound_statement")
c054b6b… lmata 2466 fn_node = MockNode("function_definition", children=[declarator, body])
c054b6b… lmata 2467 fn_node.set_field("declarator", declarator)
c054b6b… lmata 2468 fn_node.set_field("body", body)
c054b6b… lmata 2469
c054b6b… lmata 2470 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2471 parser._handle_function(fn_node, b"void myFunc() {}", "f.c", store, stats)
c054b6b… lmata 2472 assert stats["functions"] == 1
c054b6b… lmata 2473
c054b6b… lmata 2474 def test_handle_struct(self):
c054b6b… lmata 2475 with _mock_ts("tree_sitter_c"):
c054b6b… lmata 2476 from navegador.ingestion.c import CParser
c054b6b… lmata 2477
c054b6b… lmata 2478 parser = CParser()
c054b6b… lmata 2479 store = _make_store()
c054b6b… lmata 2480
c054b6b… lmata 2481 name_node = _text_node(b"Point", "type_identifier")
c054b6b… lmata 2482 struct_node = MockNode("struct_specifier", children=[name_node])
c054b6b… lmata 2483 struct_node.set_field("name", name_node)
c054b6b… lmata 2484
c054b6b… lmata 2485 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2486 parser._handle_struct(struct_node, b"struct Point {};", "f.c", store, stats)
c054b6b… lmata 2487 assert stats["classes"] == 1
c054b6b… lmata 2488
c054b6b… lmata 2489 def test_handle_struct_no_name(self):
c054b6b… lmata 2490 with _mock_ts("tree_sitter_c"):
c054b6b… lmata 2491 from navegador.ingestion.c import CParser
c054b6b… lmata 2492
c054b6b… lmata 2493 parser = CParser()
c054b6b… lmata 2494 store = _make_store()
c054b6b… lmata 2495 anon = MockNode("struct_specifier")
c054b6b… lmata 2496 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2497 parser._handle_struct(anon, b"", "f.c", store, stats)
c054b6b… lmata 2498 assert stats["classes"] == 0
c054b6b… lmata 2499
c054b6b… lmata 2500 def test_handle_include(self):
c054b6b… lmata 2501 with _mock_ts("tree_sitter_c"):
c054b6b… lmata 2502 from navegador.ingestion.c import CParser
c054b6b… lmata 2503
c054b6b… lmata 2504 parser = CParser()
c054b6b… lmata 2505 store = _make_store()
c054b6b… lmata 2506
c054b6b… lmata 2507 path_node = _text_node(b"<stdio.h>", "system_lib_string")
c054b6b… lmata 2508 include_node = MockNode("preproc_include", children=[path_node])
c054b6b… lmata 2509 include_node.set_field("path", path_node)
c054b6b… lmata 2510
c054b6b… lmata 2511 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2512 parser._handle_include(include_node, b"#include <stdio.h>", "f.c", store, stats)
c054b6b… lmata 2513 store.create_node.assert_called_once()
c054b6b… lmata 2514
c054b6b… lmata 2515 def test_handle_function_no_name(self):
c054b6b… lmata 2516 with _mock_ts("tree_sitter_c"):
c054b6b… lmata 2517 from navegador.ingestion.c import CParser
c054b6b… lmata 2518
c054b6b… lmata 2519 parser = CParser()
c054b6b… lmata 2520 store = _make_store()
c054b6b… lmata 2521
c054b6b… lmata 2522 fn_node = MockNode("function_definition")
c054b6b… lmata 2523 # No declarator field → _extract_function_name returns None
c054b6b… lmata 2524 stats = {"functions": 0, "classes": 0, "edges": 0}
c054b6b… lmata 2525 parser._handle_function(fn_node, b"", "f.c", store, stats)
c054b6b… lmata 2526 assert stats["functions"] == 0

Keyboard Shortcuts

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