|
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 |