Navegador

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

Keyboard Shortcuts

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