forked from VOBC/oh-my-coder
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_monorepo.py
More file actions
375 lines (291 loc) · 13.1 KB
/
Copy pathtest_monorepo.py
File metadata and controls
375 lines (291 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
"""Tests for monorepo detection and workspace management."""
from __future__ import annotations
import json
from pathlib import Path
from src.core.monorepo import (
MonorepoInfo,
SubProject,
_find_common_package_dirs,
_has_agent_config,
_parse_lerna_packages,
_parse_nx_workspace,
_parse_pnpm_workspace,
detect_framework,
detect_language,
detect_monorepo,
find_monorepo_root,
get_monorepo_context,
list_subprojects,
)
class TestDetectMonorepo:
"""Test monorepo detection."""
def test_detect_pnpm_workspace(self, tmp_path: Path):
"""Detect pnpm monorepo from pnpm-workspace.yaml."""
(tmp_path / "pnpm-workspace.yaml").write_text(
"packages:\n - 'packages/*'\n", encoding="utf-8"
)
(tmp_path / "packages" / "app1").mkdir(parents=True)
info = detect_monorepo(tmp_path)
assert info is not None
assert info.type == "pnpm"
assert info.root == tmp_path
assert len(info.packages) >= 1
def test_detect_lerna(self, tmp_path: Path):
"""Detect lerna monorepo from lerna.json."""
lerna_config = {"packages": ["packages/*"], "version": "1.0.0"}
(tmp_path / "lerna.json").write_text(
json.dumps(lerna_config), encoding="utf-8"
)
(tmp_path / "packages" / "lib1").mkdir(parents=True)
info = detect_monorepo(tmp_path)
assert info is not None
assert info.type == "lerna"
assert info.root == tmp_path
def test_detect_nx(self, tmp_path: Path):
"""Detect nx monorepo from nx.json."""
(tmp_path / "nx.json").write_text("{}", encoding="utf-8")
(tmp_path / "packages" / "ui").mkdir(parents=True)
info = detect_monorepo(tmp_path)
assert info is not None
assert info.type == "nx"
def test_detect_none(self, tmp_path: Path):
"""Return None for non-monorepo directory."""
info = detect_monorepo(tmp_path)
assert info is None
def test_detect_turborepo(self, tmp_path: Path):
"""Detect turborepo from turbo.json."""
(tmp_path / "turbo.json").write_text("{}", encoding="utf-8")
(tmp_path / "apps" / "web").mkdir(parents=True)
info = detect_monorepo(tmp_path)
assert info is not None
assert info.type == "turborepo"
class TestFindMonorepoRoot:
"""Test finding monorepo root by walking up."""
def test_find_root_from_subdirectory(self, tmp_path: Path):
"""Find monorepo root from a subdirectory."""
(tmp_path / "pnpm-workspace.yaml").write_text(
"packages:\n - 'packages/*'\n", encoding="utf-8"
)
subdir = tmp_path / "packages" / "app1" / "src"
subdir.mkdir(parents=True)
root = find_monorepo_root(subdir)
assert root == tmp_path
def test_find_root_from_same_directory(self, tmp_path: Path):
"""Find monorepo root when already at root."""
(tmp_path / "lerna.json").write_text('{"version": "1.0.0"}', encoding="utf-8")
root = find_monorepo_root(tmp_path)
assert root == tmp_path
def test_find_root_none(self, tmp_path: Path):
"""Return None when not in a monorepo."""
subdir = tmp_path / "some" / "deep" / "path"
subdir.mkdir(parents=True)
root = find_monorepo_root(subdir)
assert root is None
class TestParsePnpmWorkspace:
"""Test pnpm workspace parsing."""
def test_parse_packages_glob(self, tmp_path: Path):
"""Parse packages/* glob pattern."""
(tmp_path / "pnpm-workspace.yaml").write_text(
"packages:\n - 'packages/*'\n - 'apps/*'\n", encoding="utf-8"
)
(tmp_path / "packages" / "pkg1").mkdir(parents=True)
(tmp_path / "apps" / "app1").mkdir(parents=True)
packages = _parse_pnpm_workspace(tmp_path)
assert len(packages) == 2
names = {p.name for p in packages}
assert names == {"pkg1", "app1"}
def test_parse_fallback_common_dirs(self, tmp_path: Path):
"""Fallback to common package dirs when no packages declared."""
(tmp_path / "pnpm-workspace.yaml").write_text("packages:\n", encoding="utf-8")
(tmp_path / "packages" / "lib1").mkdir(parents=True)
packages = _parse_pnpm_workspace(tmp_path)
assert len(packages) >= 1
class TestParseLernaPackages:
"""Test lerna package parsing."""
def test_parse_lerna_packages_array(self, tmp_path: Path):
"""Parse lerna packages array."""
config = {"packages": ["packages/*"], "version": "1.0.0"}
(tmp_path / "lerna.json").write_text(json.dumps(config), encoding="utf-8")
(tmp_path / "packages" / "core").mkdir(parents=True)
(tmp_path / "packages" / "utils").mkdir(parents=True)
packages = _parse_lerna_packages(tmp_path)
assert len(packages) == 2
names = {p.name for p in packages}
assert names == {"core", "utils"}
def test_parse_lerna_invalid_json(self, tmp_path: Path):
"""Handle invalid lerna.json gracefully."""
(tmp_path / "lerna.json").write_text("not json", encoding="utf-8")
(tmp_path / "packages" / "pkg1").mkdir(parents=True)
packages = _parse_lerna_packages(tmp_path)
# Should fallback to common dirs
assert len(packages) >= 1
class TestParseNxWorkspace:
"""Test nx workspace parsing."""
def test_parse_workspace_json(self, tmp_path: Path):
"""Parse workspace.json projects."""
config = {"projects": {"app1": "apps/app1", "lib1": "libs/lib1"}}
(tmp_path / "workspace.json").write_text(json.dumps(config), encoding="utf-8")
(tmp_path / "apps" / "app1").mkdir(parents=True)
(tmp_path / "libs" / "lib1").mkdir(parents=True)
packages = _parse_nx_workspace(tmp_path)
assert len(packages) == 2
names = {p.name for p in packages}
assert names == {"app1", "lib1"}
def test_parse_nx_fallback(self, tmp_path: Path):
"""Fallback to common dirs when workspace.json missing."""
(tmp_path / "nx.json").write_text("{}", encoding="utf-8")
(tmp_path / "packages" / "pkg1").mkdir(parents=True)
packages = _parse_nx_workspace(tmp_path)
assert len(packages) >= 1
class TestDetectLanguage:
"""Test project language detection."""
def test_detect_node(self, tmp_path: Path):
"""Detect Node.js/TypeScript project."""
(tmp_path / "package.json").write_text("{}", encoding="utf-8")
assert detect_language(tmp_path) == "Node/TS"
def test_detect_python_pyproject(self, tmp_path: Path):
"""Detect Python project via pyproject.toml."""
(tmp_path / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
assert detect_language(tmp_path) == "Python"
def test_detect_python_setup(self, tmp_path: Path):
"""Detect Python project via setup.py."""
(tmp_path / "setup.py").write_text("", encoding="utf-8")
assert detect_language(tmp_path) == "Python"
def test_detect_rust(self, tmp_path: Path):
"""Detect Rust project."""
(tmp_path / "Cargo.toml").write_text("[package]\n", encoding="utf-8")
assert detect_language(tmp_path) == "Rust"
def test_detect_go(self, tmp_path: Path):
"""Detect Go project."""
(tmp_path / "go.mod").write_text("module test\n", encoding="utf-8")
assert detect_language(tmp_path) == "Go"
def test_detect_unknown(self, tmp_path: Path):
"""Return unknown for unrecognized project."""
assert detect_language(tmp_path) == "unknown"
class TestDetectFramework:
"""Test framework detection."""
def test_detect_react(self, tmp_path: Path):
"""Detect React framework."""
pkg = {"dependencies": {"react": "^18.0.0"}}
(tmp_path / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
assert detect_framework(tmp_path) == "React"
def test_detect_nextjs(self, tmp_path: Path):
"""Detect Next.js framework."""
pkg = {"dependencies": {"next": "^14.0.0"}}
(tmp_path / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
assert detect_framework(tmp_path) == "Next.js"
def test_detect_no_framework(self, tmp_path: Path):
"""Return empty for project without framework."""
pkg = {"dependencies": {"lodash": "^4.0.0"}}
(tmp_path / "package.json").write_text(json.dumps(pkg), encoding="utf-8")
assert detect_framework(tmp_path) == ""
def test_detect_no_package_json(self, tmp_path: Path):
"""Return empty when no package.json exists."""
assert detect_framework(tmp_path) == ""
class TestHasAgentConfig:
"""Test agent config detection."""
def test_has_config_json(self, tmp_path: Path):
"""Detect .omc/config.json."""
omc_dir = tmp_path / ".omc"
omc_dir.mkdir(parents=True)
(omc_dir / "config.json").write_text("{}", encoding="utf-8")
assert _has_agent_config(tmp_path) is True
def test_has_agent_yaml(self, tmp_path: Path):
"""Detect .omc/agent.yaml."""
omc_dir = tmp_path / ".omc"
omc_dir.mkdir(parents=True)
(omc_dir / "agent.yaml").write_text("", encoding="utf-8")
assert _has_agent_config(tmp_path) is True
def test_no_config(self, tmp_path: Path):
"""Return False when no agent config exists."""
assert _has_agent_config(tmp_path) is False
class TestListSubprojects:
"""Test listing subprojects."""
def test_list_subprojects(self, tmp_path: Path):
"""List all subprojects in monorepo."""
(tmp_path / "pnpm-workspace.yaml").write_text(
"packages:\n - 'packages/*'\n", encoding="utf-8"
)
(tmp_path / "packages" / "frontend").mkdir(parents=True)
(tmp_path / "packages" / "backend").mkdir(parents=True)
(tmp_path / "packages" / "frontend" / "package.json").write_text(
'{"dependencies": {"react": "^18"}}', encoding="utf-8"
)
info = detect_monorepo(tmp_path)
assert info is not None
subprojects = list_subprojects(info)
assert len(subprojects) == 2
frontend = next(sp for sp in subprojects if sp.name == "frontend")
assert frontend.language == "Node/TS"
assert frontend.framework == "React"
def test_list_subprojects_none(self):
"""Return empty list when not in monorepo."""
subprojects = list_subprojects(None)
assert subprojects == []
class TestGetMonorepoContext:
"""Test getting monorepo context."""
def test_context_in_monorepo(self, tmp_path: Path):
"""Get context when inside monorepo."""
(tmp_path / "pnpm-workspace.yaml").write_text(
"packages:\n - 'packages/*'\n", encoding="utf-8"
)
(tmp_path / "packages" / "app1").mkdir(parents=True)
ctx = get_monorepo_context(tmp_path / "packages" / "app1")
assert ctx["is_monorepo"] is True
assert ctx["monorepo_type"] == "pnpm"
assert ctx["total_projects"] == 1
assert ctx["current_project"] is not None
def test_context_not_in_monorepo(self, tmp_path: Path):
"""Get empty context when not in monorepo."""
ctx = get_monorepo_context(tmp_path)
assert ctx == {}
class TestFindCommonPackageDirs:
"""Test finding packages in common directory names."""
def test_find_packages_dir(self, tmp_path: Path):
"""Find packages in packages/ directory."""
(tmp_path / "packages" / "pkg1").mkdir(parents=True)
(tmp_path / "packages" / "pkg2").mkdir(parents=True)
packages = _find_common_package_dirs(tmp_path)
assert len(packages) == 2
names = {p.name for p in packages}
assert names == {"pkg1", "pkg2"}
def test_find_apps_dir(self, tmp_path: Path):
"""Find packages in apps/ directory."""
(tmp_path / "apps" / "web").mkdir(parents=True)
packages = _find_common_package_dirs(tmp_path)
assert len(packages) == 1
assert packages[0].name == "web"
def test_find_no_dirs(self, tmp_path: Path):
"""Return empty when no common dirs exist."""
packages = _find_common_package_dirs(tmp_path)
assert packages == []
class TestMonorepoInfo:
"""Test MonorepoInfo dataclass."""
def test_to_dict(self, tmp_path: Path):
"""Serialize MonorepoInfo to dict."""
info = MonorepoInfo(
root=tmp_path,
type="pnpm",
packages=[tmp_path / "pkg1"],
config_file=tmp_path / "pnpm-workspace.yaml",
)
d = info.to_dict()
assert d["type"] == "pnpm"
assert d["root"] == str(tmp_path)
assert d["packages"] == [str(tmp_path / "pkg1")]
class TestSubProject:
"""Test SubProject dataclass."""
def test_to_dict(self, tmp_path: Path):
"""Serialize SubProject to dict."""
sp = SubProject(
name="test",
path=tmp_path,
language="Python",
framework="FastAPI",
has_agent_config=True,
)
d = sp.to_dict()
assert d["name"] == "test"
assert d["language"] == "Python"
assert d["framework"] == "FastAPI"
assert d["has_agent_config"] is True