test_upgrade_regime_integration.py
python
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