Navegador

navegador / tests / test_puppet_parser.py
Blame History Raw 510 lines
1
"""Tests for navegador.ingestion.puppet — PuppetParser internal methods."""
2
3
from unittest.mock import MagicMock, patch
4
5
import pytest
6
7
from navegador.graph.schema import NodeLabel
8
9
10
class MockNode:
11
_id_counter = 0
12
13
def __init__(
14
self,
15
type_: str,
16
text: bytes = b"",
17
children: list = None,
18
start_byte: int = 0,
19
end_byte: int = 0,
20
start_point: tuple = (0, 0),
21
end_point: tuple = (0, 0),
22
parent=None,
23
):
24
MockNode._id_counter += 1
25
self.id = MockNode._id_counter
26
self.type = type_
27
self._text = text
28
self.children = children or []
29
self.start_byte = start_byte
30
self.end_byte = end_byte
31
self.start_point = start_point
32
self.end_point = end_point
33
self.parent = parent
34
self._fields: dict = {}
35
for child in self.children:
36
child.parent = self
37
38
def child_by_field_name(self, name: str):
39
return self._fields.get(name)
40
41
def set_field(self, name: str, node):
42
self._fields[name] = node
43
node.parent = self
44
return self
45
46
47
def _text_node(text: bytes, type_: str = "identifier") -> MockNode:
48
return MockNode(type_, text, start_byte=0, end_byte=len(text))
49
50
51
def _make_store():
52
store = MagicMock()
53
store.query.return_value = MagicMock(result_set=[])
54
return store
55
56
57
def _make_parser():
58
from navegador.ingestion.puppet import PuppetParser
59
60
parser = PuppetParser.__new__(PuppetParser)
61
parser._parser = MagicMock()
62
return parser
63
64
65
class TestPuppetGetLanguage:
66
def test_raises_when_not_installed(self):
67
from navegador.ingestion.puppet import _get_puppet_language
68
69
with patch.dict(
70
"sys.modules",
71
{
72
"tree_sitter_puppet": None,
73
"tree_sitter": None,
74
},
75
):
76
with pytest.raises(ImportError, match="tree-sitter-puppet"):
77
_get_puppet_language()
78
79
def test_returns_language_object(self):
80
from navegador.ingestion.puppet import _get_puppet_language
81
82
mock_tspuppet = MagicMock()
83
mock_ts = MagicMock()
84
with patch.dict(
85
"sys.modules",
86
{
87
"tree_sitter_puppet": mock_tspuppet,
88
"tree_sitter": mock_ts,
89
},
90
):
91
result = _get_puppet_language()
92
assert result is mock_ts.Language.return_value
93
94
95
class TestPuppetHandleClass:
96
def test_creates_class_with_puppet_class_semantic_type(self):
97
parser = _make_parser()
98
store = _make_store()
99
source = b"nginx"
100
class_ident = MockNode(
101
"class_identifier",
102
children=[
103
MockNode(
104
"identifier",
105
start_byte=0,
106
end_byte=5,
107
),
108
],
109
start_byte=0,
110
end_byte=5,
111
)
112
node = MockNode(
113
"class_definition",
114
children=[class_ident],
115
start_point=(0, 0),
116
end_point=(5, 1),
117
)
118
stats = {"functions": 0, "classes": 0, "edges": 0}
119
parser._handle_class(node, source, "nginx.pp", store, stats)
120
assert stats["classes"] == 1
121
assert stats["edges"] == 1
122
label = store.create_node.call_args[0][0]
123
props = store.create_node.call_args[0][1]
124
assert label == NodeLabel.Class
125
assert props["name"] == "nginx"
126
assert props["semantic_type"] == "puppet_class"
127
128
def test_skips_when_no_class_identifier(self):
129
parser = _make_parser()
130
store = _make_store()
131
node = MockNode(
132
"class_definition",
133
children=[],
134
start_point=(0, 0),
135
end_point=(0, 5),
136
)
137
stats = {"functions": 0, "classes": 0, "edges": 0}
138
parser._handle_class(node, b"", "test.pp", store, stats)
139
assert stats["classes"] == 0
140
store.create_node.assert_not_called()
141
142
143
class TestPuppetHandleDefinedType:
144
def test_creates_class_with_puppet_defined_type(self):
145
parser = _make_parser()
146
store = _make_store()
147
source = b"nginx::vhost"
148
class_ident = MockNode(
149
"class_identifier",
150
children=[
151
MockNode(
152
"identifier",
153
start_byte=0,
154
end_byte=5,
155
),
156
MockNode(
157
"identifier",
158
start_byte=7,
159
end_byte=12,
160
),
161
],
162
start_byte=0,
163
end_byte=12,
164
)
165
node = MockNode(
166
"defined_resource_type",
167
children=[class_ident],
168
start_point=(0, 0),
169
end_point=(3, 1),
170
)
171
stats = {"functions": 0, "classes": 0, "edges": 0}
172
parser._handle_defined_type(node, source, "vhost.pp", store, stats)
173
assert stats["classes"] == 1
174
label = store.create_node.call_args[0][0]
175
props = store.create_node.call_args[0][1]
176
assert label == NodeLabel.Class
177
assert props["name"] == "nginx::vhost"
178
assert props["semantic_type"] == "puppet_defined_type"
179
180
181
class TestPuppetHandleNode:
182
def test_creates_class_with_puppet_node(self):
183
parser = _make_parser()
184
store = _make_store()
185
source = b"'webserver'"
186
string_node = MockNode(
187
"string",
188
start_byte=0,
189
end_byte=11,
190
)
191
node_name = MockNode(
192
"node_name",
193
children=[string_node],
194
)
195
node = MockNode(
196
"node_definition",
197
children=[node_name],
198
start_point=(0, 0),
199
end_point=(3, 1),
200
)
201
stats = {"functions": 0, "classes": 0, "edges": 0}
202
parser._handle_node(node, source, "nodes.pp", store, stats)
203
assert stats["classes"] == 1
204
label = store.create_node.call_args[0][0]
205
props = store.create_node.call_args[0][1]
206
assert label == NodeLabel.Class
207
assert props["name"] == "webserver"
208
assert props["semantic_type"] == "puppet_node"
209
210
def test_skips_when_no_node_name(self):
211
parser = _make_parser()
212
store = _make_store()
213
node = MockNode(
214
"node_definition",
215
children=[],
216
start_point=(0, 0),
217
end_point=(0, 5),
218
)
219
stats = {"functions": 0, "classes": 0, "edges": 0}
220
parser._handle_node(node, b"", "nodes.pp", store, stats)
221
assert stats["classes"] == 0
222
223
224
class TestPuppetHandleResource:
225
def test_creates_function_with_puppet_resource(self):
226
parser = _make_parser()
227
store = _make_store()
228
source = b"package 'nginx'"
229
ident = MockNode(
230
"identifier",
231
start_byte=0,
232
end_byte=7,
233
)
234
title = MockNode(
235
"string",
236
start_byte=8,
237
end_byte=15,
238
)
239
node = MockNode(
240
"resource_declaration",
241
children=[ident, title],
242
start_point=(1, 0),
243
end_point=(3, 1),
244
)
245
stats = {"functions": 0, "classes": 0, "edges": 0}
246
parser._handle_resource(node, source, "nginx.pp", "nginx", store, stats)
247
assert stats["functions"] == 1
248
assert stats["edges"] == 1
249
label = store.create_node.call_args[0][0]
250
props = store.create_node.call_args[0][1]
251
assert label == NodeLabel.Function
252
assert props["name"] == "package[nginx]"
253
assert props["semantic_type"] == "puppet_resource"
254
255
def test_skips_when_no_type_identifier(self):
256
parser = _make_parser()
257
store = _make_store()
258
node = MockNode(
259
"resource_declaration",
260
children=[],
261
start_point=(0, 0),
262
end_point=(0, 5),
263
)
264
stats = {"functions": 0, "classes": 0, "edges": 0}
265
parser._handle_resource(node, b"", "test.pp", "myclass", store, stats)
266
assert stats["functions"] == 0
267
268
269
class TestPuppetHandleInclude:
270
def test_creates_import_node(self):
271
parser = _make_parser()
272
store = _make_store()
273
source = b"stdlib"
274
class_ident = MockNode(
275
"class_identifier",
276
children=[
277
MockNode(
278
"identifier",
279
start_byte=0,
280
end_byte=6,
281
),
282
],
283
)
284
node = MockNode(
285
"include_statement",
286
children=[class_ident],
287
start_point=(0, 0),
288
end_point=(0, 14),
289
)
290
stats = {"functions": 0, "classes": 0, "edges": 0}
291
parser._handle_include(node, source, "init.pp", store, stats)
292
assert stats["edges"] == 1
293
label = store.create_node.call_args[0][0]
294
props = store.create_node.call_args[0][1]
295
assert label == NodeLabel.Import
296
assert props["name"] == "stdlib"
297
assert props["semantic_type"] == "puppet_include"
298
299
def test_skips_when_no_class_identifier(self):
300
parser = _make_parser()
301
store = _make_store()
302
node = MockNode(
303
"include_statement",
304
children=[],
305
start_point=(0, 0),
306
end_point=(0, 7),
307
)
308
stats = {"functions": 0, "classes": 0, "edges": 0}
309
parser._handle_include(node, b"", "init.pp", store, stats)
310
assert stats["edges"] == 0
311
store.create_node.assert_not_called()
312
313
314
class TestPuppetHandleParameters:
315
def test_creates_variable_nodes(self):
316
parser = _make_parser()
317
store = _make_store()
318
source = b"$port"
319
var_node = MockNode(
320
"variable",
321
start_byte=0,
322
end_byte=5,
323
)
324
param = MockNode(
325
"parameter",
326
children=[var_node],
327
start_point=(1, 2),
328
end_point=(1, 7),
329
)
330
param_list = MockNode(
331
"parameter_list",
332
children=[param],
333
)
334
class_ident = MockNode(
335
"class_identifier",
336
children=[
337
MockNode(
338
"identifier",
339
start_byte=0,
340
end_byte=5,
341
),
342
],
343
start_byte=0,
344
end_byte=5,
345
)
346
node = MockNode(
347
"class_definition",
348
children=[class_ident, param_list],
349
start_point=(0, 0),
350
end_point=(5, 1),
351
)
352
stats = {"functions": 0, "classes": 0, "edges": 0}
353
parser._extract_parameters(node, source, "nginx.pp", "nginx", store, stats)
354
store.create_node.assert_called_once()
355
label = store.create_node.call_args[0][0]
356
props = store.create_node.call_args[0][1]
357
assert label == NodeLabel.Variable
358
assert props["name"] == "port"
359
assert props["semantic_type"] == "puppet_parameter"
360
assert stats["edges"] == 1
361
362
def test_skips_param_without_variable(self):
363
parser = _make_parser()
364
store = _make_store()
365
param = MockNode(
366
"parameter",
367
children=[MockNode("type")],
368
start_point=(1, 2),
369
end_point=(1, 7),
370
)
371
param_list = MockNode(
372
"parameter_list",
373
children=[param],
374
)
375
node = MockNode(
376
"class_definition",
377
children=[param_list],
378
start_point=(0, 0),
379
end_point=(5, 1),
380
)
381
stats = {"functions": 0, "classes": 0, "edges": 0}
382
parser._extract_parameters(node, b"", "test.pp", "myclass", store, stats)
383
store.create_node.assert_not_called()
384
385
386
class TestPuppetWalkDispatch:
387
def test_walk_dispatches_class_definition(self):
388
parser = _make_parser()
389
store = _make_store()
390
source = b"nginx"
391
class_ident = MockNode(
392
"class_identifier",
393
children=[
394
MockNode(
395
"identifier",
396
start_byte=0,
397
end_byte=5,
398
),
399
],
400
)
401
class_def = MockNode(
402
"class_definition",
403
children=[class_ident],
404
start_point=(0, 0),
405
end_point=(5, 1),
406
)
407
root = MockNode("program", children=[class_def])
408
stats = {"functions": 0, "classes": 0, "edges": 0}
409
parser._walk(root, source, "nginx.pp", store, stats)
410
assert stats["classes"] == 1
411
412
def test_walk_dispatches_defined_resource_type(self):
413
parser = _make_parser()
414
store = _make_store()
415
source = b"vhost"
416
class_ident = MockNode(
417
"class_identifier",
418
children=[
419
MockNode(
420
"identifier",
421
start_byte=0,
422
end_byte=5,
423
),
424
],
425
)
426
define_node = MockNode(
427
"defined_resource_type",
428
children=[class_ident],
429
start_point=(0, 0),
430
end_point=(3, 1),
431
)
432
root = MockNode("program", children=[define_node])
433
stats = {"functions": 0, "classes": 0, "edges": 0}
434
parser._walk(root, source, "vhost.pp", store, stats)
435
assert stats["classes"] == 1
436
437
def test_walk_dispatches_node_definition(self):
438
parser = _make_parser()
439
store = _make_store()
440
source = b"'webserver'"
441
string_node = MockNode(
442
"string",
443
start_byte=0,
444
end_byte=11,
445
)
446
node_name = MockNode(
447
"node_name",
448
children=[string_node],
449
)
450
node_def = MockNode(
451
"node_definition",
452
children=[node_name],
453
start_point=(0, 0),
454
end_point=(3, 1),
455
)
456
root = MockNode("program", children=[node_def])
457
stats = {"functions": 0, "classes": 0, "edges": 0}
458
parser._walk(root, source, "nodes.pp", store, stats)
459
assert stats["classes"] == 1
460
461
def test_walk_dispatches_include_statement(self):
462
parser = _make_parser()
463
store = _make_store()
464
source = b"stdlib"
465
class_ident = MockNode(
466
"class_identifier",
467
children=[
468
MockNode(
469
"identifier",
470
start_byte=0,
471
end_byte=6,
472
),
473
],
474
)
475
include = MockNode(
476
"include_statement",
477
children=[class_ident],
478
start_point=(0, 0),
479
end_point=(0, 14),
480
)
481
root = MockNode("program", children=[include])
482
stats = {"functions": 0, "classes": 0, "edges": 0}
483
parser._walk(root, source, "init.pp", store, stats)
484
assert stats["edges"] == 1
485
486
487
class TestPuppetParseFile:
488
def test_creates_file_node(self):
489
import tempfile
490
from pathlib import Path
491
492
parser = _make_parser()
493
store = _make_store()
494
mock_tree = MagicMock()
495
mock_tree.root_node.type = "program"
496
mock_tree.root_node.children = []
497
parser._parser.parse.return_value = mock_tree
498
with tempfile.NamedTemporaryFile(suffix=".pp", delete=False) as f:
499
f.write(b"class nginx {}\n")
500
fpath = Path(f.name)
501
try:
502
parser.parse_file(fpath, fpath.parent, store)
503
store.create_node.assert_called_once()
504
label = store.create_node.call_args[0][0]
505
props = store.create_node.call_args[0][1]
506
assert label == NodeLabel.File
507
assert props["language"] == "puppet"
508
finally:
509
fpath.unlink()
510

Keyboard Shortcuts

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