|
1
|
"""Tests for navegador.monorepo — WorkspaceDetector, MonorepoIngester, CLI flag.""" |
|
2
|
|
|
3
|
import json |
|
4
|
import tempfile |
|
5
|
from pathlib import Path |
|
6
|
from unittest.mock import MagicMock, call, patch |
|
7
|
|
|
8
|
import pytest |
|
9
|
from click.testing import CliRunner |
|
10
|
|
|
11
|
from navegador.cli.commands import main |
|
12
|
from navegador.monorepo import MonorepoIngester, WorkspaceConfig, WorkspaceDetector |
|
13
|
|
|
14
|
|
|
15
|
# ── Helpers ─────────────────────────────────────────────────────────────────── |
|
16
|
|
|
17
|
|
|
18
|
def _mock_store(): |
|
19
|
store = MagicMock() |
|
20
|
store.query.return_value = MagicMock(result_set=[]) |
|
21
|
return store |
|
22
|
|
|
23
|
|
|
24
|
def _write(path: Path, content: str) -> None: |
|
25
|
path.parent.mkdir(parents=True, exist_ok=True) |
|
26
|
path.write_text(content, encoding="utf-8") |
|
27
|
|
|
28
|
|
|
29
|
# ── WorkspaceDetector — positive cases ──────────────────────────────────────── |
|
30
|
|
|
31
|
|
|
32
|
class TestWorkspaceDetectorTurborepo: |
|
33
|
def test_detects_type(self, tmp_path): |
|
34
|
_write(tmp_path / "turbo.json", '{"pipeline": {}}') |
|
35
|
# No package.json workspaces — fallback scan |
|
36
|
(tmp_path / "packages" / "app").mkdir(parents=True) |
|
37
|
_write(tmp_path / "packages" / "app" / "package.json", '{"name": "app"}') |
|
38
|
config = WorkspaceDetector().detect(tmp_path) |
|
39
|
assert config is not None |
|
40
|
assert config.type == "turborepo" |
|
41
|
|
|
42
|
def test_root_is_resolved(self, tmp_path): |
|
43
|
_write(tmp_path / "turbo.json", '{"pipeline": {}}') |
|
44
|
config = WorkspaceDetector().detect(tmp_path) |
|
45
|
assert config.root == tmp_path.resolve() |
|
46
|
|
|
47
|
def test_uses_package_json_workspaces(self, tmp_path): |
|
48
|
_write(tmp_path / "turbo.json", '{}') |
|
49
|
_write( |
|
50
|
tmp_path / "package.json", |
|
51
|
json.dumps({"workspaces": ["packages/*"]}), |
|
52
|
) |
|
53
|
(tmp_path / "packages" / "alpha").mkdir(parents=True) |
|
54
|
(tmp_path / "packages" / "beta").mkdir(parents=True) |
|
55
|
config = WorkspaceDetector().detect(tmp_path) |
|
56
|
assert config is not None |
|
57
|
names = [p.name for p in config.packages] |
|
58
|
assert "alpha" in names |
|
59
|
assert "beta" in names |
|
60
|
|
|
61
|
def test_name_defaults_to_dirname(self, tmp_path): |
|
62
|
_write(tmp_path / "turbo.json", '{}') |
|
63
|
config = WorkspaceDetector().detect(tmp_path) |
|
64
|
assert config.name == tmp_path.name |
|
65
|
|
|
66
|
|
|
67
|
class TestWorkspaceDetectorNx: |
|
68
|
def test_detects_type(self, tmp_path): |
|
69
|
_write(tmp_path / "nx.json", '{}') |
|
70
|
config = WorkspaceDetector().detect(tmp_path) |
|
71
|
assert config is not None |
|
72
|
assert config.type == "nx" |
|
73
|
|
|
74
|
def test_finds_apps_and_libs(self, tmp_path): |
|
75
|
_write(tmp_path / "nx.json", '{}') |
|
76
|
(tmp_path / "apps" / "web").mkdir(parents=True) |
|
77
|
(tmp_path / "libs" / "ui").mkdir(parents=True) |
|
78
|
config = WorkspaceDetector().detect(tmp_path) |
|
79
|
names = [p.name for p in config.packages] |
|
80
|
assert "web" in names |
|
81
|
assert "ui" in names |
|
82
|
|
|
83
|
def test_empty_nx_has_no_packages(self, tmp_path): |
|
84
|
_write(tmp_path / "nx.json", '{}') |
|
85
|
config = WorkspaceDetector().detect(tmp_path) |
|
86
|
# No apps/libs dirs — falls back to package.json scan which also finds nothing |
|
87
|
assert config is not None |
|
88
|
assert config.packages == [] |
|
89
|
|
|
90
|
|
|
91
|
class TestWorkspaceDetectorPnpm: |
|
92
|
def test_detects_type(self, tmp_path): |
|
93
|
_write( |
|
94
|
tmp_path / "pnpm-workspace.yaml", |
|
95
|
"packages:\n - 'packages/*'\n", |
|
96
|
) |
|
97
|
(tmp_path / "packages" / "foo").mkdir(parents=True) |
|
98
|
config = WorkspaceDetector().detect(tmp_path) |
|
99
|
assert config is not None |
|
100
|
assert config.type == "pnpm" |
|
101
|
|
|
102
|
def test_resolves_glob_packages(self, tmp_path): |
|
103
|
_write( |
|
104
|
tmp_path / "pnpm-workspace.yaml", |
|
105
|
"packages:\n - 'pkgs/*'\n", |
|
106
|
) |
|
107
|
(tmp_path / "pkgs" / "core").mkdir(parents=True) |
|
108
|
(tmp_path / "pkgs" / "utils").mkdir(parents=True) |
|
109
|
config = WorkspaceDetector().detect(tmp_path) |
|
110
|
names = [p.name for p in config.packages] |
|
111
|
assert "core" in names |
|
112
|
assert "utils" in names |
|
113
|
|
|
114
|
def test_empty_yaml_returns_fallback(self, tmp_path): |
|
115
|
_write(tmp_path / "pnpm-workspace.yaml", "") |
|
116
|
config = WorkspaceDetector().detect(tmp_path) |
|
117
|
assert config is not None |
|
118
|
assert config.type == "pnpm" |
|
119
|
|
|
120
|
|
|
121
|
class TestWorkspaceDetectorYarn: |
|
122
|
def test_detects_type(self, tmp_path): |
|
123
|
_write( |
|
124
|
tmp_path / "package.json", |
|
125
|
json.dumps({"name": "root", "workspaces": ["packages/*"]}), |
|
126
|
) |
|
127
|
(tmp_path / "packages" / "a").mkdir(parents=True) |
|
128
|
config = WorkspaceDetector().detect(tmp_path) |
|
129
|
assert config is not None |
|
130
|
assert config.type == "yarn" |
|
131
|
|
|
132
|
def test_yarn_berry_workspaces_packages_key(self, tmp_path): |
|
133
|
_write( |
|
134
|
tmp_path / "package.json", |
|
135
|
json.dumps({"workspaces": {"packages": ["apps/*"]}}), |
|
136
|
) |
|
137
|
(tmp_path / "apps" / "web").mkdir(parents=True) |
|
138
|
config = WorkspaceDetector().detect(tmp_path) |
|
139
|
assert config is not None |
|
140
|
assert config.type == "yarn" |
|
141
|
assert any(p.name == "web" for p in config.packages) |
|
142
|
|
|
143
|
def test_explicit_package_path(self, tmp_path): |
|
144
|
pkg_dir = tmp_path / "my-package" |
|
145
|
pkg_dir.mkdir() |
|
146
|
_write( |
|
147
|
tmp_path / "package.json", |
|
148
|
json.dumps({"workspaces": ["my-package"]}), |
|
149
|
) |
|
150
|
config = WorkspaceDetector().detect(tmp_path) |
|
151
|
assert config is not None |
|
152
|
assert any(p.name == "my-package" for p in config.packages) |
|
153
|
|
|
154
|
|
|
155
|
class TestWorkspaceDetectorCargo: |
|
156
|
def test_detects_type(self, tmp_path): |
|
157
|
_write( |
|
158
|
tmp_path / "Cargo.toml", |
|
159
|
'[workspace]\nmembers = ["crates/core", "crates/utils"]\n', |
|
160
|
) |
|
161
|
(tmp_path / "crates" / "core").mkdir(parents=True) |
|
162
|
(tmp_path / "crates" / "utils").mkdir(parents=True) |
|
163
|
config = WorkspaceDetector().detect(tmp_path) |
|
164
|
assert config is not None |
|
165
|
assert config.type == "cargo" |
|
166
|
|
|
167
|
def test_resolves_member_paths(self, tmp_path): |
|
168
|
_write( |
|
169
|
tmp_path / "Cargo.toml", |
|
170
|
'[workspace]\nmembers = ["crates/alpha"]\n', |
|
171
|
) |
|
172
|
(tmp_path / "crates" / "alpha").mkdir(parents=True) |
|
173
|
config = WorkspaceDetector().detect(tmp_path) |
|
174
|
assert any(p.name == "alpha" for p in config.packages) |
|
175
|
|
|
176
|
def test_non_workspace_cargo_returns_none(self, tmp_path): |
|
177
|
_write( |
|
178
|
tmp_path / "Cargo.toml", |
|
179
|
'[package]\nname = "myapp"\nversion = "0.1.0"\n', |
|
180
|
) |
|
181
|
config = WorkspaceDetector().detect(tmp_path) |
|
182
|
assert config is None |
|
183
|
|
|
184
|
|
|
185
|
class TestWorkspaceDetectorGo: |
|
186
|
def test_detects_type(self, tmp_path): |
|
187
|
_write(tmp_path / "go.work", "go 1.21\nuse ./api\nuse ./worker\n") |
|
188
|
(tmp_path / "api").mkdir() |
|
189
|
(tmp_path / "worker").mkdir() |
|
190
|
config = WorkspaceDetector().detect(tmp_path) |
|
191
|
assert config is not None |
|
192
|
assert config.type == "go" |
|
193
|
|
|
194
|
def test_resolves_use_paths(self, tmp_path): |
|
195
|
_write(tmp_path / "go.work", "go 1.21\nuse ./pkg/a\n") |
|
196
|
(tmp_path / "pkg" / "a").mkdir(parents=True) |
|
197
|
config = WorkspaceDetector().detect(tmp_path) |
|
198
|
assert any(p.name == "a" for p in config.packages) |
|
199
|
|
|
200
|
def test_missing_dirs_skipped(self, tmp_path): |
|
201
|
_write(tmp_path / "go.work", "go 1.21\nuse ./missing\n") |
|
202
|
config = WorkspaceDetector().detect(tmp_path) |
|
203
|
assert config is not None |
|
204
|
assert config.packages == [] |
|
205
|
|
|
206
|
|
|
207
|
# ── WorkspaceDetector — negative case ──────────────────────────────────────── |
|
208
|
|
|
209
|
|
|
210
|
class TestWorkspaceDetectorNoWorkspace: |
|
211
|
def test_plain_repo_returns_none(self, tmp_path): |
|
212
|
# Just a bare directory — no workspace config files |
|
213
|
_write(tmp_path / "main.py", "print('hello')") |
|
214
|
config = WorkspaceDetector().detect(tmp_path) |
|
215
|
assert config is None |
|
216
|
|
|
217
|
def test_package_json_without_workspaces_returns_none(self, tmp_path): |
|
218
|
_write(tmp_path / "package.json", '{"name": "single-package"}') |
|
219
|
config = WorkspaceDetector().detect(tmp_path) |
|
220
|
assert config is None |
|
221
|
|
|
222
|
def test_empty_directory_returns_none(self, tmp_path): |
|
223
|
config = WorkspaceDetector().detect(tmp_path) |
|
224
|
assert config is None |
|
225
|
|
|
226
|
|
|
227
|
# ── WorkspaceConfig ─────────────────────────────────────────────────────────── |
|
228
|
|
|
229
|
|
|
230
|
class TestWorkspaceConfig: |
|
231
|
def test_name_defaults_to_root_dirname(self, tmp_path): |
|
232
|
cfg = WorkspaceConfig(type="yarn", root=tmp_path, packages=[]) |
|
233
|
assert cfg.name == tmp_path.name |
|
234
|
|
|
235
|
def test_explicit_name_preserved(self, tmp_path): |
|
236
|
cfg = WorkspaceConfig(type="yarn", root=tmp_path, packages=[], name="my-repo") |
|
237
|
assert cfg.name == "my-repo" |
|
238
|
|
|
239
|
def test_packages_list_stored(self, tmp_path): |
|
240
|
pkgs = [tmp_path / "a", tmp_path / "b"] |
|
241
|
cfg = WorkspaceConfig(type="pnpm", root=tmp_path, packages=pkgs) |
|
242
|
assert cfg.packages == pkgs |
|
243
|
|
|
244
|
|
|
245
|
# ── MonorepoIngester ────────────────────────────────────────────────────────── |
|
246
|
|
|
247
|
|
|
248
|
class TestMonorepoIngesterFallback: |
|
249
|
def test_no_workspace_falls_back_to_single_ingest(self, tmp_path): |
|
250
|
"""When no workspace config is detected, ingest as a regular repo.""" |
|
251
|
store = _mock_store() |
|
252
|
_write(tmp_path / "main.py", "x = 1") |
|
253
|
|
|
254
|
with patch( |
|
255
|
"navegador.monorepo.WorkspaceDetector.detect", return_value=None |
|
256
|
), patch("navegador.monorepo.RepoIngester") as MockRI: |
|
257
|
MockRI.return_value.ingest.return_value = { |
|
258
|
"files": 1, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
259
|
} |
|
260
|
ingester = MonorepoIngester(store) |
|
261
|
stats = ingester.ingest(tmp_path) |
|
262
|
|
|
263
|
assert stats["packages"] == 0 |
|
264
|
assert stats["workspace_type"] == "none" |
|
265
|
MockRI.return_value.ingest.assert_called_once() |
|
266
|
|
|
267
|
def test_raises_on_missing_path(self): |
|
268
|
store = _mock_store() |
|
269
|
ingester = MonorepoIngester(store) |
|
270
|
with pytest.raises(FileNotFoundError): |
|
271
|
ingester.ingest("/this/does/not/exist") |
|
272
|
|
|
273
|
|
|
274
|
class TestMonorepoIngesterWithWorkspace: |
|
275
|
def _setup_yarn_monorepo(self, tmp_path): |
|
276
|
"""Create a minimal Yarn workspace fixture.""" |
|
277
|
_write( |
|
278
|
tmp_path / "package.json", |
|
279
|
json.dumps({"name": "root", "workspaces": ["packages/*"]}), |
|
280
|
) |
|
281
|
(tmp_path / "packages" / "app").mkdir(parents=True) |
|
282
|
(tmp_path / "packages" / "lib").mkdir(parents=True) |
|
283
|
_write( |
|
284
|
tmp_path / "packages" / "app" / "package.json", |
|
285
|
json.dumps({"name": "app", "dependencies": {"lib": "*"}}), |
|
286
|
) |
|
287
|
_write( |
|
288
|
tmp_path / "packages" / "lib" / "package.json", |
|
289
|
json.dumps({"name": "lib"}), |
|
290
|
) |
|
291
|
|
|
292
|
def test_creates_root_repository_node(self, tmp_path): |
|
293
|
self._setup_yarn_monorepo(tmp_path) |
|
294
|
store = _mock_store() |
|
295
|
|
|
296
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
297
|
MockRI.return_value.ingest.return_value = { |
|
298
|
"files": 0, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
299
|
} |
|
300
|
MonorepoIngester(store).ingest(tmp_path) |
|
301
|
|
|
302
|
# Root Repository node must have been created |
|
303
|
create_node_calls = store.create_node.call_args_list |
|
304
|
labels = [c[0][0] for c in create_node_calls] |
|
305
|
from navegador.graph.schema import NodeLabel |
|
306
|
assert NodeLabel.Repository in labels |
|
307
|
|
|
308
|
def test_ingest_called_per_package(self, tmp_path): |
|
309
|
self._setup_yarn_monorepo(tmp_path) |
|
310
|
store = _mock_store() |
|
311
|
|
|
312
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
313
|
MockRI.return_value.ingest.return_value = { |
|
314
|
"files": 2, "functions": 3, "classes": 1, "edges": 1, "skipped": 0 |
|
315
|
} |
|
316
|
stats = MonorepoIngester(store).ingest(tmp_path) |
|
317
|
|
|
318
|
# Two packages → ingest called twice |
|
319
|
assert MockRI.return_value.ingest.call_count == 2 |
|
320
|
assert stats["packages"] == 2 |
|
321
|
|
|
322
|
def test_aggregates_stats(self, tmp_path): |
|
323
|
self._setup_yarn_monorepo(tmp_path) |
|
324
|
store = _mock_store() |
|
325
|
|
|
326
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
327
|
MockRI.return_value.ingest.return_value = { |
|
328
|
"files": 3, "functions": 5, "classes": 2, "edges": 4, "skipped": 0 |
|
329
|
} |
|
330
|
stats = MonorepoIngester(store).ingest(tmp_path) |
|
331
|
|
|
332
|
# 2 packages × per-package values |
|
333
|
assert stats["files"] == 6 |
|
334
|
assert stats["functions"] == 10 |
|
335
|
assert stats["workspace_type"] == "yarn" |
|
336
|
|
|
337
|
def test_clear_calls_store_clear(self, tmp_path): |
|
338
|
self._setup_yarn_monorepo(tmp_path) |
|
339
|
store = _mock_store() |
|
340
|
|
|
341
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
342
|
MockRI.return_value.ingest.return_value = { |
|
343
|
"files": 0, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
344
|
} |
|
345
|
MonorepoIngester(store).ingest(tmp_path, clear=True) |
|
346
|
|
|
347
|
store.clear.assert_called_once() |
|
348
|
|
|
349
|
def test_depends_on_edges_created_for_sibling_deps(self, tmp_path): |
|
350
|
"""app depends on lib — a DEPENDS_ON edge should be created.""" |
|
351
|
self._setup_yarn_monorepo(tmp_path) |
|
352
|
store = _mock_store() |
|
353
|
|
|
354
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
355
|
MockRI.return_value.ingest.return_value = { |
|
356
|
"files": 0, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
357
|
} |
|
358
|
MonorepoIngester(store).ingest(tmp_path) |
|
359
|
|
|
360
|
from navegador.graph.schema import EdgeType |
|
361
|
edge_calls = store.create_edge.call_args_list |
|
362
|
depends_on_edges = [ |
|
363
|
c for c in edge_calls |
|
364
|
if c[1].get("edge_type") == EdgeType.DEPENDS_ON |
|
365
|
or (len(c[0]) > 2 and c[0][2] == EdgeType.DEPENDS_ON) |
|
366
|
] |
|
367
|
# At minimum one DEPENDS_ON call should have been attempted |
|
368
|
# (exact count depends on resolution; we verify the mechanism fired) |
|
369
|
assert store.create_edge.called |
|
370
|
|
|
371
|
|
|
372
|
class TestMonorepoIngesterCargo: |
|
373
|
def test_cargo_workspace_type(self, tmp_path): |
|
374
|
_write( |
|
375
|
tmp_path / "Cargo.toml", |
|
376
|
'[workspace]\nmembers = ["crates/core"]\n', |
|
377
|
) |
|
378
|
(tmp_path / "crates" / "core").mkdir(parents=True) |
|
379
|
store = _mock_store() |
|
380
|
|
|
381
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
382
|
MockRI.return_value.ingest.return_value = { |
|
383
|
"files": 0, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
384
|
} |
|
385
|
stats = MonorepoIngester(store).ingest(tmp_path) |
|
386
|
|
|
387
|
assert stats["workspace_type"] == "cargo" |
|
388
|
assert stats["packages"] == 1 |
|
389
|
|
|
390
|
|
|
391
|
class TestMonorepoIngesterGo: |
|
392
|
def test_go_workspace_type(self, tmp_path): |
|
393
|
(tmp_path / "svc").mkdir() |
|
394
|
_write(tmp_path / "go.work", "go 1.21\nuse ./svc\n") |
|
395
|
store = _mock_store() |
|
396
|
|
|
397
|
with patch("navegador.monorepo.RepoIngester") as MockRI: |
|
398
|
MockRI.return_value.ingest.return_value = { |
|
399
|
"files": 0, "functions": 0, "classes": 0, "edges": 0, "skipped": 0 |
|
400
|
} |
|
401
|
stats = MonorepoIngester(store).ingest(tmp_path) |
|
402
|
|
|
403
|
assert stats["workspace_type"] == "go" |
|
404
|
assert stats["packages"] == 1 |
|
405
|
|
|
406
|
|
|
407
|
# ── CLI flag ────────────────────────────────────────────────────────────────── |
|
408
|
|
|
409
|
|
|
410
|
class TestIngestMonorepoFlag: |
|
411
|
def _mock_store_fn(self): |
|
412
|
return _mock_store() |
|
413
|
|
|
414
|
def test_monorepo_flag_calls_monorepo_ingester(self, tmp_path): |
|
415
|
runner = CliRunner() |
|
416
|
with patch("navegador.cli.commands._get_store", return_value=_mock_store()), \ |
|
417
|
patch("navegador.monorepo.MonorepoIngester") as MockMI: |
|
418
|
MockMI.return_value.ingest.return_value = { |
|
419
|
"files": 4, "functions": 10, "classes": 2, |
|
420
|
"edges": 3, "skipped": 0, "packages": 2, "workspace_type": "yarn" |
|
421
|
} |
|
422
|
result = runner.invoke(main, ["ingest", str(tmp_path), "--monorepo"]) |
|
423
|
|
|
424
|
assert result.exit_code == 0 |
|
425
|
MockMI.return_value.ingest.assert_called_once() |
|
426
|
|
|
427
|
def test_monorepo_flag_with_json_output(self, tmp_path): |
|
428
|
runner = CliRunner() |
|
429
|
expected = { |
|
430
|
"files": 4, "functions": 10, "classes": 2, |
|
431
|
"edges": 3, "skipped": 0, "packages": 2, "workspace_type": "pnpm" |
|
432
|
} |
|
433
|
with patch("navegador.cli.commands._get_store", return_value=_mock_store()), \ |
|
434
|
patch("navegador.monorepo.MonorepoIngester") as MockMI: |
|
435
|
MockMI.return_value.ingest.return_value = expected |
|
436
|
result = runner.invoke( |
|
437
|
main, ["ingest", str(tmp_path), "--monorepo", "--json"] |
|
438
|
) |
|
439
|
|
|
440
|
assert result.exit_code == 0 |
|
441
|
data = json.loads(result.output) |
|
442
|
assert data["packages"] == 2 |
|
443
|
assert data["workspace_type"] == "pnpm" |
|
444
|
|
|
445
|
def test_monorepo_flag_passes_clear(self, tmp_path): |
|
446
|
runner = CliRunner() |
|
447
|
with patch("navegador.cli.commands._get_store", return_value=_mock_store()), \ |
|
448
|
patch("navegador.monorepo.MonorepoIngester") as MockMI: |
|
449
|
MockMI.return_value.ingest.return_value = { |
|
450
|
"files": 0, "functions": 0, "classes": 0, |
|
451
|
"edges": 0, "skipped": 0, "packages": 0, "workspace_type": "none" |
|
452
|
} |
|
453
|
result = runner.invoke( |
|
454
|
main, ["ingest", str(tmp_path), "--monorepo", "--clear"] |
|
455
|
) |
|
456
|
|
|
457
|
assert result.exit_code == 0 |
|
458
|
_, kwargs = MockMI.return_value.ingest.call_args |
|
459
|
assert kwargs.get("clear") is True |
|
460
|
|
|
461
|
def test_without_monorepo_flag_uses_repo_ingester(self, tmp_path): |
|
462
|
"""Sanity: the regular ingest path is not affected by the new flag.""" |
|
463
|
runner = CliRunner() |
|
464
|
with patch("navegador.cli.commands._get_store", return_value=_mock_store()), \ |
|
465
|
patch("navegador.ingestion.RepoIngester") as MockRI: |
|
466
|
MockRI.return_value.ingest.return_value = { |
|
467
|
"files": 1, "functions": 2, "classes": 0, "edges": 0, "skipped": 0 |
|
468
|
} |
|
469
|
result = runner.invoke(main, ["ingest", str(tmp_path)]) |
|
470
|
|
|
471
|
assert result.exit_code == 0 |
|
472
|
MockRI.return_value.ingest.assert_called_once() |
|
473
|
|
|
474
|
def test_monorepo_flag_help_text(self): |
|
475
|
runner = CliRunner() |
|
476
|
result = runner.invoke(main, ["ingest", "--help"]) |
|
477
|
assert result.exit_code == 0 |
|
478
|
assert "--monorepo" in result.output |
|
479
|
|