test_upgrade_regime_integration.py python
300 lines 8.8 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Integration tests — Track O / O3 upgrade-regime on tmp trees (§O2.9 integration)."""
2
3 from __future__ import annotations
4
5 import json
6 from pathlib import Path
7
8 from adapters.config import load_config
9 from adapters.runner import CommandResult
10 from cli.footprint import MUSE_BRIDGE_DEPLOY_DEST, MUSE_BRIDGE_WORKFLOW_DEST
11 from cli.kit_root import kit_root
12 from cli.version_lock import read_version_lock
13 from tests.support import (
14 FIXTURES,
15 make_runner,
16 muse_mirror_status_runner,
17 muse_status_runner,
18 ok,
19 run_cli,
20 seed_muse_substrate,
21 write_config,
22 )
23
24
25 def _init_muse_only(tmp_path: Path) -> None:
26 seed_muse_substrate(tmp_path)
27 code = run_cli(
28 [
29 "init",
30 "--from-config",
31 str(FIXTURES / "config-muse-only.yaml"),
32 "--non-interactive",
33 ],
34 cwd=tmp_path,
35 kit=kit_root(),
36 runner=muse_status_runner(tmp_path),
37 )
38 assert code == 0
39
40
41 def _upgrade_runner(tmp_path: Path, *, remote_url: str | None = "[email protected]:o/r.git"):
42 base = muse_mirror_status_runner(tmp_path)
43 responses = dict(base.responses)
44 if remote_url is None:
45 responses["git remote get-url origin"] = CommandResult(stdout="", stderr="missing", exit_code=2)
46 else:
47 responses["git remote get-url origin"] = ok(remote_url)
48 return make_runner(responses)
49
50
51 def test_apply_muse_only_seeds_bridge_files(tmp_path: Path) -> None:
52 _init_muse_only(tmp_path)
53 before_docs = load_config(tmp_path / ".overseer" / "config.yaml").docs.handover
54 code = run_cli(
55 [
56 "upgrade-regime",
57 "--from",
58 "muse-only",
59 "--to",
60 "muse+git-mirror",
61 "--apply",
62 "--json",
63 ],
64 cwd=tmp_path,
65 kit=kit_root(),
66 runner=_upgrade_runner(tmp_path),
67 json_mode=True,
68 )
69 assert code == 0
70 assert (tmp_path / MUSE_BRIDGE_WORKFLOW_DEST).is_file()
71 assert (tmp_path / MUSE_BRIDGE_DEPLOY_DEST).is_file()
72 lock = read_version_lock(tmp_path / ".overseer" / "version.lock")
73 paths = {e.path for e in lock.footprint}
74 assert MUSE_BRIDGE_WORKFLOW_DEST in paths
75 assert MUSE_BRIDGE_DEPLOY_DEST in paths
76 after = load_config(tmp_path / ".overseer" / "config.yaml")
77 assert after.vcs.regime == "muse+git-mirror"
78 assert after.docs.handover == before_docs
79
80
81 def test_regime_only_mutation_refused_ceremony_writes_complete_vcs(tmp_path: Path) -> None:
82 """§O2.9: regime-only mutation refused — apply always writes full §O2.4.2 VCS block."""
83 from tools.upgrade_regime.ceremony import (
84 build_upgraded_config_dict,
85 is_silent_regime_only_patch,
86 required_vcs_complete,
87 )
88
89 _init_muse_only(tmp_path)
90 before = load_config(tmp_path / ".overseer" / "config.yaml")
91 silent = {
92 "vcs": {
93 "regime": "muse+git-mirror",
94 "canonical": "muse",
95 "git": {
96 "remote": before.vcs.git.remote,
97 "main_branch": before.vcs.git.main_branch,
98 "mirror_branch": None,
99 "feature_branch_pattern": before.vcs.git.feature_branch_pattern,
100 },
101 "muse": {
102 "staging_remote": None,
103 "main_branch": before.vcs.muse.main_branch,
104 "working_dir": before.vcs.muse.working_dir,
105 },
106 }
107 }
108 assert is_silent_regime_only_patch(before, silent) is True
109 upgraded = build_upgraded_config_dict(before)
110 assert is_silent_regime_only_patch(before, upgraded) is False
111
112 code = run_cli(
113 [
114 "upgrade-regime",
115 "--from",
116 "muse-only",
117 "--to",
118 "muse+git-mirror",
119 "--apply",
120 ],
121 cwd=tmp_path,
122 kit=kit_root(),
123 runner=_upgrade_runner(tmp_path),
124 )
125 assert code == 0
126 after = load_config(tmp_path / ".overseer" / "config.yaml")
127 assert required_vcs_complete(after)
128 assert after.vcs.git.mirror_branch == "muse-mirror"
129 assert after.vcs.muse.staging_remote == "staging"
130 assert after.docs.handover == before.docs.handover
131
132 # git-only start still refused
133 git_tree = tmp_path / "git"
134 git_tree.mkdir()
135 write_config(git_tree, "config-git-only.yaml")
136 code = run_cli(
137 [
138 "upgrade-regime",
139 "--from",
140 "muse-only",
141 "--to",
142 "muse+git-mirror",
143 "--dry-run",
144 ],
145 cwd=git_tree,
146 kit=kit_root(),
147 )
148 assert code == 4
149
150
151 def test_shared_asset_conflict_without_force_exit_4(tmp_path: Path) -> None:
152 _init_muse_only(tmp_path)
153 scripts = tmp_path / "scripts"
154 scripts.mkdir(parents=True)
155 (scripts / "muse-bridge-deploy.sh").write_text("#!/bin/bash\necho hand-tuned\n", encoding="utf-8")
156 (tmp_path / MUSE_BRIDGE_WORKFLOW_DEST).write_text("# hand\n", encoding="utf-8")
157 code = run_cli(
158 [
159 "upgrade-regime",
160 "--from",
161 "muse-only",
162 "--to",
163 "muse+git-mirror",
164 "--apply",
165 ],
166 cwd=tmp_path,
167 kit=kit_root(),
168 runner=_upgrade_runner(tmp_path),
169 )
170 assert code == 4
171 assert (scripts / "muse-bridge-deploy.sh").read_text(encoding="utf-8") == "#!/bin/bash\necho hand-tuned\n"
172
173
174 def test_shared_asset_conflict_with_force_ok(tmp_path: Path) -> None:
175 _init_muse_only(tmp_path)
176 scripts = tmp_path / "scripts"
177 scripts.mkdir(parents=True)
178 (scripts / "muse-bridge-deploy.sh").write_text("#!/bin/bash\necho hand-tuned\n", encoding="utf-8")
179 code = run_cli(
180 [
181 "upgrade-regime",
182 "--from",
183 "muse-only",
184 "--to",
185 "muse+git-mirror",
186 "--apply",
187 "--force",
188 ],
189 cwd=tmp_path,
190 kit=kit_root(),
191 runner=_upgrade_runner(tmp_path),
192 )
193 assert code == 0
194 body = (tmp_path / MUSE_BRIDGE_DEPLOY_DEST).read_text(encoding="utf-8")
195 assert "hand-tuned" not in body
196 assert "muse -C" in body
197
198
199 def test_idempotent_complete_upgrade(tmp_path: Path) -> None:
200 _init_muse_only(tmp_path)
201 assert (
202 run_cli(
203 ["upgrade-regime", "--from", "muse-only", "--to", "muse+git-mirror", "--apply"],
204 cwd=tmp_path,
205 kit=kit_root(),
206 runner=_upgrade_runner(tmp_path),
207 )
208 == 0
209 )
210 code = run_cli(
211 ["upgrade-regime", "--from", "muse-only", "--to", "muse+git-mirror", "--apply", "--json"],
212 cwd=tmp_path,
213 kit=kit_root(),
214 runner=_upgrade_runner(tmp_path),
215 json_mode=True,
216 )
217 assert code == 0
218
219
220 def test_incomplete_upgrade_repair(tmp_path: Path) -> None:
221 # Start as complete muse+git-mirror init, then remove bridge files from disk/lock path.
222 seed_muse_substrate(tmp_path)
223 code = run_cli(
224 [
225 "init",
226 "--from-config",
227 str(FIXTURES / "config-muse-git-mirror.yaml"),
228 "--non-interactive",
229 ],
230 cwd=tmp_path,
231 kit=kit_root(),
232 runner=muse_mirror_status_runner(tmp_path),
233 )
234 assert code == 0
235 (tmp_path / MUSE_BRIDGE_DEPLOY_DEST).unlink()
236 (tmp_path / MUSE_BRIDGE_WORKFLOW_DEST).unlink()
237 code = run_cli(
238 ["upgrade-regime", "--from", "muse-only", "--to", "muse+git-mirror", "--apply"],
239 cwd=tmp_path,
240 kit=kit_root(),
241 runner=_upgrade_runner(tmp_path),
242 )
243 assert code == 0
244 assert (tmp_path / MUSE_BRIDGE_DEPLOY_DEST).is_file()
245
246
247 def test_g8_fail_marks_not_ready_for_live(tmp_path: Path) -> None:
248 _init_muse_only(tmp_path)
249 code = run_cli(
250 [
251 "upgrade-regime",
252 "--from",
253 "muse-only",
254 "--to",
255 "muse+git-mirror",
256 "--apply",
257 "--json",
258 ],
259 cwd=tmp_path,
260 kit=kit_root(),
261 runner=_upgrade_runner(tmp_path, remote_url=None),
262 json_mode=True,
263 )
264 assert code == 0
265 # Capture JSON via a second dry-run on the upgraded tree
266 import io
267 import sys
268
269 from cli.context import CliContext
270 from cli.main import main
271 from cli.output import OutputContext
272
273 buf = io.StringIO()
274 old = sys.stdout
275 sys.stdout = buf
276 try:
277 ctx = CliContext.create(
278 runner=_upgrade_runner(tmp_path, remote_url=None),
279 cwd=tmp_path,
280 kit=kit_root(),
281 output=OutputContext(json_mode=True),
282 )
283 code2 = main(
284 [
285 "upgrade-regime",
286 "--from",
287 "muse-only",
288 "--to",
289 "muse+git-mirror",
290 "--dry-run",
291 "--json",
292 ],
293 ctx=ctx,
294 )
295 finally:
296 sys.stdout = old
297 assert code2 == 0
298 payload = json.loads(buf.getvalue())
299 assert payload["ready_for_live_bridge"] is False
300 assert payload["gates"]["G8"]["ok"] is False
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago