Navegador

navegador / tests / test_v04_features.py
Blame History Raw 755 lines
1
"""
2
Tests for navegador v0.4 features:
3
#16 — Multi-repo support (MultiRepoManager)
4
#26 — Coordinated rename (SymbolRenamer)
5
#39 — CODEOWNERS integration (CodeownersIngester)
6
#40 — ADR ingestion (ADRIngester)
7
#41 — OpenAPI / GraphQL schema (APISchemaIngester)
8
"""
9
10
from __future__ import annotations
11
12
import json
13
import tempfile
14
from pathlib import Path
15
from unittest.mock import MagicMock, call, patch
16
17
import pytest
18
from click.testing import CliRunner
19
20
from navegador.cli.commands import main
21
22
23
# ── Shared helpers ────────────────────────────────────────────────────────────
24
25
26
def _mock_store():
27
store = MagicMock()
28
store.query.return_value = MagicMock(result_set=[])
29
return store
30
31
32
def _write(path: Path, content: str) -> None:
33
path.parent.mkdir(parents=True, exist_ok=True)
34
path.write_text(content, encoding="utf-8")
35
36
37
# ═════════════════════════════════════════════════════════════════════════════
38
# #16 — MultiRepoManager
39
# ═════════════════════════════════════════════════════════════════════════════
40
41
42
class TestMultiRepoManagerAddRepo:
43
def test_creates_repository_node(self, tmp_path):
44
from navegador.multirepo import MultiRepoManager
45
46
store = _mock_store()
47
mgr = MultiRepoManager(store)
48
mgr.add_repo("backend", str(tmp_path))
49
store.create_node.assert_called_once()
50
args = store.create_node.call_args[0]
51
assert args[0] == "Repository"
52
assert args[1]["name"] == "backend"
53
54
def test_resolves_path(self, tmp_path):
55
from navegador.multirepo import MultiRepoManager
56
57
store = _mock_store()
58
mgr = MultiRepoManager(store)
59
mgr.add_repo("x", str(tmp_path))
60
props = store.create_node.call_args[0][1]
61
assert Path(props["path"]).is_absolute()
62
63
64
class TestMultiRepoManagerListRepos:
65
def test_returns_empty_list_when_no_repos(self):
66
from navegador.multirepo import MultiRepoManager
67
68
store = _mock_store()
69
store.query.return_value = MagicMock(result_set=[])
70
mgr = MultiRepoManager(store)
71
assert mgr.list_repos() == []
72
73
def test_parses_result_set(self):
74
from navegador.multirepo import MultiRepoManager
75
76
store = _mock_store()
77
store.query.return_value = MagicMock(
78
result_set=[["backend", "/repos/backend"], ["frontend", "/repos/frontend"]]
79
)
80
mgr = MultiRepoManager(store)
81
repos = mgr.list_repos()
82
assert len(repos) == 2
83
assert repos[0] == {"name": "backend", "path": "/repos/backend"}
84
assert repos[1] == {"name": "frontend", "path": "/repos/frontend"}
85
86
87
class TestMultiRepoManagerIngestAll:
88
def test_calls_repo_ingester_for_each_repo(self, tmp_path):
89
from navegador.multirepo import MultiRepoManager
90
91
store = _mock_store()
92
# list_repos() is called first; return one repo
93
store.query.return_value = MagicMock(
94
result_set=[["svc", str(tmp_path)]]
95
)
96
mgr = MultiRepoManager(store)
97
98
mock_ingester_instance = MagicMock()
99
mock_ingester_instance.ingest.return_value = {"files": 3, "functions": 10}
100
mock_ingester_cls = MagicMock(return_value=mock_ingester_instance)
101
102
# Patch the lazy import inside ingest_all
103
with patch("navegador.ingestion.parser.RepoIngester", mock_ingester_cls):
104
# Also patch the name that is imported lazily inside the method
105
import navegador.multirepo as _m
106
import navegador.ingestion.parser as _p
107
original = getattr(_p, "RepoIngester", None)
108
_p.RepoIngester = mock_ingester_cls
109
try:
110
summary = mgr.ingest_all()
111
finally:
112
if original is not None:
113
_p.RepoIngester = original
114
115
assert "svc" in summary
116
assert summary["svc"]["files"] == 3
117
118
def test_returns_empty_when_no_repos(self):
119
from navegador.multirepo import MultiRepoManager
120
121
store = _mock_store()
122
store.query.return_value = MagicMock(result_set=[])
123
mgr = MultiRepoManager(store)
124
assert mgr.ingest_all() == {}
125
126
def test_clear_flag_calls_store_clear_when_repos_exist(self, tmp_path):
127
from navegador.multirepo import MultiRepoManager
128
129
store = _mock_store()
130
# Return one repo so ingest_all proceeds past the empty check
131
store.query.return_value = MagicMock(
132
result_set=[["svc", str(tmp_path)]]
133
)
134
mgr = MultiRepoManager(store)
135
136
mock_ingester_instance = MagicMock()
137
mock_ingester_instance.ingest.return_value = {"files": 1}
138
mock_ingester_cls = MagicMock(return_value=mock_ingester_instance)
139
140
import navegador.ingestion.parser as _p
141
original = getattr(_p, "RepoIngester", None)
142
_p.RepoIngester = mock_ingester_cls
143
try:
144
mgr.ingest_all(clear=True)
145
finally:
146
if original is not None:
147
_p.RepoIngester = original
148
149
store.clear.assert_called_once()
150
151
152
class TestMultiRepoManagerCrossRepoSearch:
153
def test_returns_results(self):
154
from navegador.multirepo import MultiRepoManager
155
156
store = _mock_store()
157
store.query.return_value = MagicMock(
158
result_set=[["Function", "authenticate", "auth.py"]]
159
)
160
mgr = MultiRepoManager(store)
161
results = mgr.cross_repo_search("authenticate")
162
assert len(results) == 1
163
assert results[0]["name"] == "authenticate"
164
165
def test_empty_when_no_match(self):
166
from navegador.multirepo import MultiRepoManager
167
168
store = _mock_store()
169
store.query.return_value = MagicMock(result_set=[])
170
mgr = MultiRepoManager(store)
171
assert mgr.cross_repo_search("zzz_nonexistent") == []
172
173
def test_limit_is_applied(self):
174
from navegador.multirepo import MultiRepoManager
175
176
store = _mock_store()
177
store.query.return_value = MagicMock(result_set=[])
178
mgr = MultiRepoManager(store)
179
mgr.cross_repo_search("foo", limit=5)
180
cypher = store.query.call_args[0][0]
181
assert "LIMIT 5" in cypher
182
183
184
# ── CLI: repo ──────────────────────────────────────────────────────────────
185
186
187
class TestRepoCLI:
188
def test_repo_add(self, tmp_path):
189
runner = CliRunner()
190
store = _mock_store()
191
with patch("navegador.cli.commands._get_store", return_value=store):
192
result = runner.invoke(
193
main, ["repo", "add", "myapp", str(tmp_path)]
194
)
195
assert result.exit_code == 0
196
assert "myapp" in result.output
197
198
def test_repo_list_empty(self):
199
runner = CliRunner()
200
store = _mock_store()
201
store.query.return_value = MagicMock(result_set=[])
202
with patch("navegador.cli.commands._get_store", return_value=store):
203
result = runner.invoke(main, ["repo", "list"])
204
assert result.exit_code == 0
205
206
def test_repo_search(self):
207
runner = CliRunner()
208
store = _mock_store()
209
store.query.return_value = MagicMock(result_set=[])
210
with patch("navegador.cli.commands._get_store", return_value=store):
211
result = runner.invoke(main, ["repo", "search", "foo"])
212
assert result.exit_code == 0
213
214
215
# ═════════════════════════════════════════════════════════════════════════════
216
# #26 — SymbolRenamer
217
# ═════════════════════════════════════════════════════════════════════════════
218
219
220
class TestSymbolRenamerFindReferences:
221
def test_returns_references(self):
222
from navegador.refactor import SymbolRenamer
223
224
store = _mock_store()
225
store.query.return_value = MagicMock(
226
result_set=[["Function", "foo", "a.py", 10]]
227
)
228
renamer = SymbolRenamer(store)
229
refs = renamer.find_references("foo")
230
assert len(refs) == 1
231
assert refs[0]["name"] == "foo"
232
assert refs[0]["file_path"] == "a.py"
233
234
def test_filters_by_file_path(self):
235
from navegador.refactor import SymbolRenamer
236
237
store = _mock_store()
238
store.query.return_value = MagicMock(result_set=[])
239
renamer = SymbolRenamer(store)
240
renamer.find_references("foo", file_path="a.py")
241
cypher = store.query.call_args[0][0]
242
assert "file_path" in cypher
243
244
def test_returns_empty_list_when_no_matches(self):
245
from navegador.refactor import SymbolRenamer
246
247
store = _mock_store()
248
store.query.return_value = MagicMock(result_set=[])
249
renamer = SymbolRenamer(store)
250
assert renamer.find_references("nonexistent") == []
251
252
253
class TestSymbolRenamerPreview:
254
def test_preview_does_not_update_graph(self):
255
from navegador.refactor import SymbolRenamer
256
257
store = _mock_store()
258
store.query.return_value = MagicMock(result_set=[])
259
renamer = SymbolRenamer(store)
260
preview = renamer.preview_rename("old", "new")
261
# No SET query should have been issued
262
for c in store.query.call_args_list:
263
assert "SET n.name" not in (c[0][0] if c[0] else "")
264
265
assert preview.old_name == "old"
266
assert preview.new_name == "new"
267
268
def test_preview_collects_affected_files(self):
269
from navegador.refactor import SymbolRenamer
270
271
store = _mock_store()
272
273
def _side(cypher, params=None):
274
if "SET" not in cypher:
275
return MagicMock(
276
result_set=[["Function", "old", "a.py", 1], ["Function", "old", "b.py", 5]]
277
)
278
return MagicMock(result_set=[])
279
280
store.query.side_effect = _side
281
renamer = SymbolRenamer(store)
282
preview = renamer.preview_rename("old", "new")
283
assert set(preview.affected_files) == {"a.py", "b.py"}
284
285
286
class TestSymbolRenamerApply:
287
def test_apply_issues_set_query(self):
288
from navegador.refactor import SymbolRenamer
289
290
store = _mock_store()
291
store.query.return_value = MagicMock(result_set=[])
292
renamer = SymbolRenamer(store)
293
renamer.apply_rename("old", "new")
294
cypher_calls = [c[0][0] for c in store.query.call_args_list]
295
assert any("SET n.name" in c for c in cypher_calls)
296
297
def test_apply_returns_result_with_names(self):
298
from navegador.refactor import SymbolRenamer
299
300
store = _mock_store()
301
store.query.return_value = MagicMock(result_set=[])
302
renamer = SymbolRenamer(store)
303
result = renamer.apply_rename("alpha", "beta")
304
assert result.old_name == "alpha"
305
assert result.new_name == "beta"
306
307
308
# ── CLI: rename ───────────────────────────────────────────────────────────────
309
310
311
class TestRenameCLI:
312
def test_rename_preview(self):
313
runner = CliRunner()
314
store = _mock_store()
315
store.query.return_value = MagicMock(result_set=[])
316
with patch("navegador.cli.commands._get_store", return_value=store):
317
result = runner.invoke(main, ["rename", "old_fn", "new_fn", "--preview"])
318
assert result.exit_code == 0
319
320
def test_rename_apply(self):
321
runner = CliRunner()
322
store = _mock_store()
323
store.query.return_value = MagicMock(result_set=[])
324
with patch("navegador.cli.commands._get_store", return_value=store):
325
result = runner.invoke(main, ["rename", "old_fn", "new_fn"])
326
assert result.exit_code == 0
327
328
329
# ═════════════════════════════════════════════════════════════════════════════
330
# #39 — CodeownersIngester
331
# ═════════════════════════════════════════════════════════════════════════════
332
333
334
class TestCodeownersIngesterParseFile:
335
def test_parses_basic_entries(self, tmp_path):
336
from navegador.codeowners import CodeownersIngester
337
338
co = tmp_path / "CODEOWNERS"
339
co.write_text("*.py @alice @bob\ndocs/ @carol\n")
340
ingester = CodeownersIngester(_mock_store())
341
entries = ingester._parse_codeowners(co)
342
assert len(entries) == 2
343
assert entries[0] == ("*.py", ["@alice", "@bob"])
344
assert entries[1] == ("docs/", ["@carol"])
345
346
def test_ignores_comments(self, tmp_path):
347
from navegador.codeowners import CodeownersIngester
348
349
co = tmp_path / "CODEOWNERS"
350
co.write_text("# comment\n*.py @alice\n")
351
ingester = CodeownersIngester(_mock_store())
352
entries = ingester._parse_codeowners(co)
353
assert len(entries) == 1
354
355
def test_ignores_blank_lines(self, tmp_path):
356
from navegador.codeowners import CodeownersIngester
357
358
co = tmp_path / "CODEOWNERS"
359
co.write_text("\n\n*.py @alice\n\n")
360
ingester = CodeownersIngester(_mock_store())
361
entries = ingester._parse_codeowners(co)
362
assert len(entries) == 1
363
364
def test_handles_email_owner(self, tmp_path):
365
from navegador.codeowners import CodeownersIngester
366
367
co = tmp_path / "CODEOWNERS"
368
co.write_text("*.go [email protected]\n")
369
ingester = CodeownersIngester(_mock_store())
370
entries = ingester._parse_codeowners(co)
371
assert entries[0][1] == ["[email protected]"]
372
373
374
class TestCodeownersIngesterIngest:
375
def test_creates_person_nodes(self, tmp_path):
376
from navegador.codeowners import CodeownersIngester
377
378
co = tmp_path / "CODEOWNERS"
379
co.write_text("*.py @alice\n")
380
store = _mock_store()
381
ingester = CodeownersIngester(store)
382
stats = ingester.ingest(str(tmp_path))
383
assert stats["owners"] == 1
384
assert stats["patterns"] == 1
385
assert stats["edges"] == 1
386
387
def test_deduplicates_owners(self, tmp_path):
388
from navegador.codeowners import CodeownersIngester
389
390
co = tmp_path / "CODEOWNERS"
391
co.write_text("*.py @alice\ndocs/ @alice\n")
392
store = _mock_store()
393
ingester = CodeownersIngester(store)
394
stats = ingester.ingest(str(tmp_path))
395
# alice appears in both patterns but should only be created once
396
assert stats["owners"] == 1
397
assert stats["patterns"] == 2
398
399
def test_returns_zeros_when_no_codeowners(self, tmp_path):
400
from navegador.codeowners import CodeownersIngester
401
402
store = _mock_store()
403
stats = CodeownersIngester(store).ingest(str(tmp_path))
404
assert stats == {"owners": 0, "patterns": 0, "edges": 0}
405
406
def test_finds_github_codeowners(self, tmp_path):
407
from navegador.codeowners import CodeownersIngester
408
409
gh = tmp_path / ".github"
410
gh.mkdir()
411
(gh / "CODEOWNERS").write_text("* @team\n")
412
store = _mock_store()
413
stats = CodeownersIngester(store).ingest(str(tmp_path))
414
assert stats["owners"] == 1
415
416
417
# ── CLI: codeowners ───────────────────────────────────────────────────────────
418
419
420
class TestCodeownersCLI:
421
def test_cli_codeowners(self, tmp_path):
422
runner = CliRunner()
423
(tmp_path / "CODEOWNERS").write_text("*.py @alice\n")
424
store = _mock_store()
425
with patch("navegador.cli.commands._get_store", return_value=store):
426
result = runner.invoke(main, ["codeowners", str(tmp_path)])
427
assert result.exit_code == 0
428
assert "owner" in result.output
429
430
431
# ═════════════════════════════════════════════════════════════════════════════
432
# #40 — ADRIngester
433
# ═════════════════════════════════════════════════════════════════════════════
434
435
436
_SAMPLE_ADR = """\
437
# Use FalkorDB as the graph database
438
439
## Status
440
441
Accepted
442
443
## Context
444
445
We need a property graph DB.
446
447
## Decision
448
449
We will use FalkorDB.
450
451
## Rationale
452
453
Best performance for our use case. Supports Cypher.
454
455
## Date
456
457
2024-01-15
458
"""
459
460
461
class TestADRIngesterParse:
462
def test_parses_title(self, tmp_path):
463
from navegador.adr import ADRIngester
464
465
f = tmp_path / "0001-use-falkordb.md"
466
f.write_text(_SAMPLE_ADR)
467
ingester = ADRIngester(_mock_store())
468
parsed = ingester._parse_adr(f)
469
assert parsed is not None
470
assert "FalkorDB" in parsed["description"]
471
472
def test_parses_status(self, tmp_path):
473
from navegador.adr import ADRIngester
474
475
f = tmp_path / "0001-test.md"
476
f.write_text(_SAMPLE_ADR)
477
ingester = ADRIngester(_mock_store())
478
parsed = ingester._parse_adr(f)
479
assert parsed["status"] == "accepted"
480
481
def test_parses_rationale(self, tmp_path):
482
from navegador.adr import ADRIngester
483
484
f = tmp_path / "0001-test.md"
485
f.write_text(_SAMPLE_ADR)
486
ingester = ADRIngester(_mock_store())
487
parsed = ingester._parse_adr(f)
488
assert "performance" in parsed["rationale"].lower()
489
490
def test_parses_date(self, tmp_path):
491
from navegador.adr import ADRIngester
492
493
f = tmp_path / "0001-test.md"
494
f.write_text(_SAMPLE_ADR)
495
ingester = ADRIngester(_mock_store())
496
parsed = ingester._parse_adr(f)
497
assert parsed["date"] == "2024-01-15"
498
499
def test_uses_stem_as_name(self, tmp_path):
500
from navegador.adr import ADRIngester
501
502
f = tmp_path / "0042-my-decision.md"
503
f.write_text(_SAMPLE_ADR)
504
ingester = ADRIngester(_mock_store())
505
parsed = ingester._parse_adr(f)
506
assert parsed["name"] == "0042-my-decision"
507
508
def test_returns_none_for_non_adr(self, tmp_path):
509
from navegador.adr import ADRIngester
510
511
f = tmp_path / "readme.md"
512
f.write_text("No heading here.")
513
ingester = ADRIngester(_mock_store())
514
assert ingester._parse_adr(f) is None
515
516
517
class TestADRIngesterIngest:
518
def test_creates_decision_nodes(self, tmp_path):
519
from navegador.adr import ADRIngester
520
521
(tmp_path / "0001-first.md").write_text(_SAMPLE_ADR)
522
(tmp_path / "0002-second.md").write_text(_SAMPLE_ADR)
523
store = _mock_store()
524
stats = ADRIngester(store).ingest(str(tmp_path))
525
assert stats["decisions"] == 2
526
assert stats["skipped"] == 0
527
528
def test_skips_files_without_h1(self, tmp_path):
529
from navegador.adr import ADRIngester
530
531
(tmp_path / "empty.md").write_text("no heading\n")
532
store = _mock_store()
533
stats = ADRIngester(store).ingest(str(tmp_path))
534
assert stats["skipped"] == 1
535
536
def test_returns_zeros_for_empty_dir(self, tmp_path):
537
from navegador.adr import ADRIngester
538
539
store = _mock_store()
540
stats = ADRIngester(store).ingest(str(tmp_path))
541
assert stats == {"decisions": 0, "skipped": 0}
542
543
def test_nonexistent_dir_returns_zeros(self, tmp_path):
544
from navegador.adr import ADRIngester
545
546
store = _mock_store()
547
stats = ADRIngester(store).ingest(str(tmp_path / "no_such_dir"))
548
assert stats == {"decisions": 0, "skipped": 0}
549
550
551
# ── CLI: adr ─────────────────────────────────────────────────────────────────
552
553
554
class TestADRCLI:
555
def test_adr_ingest(self, tmp_path):
556
runner = CliRunner()
557
(tmp_path / "0001-test.md").write_text(_SAMPLE_ADR)
558
store = _mock_store()
559
with patch("navegador.cli.commands._get_store", return_value=store):
560
result = runner.invoke(main, ["adr", "ingest", str(tmp_path)])
561
assert result.exit_code == 0
562
assert "decision" in result.output.lower()
563
564
565
# ═════════════════════════════════════════════════════════════════════════════
566
# #41 — APISchemaIngester
567
# ═════════════════════════════════════════════════════════════════════════════
568
569
570
_OPENAPI_YAML = """\
571
openapi: "3.0.0"
572
info:
573
title: Test API
574
version: "1.0"
575
paths:
576
/users:
577
get:
578
operationId: listUsers
579
summary: List all users
580
tags:
581
- users
582
post:
583
operationId: createUser
584
summary: Create a user
585
components:
586
schemas:
587
User:
588
description: A user object
589
type: object
590
"""
591
592
_OPENAPI_JSON = {
593
"openapi": "3.0.0",
594
"info": {"title": "Test API", "version": "1.0"},
595
"paths": {
596
"/items": {
597
"get": {"operationId": "listItems", "summary": "List items"},
598
"post": {"summary": "Create item"},
599
}
600
},
601
"components": {
602
"schemas": {
603
"Item": {"description": "An item", "type": "object"}
604
}
605
},
606
}
607
608
_GRAPHQL_SCHEMA = """\
609
type Query {
610
users: [User]
611
user(id: ID!): User
612
}
613
614
type Mutation {
615
createUser(name: String!): User
616
}
617
618
type User {
619
id: ID!
620
name: String!
621
email: String
622
}
623
624
input CreateUserInput {
625
name: String!
626
email: String
627
}
628
"""
629
630
631
class TestAPISchemaIngesterOpenAPI:
632
def test_ingest_openapi_json(self, tmp_path):
633
from navegador.api_schema import APISchemaIngester
634
635
p = tmp_path / "api.json"
636
p.write_text(json.dumps(_OPENAPI_JSON))
637
store = _mock_store()
638
stats = APISchemaIngester(store).ingest_openapi(str(p))
639
assert stats["endpoints"] >= 2
640
assert stats["schemas"] >= 1
641
642
def test_ingest_creates_function_nodes(self, tmp_path):
643
from navegador.api_schema import APISchemaIngester
644
645
p = tmp_path / "api.json"
646
p.write_text(json.dumps(_OPENAPI_JSON))
647
store = _mock_store()
648
APISchemaIngester(store).ingest_openapi(str(p))
649
labels = [c[0][0] for c in store.create_node.call_args_list]
650
assert "Function" in labels
651
652
def test_ingest_creates_class_nodes_for_schemas(self, tmp_path):
653
from navegador.api_schema import APISchemaIngester
654
655
p = tmp_path / "api.json"
656
p.write_text(json.dumps(_OPENAPI_JSON))
657
store = _mock_store()
658
APISchemaIngester(store).ingest_openapi(str(p))
659
labels = [c[0][0] for c in store.create_node.call_args_list]
660
assert "Class" in labels
661
662
def test_missing_file_returns_zeros(self, tmp_path):
663
from navegador.api_schema import APISchemaIngester
664
665
store = _mock_store()
666
stats = APISchemaIngester(store).ingest_openapi(str(tmp_path / "no.yaml"))
667
assert stats == {"endpoints": 0, "schemas": 0}
668
669
def test_empty_paths_returns_zeros(self, tmp_path):
670
from navegador.api_schema import APISchemaIngester
671
672
p = tmp_path / "empty.json"
673
p.write_text(json.dumps({"openapi": "3.0.0", "info": {}}))
674
store = _mock_store()
675
stats = APISchemaIngester(store).ingest_openapi(str(p))
676
assert stats == {"endpoints": 0, "schemas": 0}
677
678
679
class TestAPISchemaIngesterGraphQL:
680
def test_ingest_graphql_types(self, tmp_path):
681
from navegador.api_schema import APISchemaIngester
682
683
p = tmp_path / "schema.graphql"
684
p.write_text(_GRAPHQL_SCHEMA)
685
store = _mock_store()
686
stats = APISchemaIngester(store).ingest_graphql(str(p))
687
# User + CreateUserInput → type nodes
688
assert stats["types"] >= 1
689
690
def test_ingest_graphql_query_fields(self, tmp_path):
691
from navegador.api_schema import APISchemaIngester
692
693
p = tmp_path / "schema.graphql"
694
p.write_text(_GRAPHQL_SCHEMA)
695
store = _mock_store()
696
stats = APISchemaIngester(store).ingest_graphql(str(p))
697
# Query.users, Query.user, Mutation.createUser
698
assert stats["fields"] >= 2
699
700
def test_missing_file_returns_zeros(self, tmp_path):
701
from navegador.api_schema import APISchemaIngester
702
703
store = _mock_store()
704
stats = APISchemaIngester(store).ingest_graphql(str(tmp_path / "no.graphql"))
705
assert stats == {"types": 0, "fields": 0}
706
707
708
# ── CLI: api ──────────────────────────────────────────────────────────────────
709
710
711
class TestAPICLI:
712
def test_api_ingest_openapi_json(self, tmp_path):
713
runner = CliRunner()
714
p = tmp_path / "api.json"
715
p.write_text(json.dumps(_OPENAPI_JSON))
716
store = _mock_store()
717
with patch("navegador.cli.commands._get_store", return_value=store):
718
result = runner.invoke(
719
main, ["api", "ingest", str(p), "--type", "openapi"]
720
)
721
assert result.exit_code == 0
722
723
def test_api_ingest_graphql(self, tmp_path):
724
runner = CliRunner()
725
p = tmp_path / "schema.graphql"
726
p.write_text(_GRAPHQL_SCHEMA)
727
store = _mock_store()
728
with patch("navegador.cli.commands._get_store", return_value=store):
729
result = runner.invoke(
730
main, ["api", "ingest", str(p), "--type", "graphql"]
731
)
732
assert result.exit_code == 0
733
734
def test_api_ingest_auto_detects_graphql(self, tmp_path):
735
runner = CliRunner()
736
p = tmp_path / "schema.graphql"
737
p.write_text(_GRAPHQL_SCHEMA)
738
store = _mock_store()
739
with patch("navegador.cli.commands._get_store", return_value=store):
740
result = runner.invoke(main, ["api", "ingest", str(p)])
741
assert result.exit_code == 0
742
743
def test_api_ingest_json_output(self, tmp_path):
744
runner = CliRunner()
745
p = tmp_path / "api.json"
746
p.write_text(json.dumps(_OPENAPI_JSON))
747
store = _mock_store()
748
with patch("navegador.cli.commands._get_store", return_value=store):
749
result = runner.invoke(
750
main, ["api", "ingest", str(p), "--type", "openapi", "--json"]
751
)
752
assert result.exit_code == 0
753
data = json.loads(result.output)
754
assert "endpoints" in data
755

Keyboard Shortcuts

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