8Integration test for L1 / F1 leanest first cut: ``oct git status``
9invoked from a workspace root (a directory containing
10``.option_c_workspace.json``) succeeds without the
11``--root-dir . --allow-outside-project`` escape hatch that pre-F1
16- Verify that placing a workspace manifest at a parent directory
17 makes ``oct git status`` work from that parent.
18- Verify backwards compat: ``oct git status`` from inside a
19 subproject still resolves to the subproject root (not the
27 L3 -- assertion details
34from pathlib
import Path
40 return shutil.which(
"git")
is not None
43def _git(repo: Path, *args: str, check: bool =
True):
44 return subprocess.run(
45 [
"git", *args], cwd=repo, check=check,
46 capture_output=
True, text=
True,
51 cwd: Path, *extra_args: str,
52) -> subprocess.CompletedProcess:
53 return subprocess.run(
54 [sys.executable,
"-m",
"oct.cli",
"git",
"status", *extra_args],
55 cwd=cwd, capture_output=
True, text=
True,
61 """Create a workspace with one Option C subproject inside.
66 ├── .option_c_workspace.json
72 ├── docs/ARCHITECTURE.md
80 pytest.skip(
"git binary not available on PATH")
82 ws_root = tmp_path /
"myws"
84 _git(ws_root,
"init",
"--quiet",
"-b",
"main")
85 _git(ws_root,
"config",
"user.email",
"test@example.com")
86 _git(ws_root,
"config",
"user.name",
"Test")
87 _git(ws_root,
"config",
"commit.gpgsign",
"false")
91 "name":
"Test Workspace",
92 "subprojects": [{
"path":
"subproj",
"name":
"subproj"}],
94 (ws_root /
".option_c_workspace.json").write_text(
95 json.dumps(manifest, indent=2), encoding=
"utf-8",
98 sub = ws_root /
"subproj"
100 (sub /
".option_c").mkdir()
101 (sub /
"src").mkdir()
102 (sub /
"tests").mkdir()
103 (sub /
"docs").mkdir()
104 (sub /
"logs").mkdir()
105 (sub /
"oc_diagnostics").mkdir()
106 (sub /
".octrc.json").write_text(
107 json.dumps({
"linter": {
"profile":
"compact"}}),
110 (sub /
"pyproject.toml").write_text(
111 '[project]\nname = "subproj"\n', encoding=
"utf-8",
113 (sub /
"debug_config.json").write_text(
114 json.dumps({
"domains": {
"GIT": {
"level": 1,
"color":
"cyan"}}}),
117 (sub /
"docs" /
"ARCHITECTURE.md").write_text(
118 "# Architecture\n", encoding=
"utf-8",
121 _git(ws_root,
"add",
"-A")
122 _git(ws_root,
"commit",
"-m",
"initial",
"--quiet")
124 return ws_root.resolve(), sub.resolve()
135 self, workspace_with_subproject,
137 ws_root, _sub = workspace_with_subproject
139 assert result.returncode == 0, result.stderr
141 assert "On branch main" in result.stdout
142 assert "Could not locate project root" not in result.stderr
157 """Two-subproject workspace fixture for F1.2 fan-out tests."""
159 pytest.skip(
"git binary not available on PATH")
161 ws_root = tmp_path /
"ws2"
163 _git(ws_root,
"init",
"--quiet",
"-b",
"main")
164 _git(ws_root,
"config",
"user.email",
"test@example.com")
165 _git(ws_root,
"config",
"user.name",
"Test")
166 _git(ws_root,
"config",
"commit.gpgsign",
"false")
170 "name":
"Two-Sub Workspace",
171 "workspace_files": [
"CLAUDE.md",
"README.md"],
173 {
"path":
"alpha",
"name":
"alpha",
"profile":
"compact"},
174 {
"path":
"beta",
"name":
"beta",
"profile":
"compact"},
177 (ws_root /
".option_c_workspace.json").write_text(
178 json.dumps(manifest, indent=2), encoding=
"utf-8",
181 for sp_name
in (
"alpha",
"beta"):
182 sub = ws_root / sp_name
184 (sub /
".option_c").mkdir()
185 (sub /
"src").mkdir()
186 (sub /
"tests").mkdir()
187 (sub /
"docs").mkdir()
188 (sub /
"logs").mkdir()
189 (sub /
"oc_diagnostics").mkdir()
190 (sub /
".octrc.json").write_text(
191 json.dumps({
"linter": {
"profile":
"compact"}}),
194 (sub /
"pyproject.toml").write_text(
195 f
'[project]\nname = "{sp_name}"\n', encoding=
"utf-8",
197 (sub /
"debug_config.json").write_text(
198 json.dumps({
"domains": {
"GIT": {
"level": 1,
"color":
"cyan"}}}),
201 (sub /
"docs" /
"ARCHITECTURE.md").write_text(
202 "# Architecture\n", encoding=
"utf-8",
206 (ws_root /
"CLAUDE.md").write_text(
"# CLAUDE\n", encoding=
"utf-8")
207 (ws_root /
"README.md").write_text(
"# README\n", encoding=
"utf-8")
209 _git(ws_root,
"add",
"-A")
210 _git(ws_root,
"commit",
"-m",
"initial",
"--quiet")
214 (ws_root /
"alpha").resolve(),
215 (ws_root /
"beta").resolve(),
222 self, workspace_with_two_subprojects,
224 ws_root, _alpha, _beta = workspace_with_two_subprojects
226 assert result.returncode == 0, result.stderr
227 data = json.loads(result.stdout)
228 assert "workspace_view" in data
229 assert data[
"workspace_view"]
is not None
230 assert set(data[
"workspace_view"].keys()) == {
231 "alpha",
"beta",
"workspace",
235 self, workspace_with_two_subprojects,
237 ws_root, alpha, beta = workspace_with_two_subprojects
239 (alpha /
"src" /
"a.py").write_text(
"x = 1\n", encoding=
"utf-8")
240 (beta /
"src" /
"b.py").write_text(
"x = 2\n", encoding=
"utf-8")
241 _git(ws_root,
"add",
"alpha/src/a.py",
"beta/src/b.py")
244 assert result.returncode == 0, result.stderr
245 data = json.loads(result.stdout)
246 wv = data[
"workspace_view"]
248 alpha_paths = [e[
"path"]
for e
in wv[
"alpha"][
"staged"]]
249 beta_paths = [e[
"path"]
for e
in wv[
"beta"][
"staged"]]
250 assert "src/a.py" in alpha_paths
251 assert "src/b.py" in beta_paths
253 assert "src/b.py" not in alpha_paths
254 assert "src/a.py" not in beta_paths
257 self, workspace_with_two_subprojects,
259 ws_root, _alpha, _beta = workspace_with_two_subprojects
261 (ws_root /
"CLAUDE.md").write_text(
262 "# CLAUDE — modified\n", encoding=
"utf-8",
265 data = json.loads(result.stdout)
266 wv = data[
"workspace_view"]
268 ws_modified = [e[
"path"]
for e
in wv[
"workspace"][
"modified"]]
269 assert "CLAUDE.md" in ws_modified
271 for sp_name
in (
"alpha",
"beta"):
272 sp_modified = [e[
"path"]
for e
in wv[sp_name][
"modified"]]
273 assert "CLAUDE.md" not in sp_modified
276 self, workspace_with_two_subprojects,
278 ws_root, alpha, _beta = workspace_with_two_subprojects
279 (alpha /
"src" /
"x.py").write_text(
"x = 1\n", encoding=
"utf-8")
280 _git(ws_root,
"add",
"alpha/src/x.py")
282 data = json.loads(result.stdout)
284 assert "staged" in data
285 assert "modified" in data
286 assert "untracked" in data
288 flat_paths = [e[
"path"]
for e
in data[
"staged"]]
289 assert any(
"x.py" in p
for p
in flat_paths)
295 self, workspace_with_subproject,
297 _ws_root, sub = workspace_with_subproject
300 (sub /
"src" /
"new.py").write_text(
301 "x = 1\n", encoding=
"utf-8",
304 assert result.returncode == 0, result.stderr
305 data = json.loads(result.stdout)
308 untracked_paths = [e[
"path"]
for e
in data[
"untracked"]]
309 assert any(
"src/new.py" in p
for p
in untracked_paths), (
310 f
"expected subproject-relative path, got: {untracked_paths}"
312 assert all(
"subproj/" not in p
for p
in untracked_paths), (
313 f
"backwards-compat broken: workspace prefix appeared in "
314 f
"subproject-mode output: {untracked_paths}"
None test_legacy_keys_preserved(self, workspace_with_two_subprojects)
None test_workspace_files_route_to_workspace_bucket(self, workspace_with_two_subprojects)
None test_status_groups_by_subproject(self, workspace_with_two_subprojects)
None test_workspace_view_present_in_json(self, workspace_with_two_subprojects)
None test_status_from_workspace_root_works(self, workspace_with_subproject)
None test_status_from_subproject_resolves_to_subproject(self, workspace_with_subproject)
tuple[Path, Path] workspace_with_subproject(Path tmp_path)
workspace_with_two_subprojects(Path tmp_path)
subprocess.CompletedProcess _run_status(Path cwd, *str extra_args)
_git(Path repo, *str args, bool check=True)