test_init_migrate.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Unit tests for ``overseer init --migrate`` (§K6.4 / §K6.10).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | from cli.version_lock import ORIGIN_KIT, ORIGIN_PRESERVED, read_version_lock |
| 8 | from tests.support import PILOT, git_status_runner, lock_origins, muse_mirror_status_runner, run_cli, seed_pilot_tree |
| 9 | |
| 10 | |
| 11 | def _migrate(tmp_path: Path, config_name: str, *extra: str) -> int: |
| 12 | runner = ( |
| 13 | git_status_runner() |
| 14 | if "videofactory" in config_name |
| 15 | else muse_mirror_status_runner(tmp_path) |
| 16 | ) |
| 17 | return run_cli( |
| 18 | [ |
| 19 | "init", |
| 20 | "--migrate", |
| 21 | "--from-config", |
| 22 | str(PILOT / config_name), |
| 23 | "--non-interactive", |
| 24 | *extra, |
| 25 | ], |
| 26 | cwd=tmp_path, |
| 27 | runner=runner, |
| 28 | ) |
| 29 | |
| 30 | |
| 31 | def _runner_for(tmp_path: Path, config_name: str = "config-scooling.yaml"): |
| 32 | if "videofactory" in config_name: |
| 33 | return git_status_runner() |
| 34 | return muse_mirror_status_runner(tmp_path) |
| 35 | |
| 36 | |
| 37 | def test_migrate_preserves_differing_living_docs(tmp_path: Path) -> None: |
| 38 | seed_pilot_tree( |
| 39 | tmp_path, |
| 40 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 41 | handover_text="# HAND SCOOLING\n", |
| 42 | roadmap_rel="docs/ROADMAP.md", |
| 43 | roadmap_text="# HAND ROADMAP\n", |
| 44 | ) |
| 45 | code = _migrate(tmp_path, "config-scooling.yaml") |
| 46 | assert code == 0 |
| 47 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# HAND SCOOLING\n" |
| 48 | origins = lock_origins(tmp_path) |
| 49 | assert origins["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 50 | assert origins["docs/ROADMAP.md"] == ORIGIN_PRESERVED |
| 51 | assert origins[".overseer/policy/tiers.yaml"] == ORIGIN_KIT |
| 52 | |
| 53 | |
| 54 | def test_migrate_force_still_preserves_living_docs(tmp_path: Path) -> None: |
| 55 | seed_pilot_tree( |
| 56 | tmp_path, |
| 57 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 58 | handover_text="# KEEP\n", |
| 59 | roadmap_rel="docs/ROADMAP.md", |
| 60 | roadmap_text="# KEEP ROAD\n", |
| 61 | ) |
| 62 | code = _migrate(tmp_path, "config-scooling.yaml", "--force") |
| 63 | assert code == 0 |
| 64 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# KEEP\n" |
| 65 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 66 | |
| 67 | |
| 68 | def test_migrate_seeds_absent_living_doc_as_preserved(tmp_path: Path) -> None: |
| 69 | seed_pilot_tree( |
| 70 | tmp_path, |
| 71 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 72 | handover_text="# KN HAND\n", |
| 73 | roadmap_rel=None, |
| 74 | ) |
| 75 | code = _migrate(tmp_path, "config-knowtation.yaml") |
| 76 | assert code == 0 |
| 77 | assert (tmp_path / "docs/ROADMAP.md").is_file() |
| 78 | assert lock_origins(tmp_path)["docs/ROADMAP.md"] == ORIGIN_PRESERVED |
| 79 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 80 | |
| 81 | |
| 82 | def test_migrate_promote_differing_living_doc(tmp_path: Path) -> None: |
| 83 | seed_pilot_tree( |
| 84 | tmp_path, |
| 85 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 86 | handover_text="# OLD\n", |
| 87 | roadmap_rel="docs/ROADMAP.md", |
| 88 | roadmap_text="# OLD R\n", |
| 89 | ) |
| 90 | code = _migrate( |
| 91 | tmp_path, |
| 92 | "config-scooling.yaml", |
| 93 | "--force", |
| 94 | "--include-preserved", |
| 95 | ) |
| 96 | assert code == 0 |
| 97 | text = (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") |
| 98 | assert text != "# OLD\n" |
| 99 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_KIT |
| 100 | |
| 101 | |
| 102 | def test_migrate_promote_identical_living_doc_ownership_only(tmp_path: Path) -> None: |
| 103 | run_cli( |
| 104 | [ |
| 105 | "init", |
| 106 | "--from-config", |
| 107 | str(PILOT / "config-scooling.yaml"), |
| 108 | "--non-interactive", |
| 109 | ], |
| 110 | cwd=tmp_path, |
| 111 | runner=muse_mirror_status_runner(tmp_path), |
| 112 | ) |
| 113 | before = (tmp_path / "docs/OVERSEER-HANDOVER.md").read_bytes() |
| 114 | code = _migrate( |
| 115 | tmp_path, |
| 116 | "config-scooling.yaml", |
| 117 | "--force", |
| 118 | "--include-preserved", |
| 119 | ) |
| 120 | assert code == 0 |
| 121 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_bytes() == before |
| 122 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_KIT |
| 123 | |
| 124 | |
| 125 | def test_migrate_include_preserved_without_force_is_noop(tmp_path: Path) -> None: |
| 126 | seed_pilot_tree( |
| 127 | tmp_path, |
| 128 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 129 | handover_text="# KEEP\n", |
| 130 | roadmap_rel="docs/ROADMAP.md", |
| 131 | roadmap_text="# KEEP R\n", |
| 132 | ) |
| 133 | code = _migrate(tmp_path, "config-scooling.yaml", "--include-preserved") |
| 134 | assert code == 0 |
| 135 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# KEEP\n" |
| 136 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 137 | |
| 138 | |
| 139 | def test_migrate_refuses_shared_asset_conflict(tmp_path: Path) -> None: |
| 140 | seed_pilot_tree( |
| 141 | tmp_path, |
| 142 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 143 | handover_text="# H\n", |
| 144 | roadmap_rel="docs/ROADMAP.md", |
| 145 | roadmap_text="# R\n", |
| 146 | ) |
| 147 | policy = tmp_path / ".overseer" / "policy" |
| 148 | policy.mkdir(parents=True, exist_ok=True) |
| 149 | (policy / "tiers.yaml").write_text("altered: true\n", encoding="utf-8") |
| 150 | code = _migrate(tmp_path, "config-scooling.yaml") |
| 151 | assert code == 4 |
| 152 | |
| 153 | |
| 154 | def test_kn_r2_pass_updates_without_force(tmp_path: Path) -> None: |
| 155 | consumer_rule = (PILOT / "knowtation-no-docs-only-pr.mdc").read_text(encoding="utf-8") |
| 156 | seed_pilot_tree( |
| 157 | tmp_path, |
| 158 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 159 | handover_text="# KN\n", |
| 160 | roadmap_rel=None, |
| 161 | extra_cursor_rules={"no-docs-only-pr-to-main.mdc": consumer_rule}, |
| 162 | ) |
| 163 | code = _migrate(tmp_path, "config-knowtation.yaml") |
| 164 | assert code == 0 |
| 165 | lock = read_version_lock(tmp_path / ".overseer" / "version.lock") |
| 166 | entry = next(e for e in lock.footprint if e.path == ".cursor/rules/no-docs-only-pr-to-main.mdc") |
| 167 | assert entry.origin == ORIGIN_KIT |
| 168 | # Written content should be rendered kit (tokens substituted), not consumer bytes |
| 169 | written = (tmp_path / ".cursor/rules/no-docs-only-pr-to-main.mdc").read_text(encoding="utf-8") |
| 170 | assert "{{vcs.git.main_branch}}" not in written |
| 171 | assert "main" in written |
| 172 | |
| 173 | |
| 174 | def test_kit_only_digest_excludes_preserved(tmp_path: Path) -> None: |
| 175 | seed_pilot_tree( |
| 176 | tmp_path, |
| 177 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 178 | handover_text="# H\n", |
| 179 | roadmap_rel="docs/ROADMAP.md", |
| 180 | roadmap_text="# R\n", |
| 181 | ) |
| 182 | assert _migrate(tmp_path, "config-scooling.yaml") == 0 |
| 183 | lock = read_version_lock(tmp_path / ".overseer" / "version.lock") |
| 184 | from cli.version_lock import compute_lock_digest |
| 185 | |
| 186 | assert lock.footprint_digest == compute_lock_digest(list(lock.footprint)) |
| 187 | assert any(e.origin == ORIGIN_PRESERVED for e in lock.footprint) |
| 188 | |
| 189 | |
| 190 | def test_hand_edit_preserved_check_footprint_ok(tmp_path: Path) -> None: |
| 191 | seed_pilot_tree( |
| 192 | tmp_path, |
| 193 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 194 | handover_text="# H\n", |
| 195 | roadmap_rel="docs/ROADMAP.md", |
| 196 | roadmap_text="# R\n", |
| 197 | ) |
| 198 | assert _migrate(tmp_path, "config-scooling.yaml") == 0 |
| 199 | (tmp_path / "docs/OVERSEER-HANDOVER.md").write_text("# H edited\n", encoding="utf-8") |
| 200 | code = run_cli( |
| 201 | ["status", "--check-footprint", "--json"], |
| 202 | cwd=tmp_path, |
| 203 | runner=muse_mirror_status_runner(tmp_path), |
| 204 | json_mode=True, |
| 205 | ) |
| 206 | assert code == 0 |
| 207 | |
| 208 | |
| 209 | def test_hand_edit_preserved_default_sync_exit_zero(tmp_path: Path) -> None: |
| 210 | seed_pilot_tree( |
| 211 | tmp_path, |
| 212 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 213 | handover_text="# H\n", |
| 214 | roadmap_rel="docs/ROADMAP.md", |
| 215 | roadmap_text="# R\n", |
| 216 | ) |
| 217 | assert _migrate(tmp_path, "config-scooling.yaml") == 0 |
| 218 | (tmp_path / "docs/OVERSEER-HANDOVER.md").write_text("# H edited\n", encoding="utf-8") |
| 219 | code = run_cli(["sync", "-y"], cwd=tmp_path, runner=muse_mirror_status_runner(tmp_path)) |
| 220 | assert code == 0 |
| 221 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# H edited\n" |
| 222 | |
| 223 | |
| 224 | def test_sync_force_alone_does_not_overwrite_preserved(tmp_path: Path) -> None: |
| 225 | seed_pilot_tree( |
| 226 | tmp_path, |
| 227 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 228 | handover_text="# H\n", |
| 229 | roadmap_rel="docs/ROADMAP.md", |
| 230 | roadmap_text="# R\n", |
| 231 | ) |
| 232 | assert _migrate(tmp_path, "config-scooling.yaml") == 0 |
| 233 | code = run_cli(["sync", "--force", "-y"], cwd=tmp_path, runner=muse_mirror_status_runner(tmp_path)) |
| 234 | assert code == 0 |
| 235 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# H\n" |
| 236 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 237 | |
| 238 | |
| 239 | def test_sync_force_include_preserved_promotes(tmp_path: Path) -> None: |
| 240 | seed_pilot_tree( |
| 241 | tmp_path, |
| 242 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 243 | handover_text="# H\n", |
| 244 | roadmap_rel="docs/ROADMAP.md", |
| 245 | roadmap_text="# R\n", |
| 246 | ) |
| 247 | assert _migrate(tmp_path, "config-scooling.yaml") == 0 |
| 248 | code = run_cli( |
| 249 | ["sync", "--force", "--include-preserved", "-y"], |
| 250 | cwd=tmp_path, |
| 251 | runner=muse_mirror_status_runner(tmp_path), |
| 252 | ) |
| 253 | assert code == 0 |
| 254 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_KIT |
| 255 | assert "# H\n" not in (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") |
| 256 | |
| 257 | |
| 258 | def test_hand_edit_seeded_roadmap_check_and_sync(tmp_path: Path) -> None: |
| 259 | seed_pilot_tree( |
| 260 | tmp_path, |
| 261 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 262 | handover_text="# KN\n", |
| 263 | roadmap_rel=None, |
| 264 | ) |
| 265 | assert _migrate(tmp_path, "config-knowtation.yaml") == 0 |
| 266 | (tmp_path / "docs/ROADMAP.md").write_text("# seeded then edited\n", encoding="utf-8") |
| 267 | assert run_cli( |
| 268 | ["status", "--check-footprint"], |
| 269 | cwd=tmp_path, |
| 270 | runner=muse_mirror_status_runner(tmp_path), |
| 271 | ) == 0 |
| 272 | assert run_cli(["sync", "-y"], cwd=tmp_path, runner=muse_mirror_status_runner(tmp_path)) == 0 |
| 273 | assert (tmp_path / "docs/ROADMAP.md").read_text(encoding="utf-8") == "# seeded then edited\n" |
| 274 | |
| 275 | |
| 276 | def test_migrate_force_overwrites_shared_asset_without_preserve_flag(tmp_path: Path) -> None: |
| 277 | seed_pilot_tree( |
| 278 | tmp_path, |
| 279 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 280 | handover_text="# H\n", |
| 281 | roadmap_rel="docs/ROADMAP.md", |
| 282 | roadmap_text="# R\n", |
| 283 | ) |
| 284 | policy = tmp_path / ".overseer" / "policy" |
| 285 | policy.mkdir(parents=True, exist_ok=True) |
| 286 | (policy / "tiers.yaml").write_text("altered: true\n", encoding="utf-8") |
| 287 | code = _migrate(tmp_path, "config-scooling.yaml", "--force") |
| 288 | assert code == 0 |
| 289 | assert (policy / "tiers.yaml").read_text(encoding="utf-8") != "altered: true\n" |
| 290 | assert lock_origins(tmp_path)[".overseer/policy/tiers.yaml"] == ORIGIN_KIT |
| 291 | |
| 292 | |
| 293 | def test_migrate_preserve_shared_assets_keeps_differing_shared(tmp_path: Path) -> None: |
| 294 | seed_pilot_tree( |
| 295 | tmp_path, |
| 296 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 297 | handover_text="# H\n", |
| 298 | roadmap_rel="docs/ROADMAP.md", |
| 299 | roadmap_text="# R\n", |
| 300 | ) |
| 301 | policy = tmp_path / ".overseer" / "policy" |
| 302 | policy.mkdir(parents=True, exist_ok=True) |
| 303 | hand = "altered: true\n# consumer-owned\n" |
| 304 | (policy / "tiers.yaml").write_text(hand, encoding="utf-8") |
| 305 | code = _migrate(tmp_path, "config-scooling.yaml", "--preserve-shared-assets") |
| 306 | assert code == 0 |
| 307 | assert (policy / "tiers.yaml").read_text(encoding="utf-8") == hand |
| 308 | assert lock_origins(tmp_path)[".overseer/policy/tiers.yaml"] == ORIGIN_PRESERVED |
| 309 | |
| 310 | |
| 311 | def test_migrate_force_preserve_shared_assets_still_keeps_shared(tmp_path: Path) -> None: |
| 312 | seed_pilot_tree( |
| 313 | tmp_path, |
| 314 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 315 | handover_text="# KEEP\n", |
| 316 | roadmap_rel="docs/ROADMAP.md", |
| 317 | roadmap_text="# KEEP R\n", |
| 318 | ) |
| 319 | policy = tmp_path / ".overseer" / "policy" |
| 320 | policy.mkdir(parents=True, exist_ok=True) |
| 321 | hand = "consumer-owned-policy: true\n" |
| 322 | (policy / "tiers.yaml").write_text(hand, encoding="utf-8") |
| 323 | code = _migrate(tmp_path, "config-scooling.yaml", "--force", "--preserve-shared-assets") |
| 324 | assert code == 0 |
| 325 | assert (policy / "tiers.yaml").read_text(encoding="utf-8") == hand |
| 326 | assert (tmp_path / "docs/OVERSEER-HANDOVER.md").read_text(encoding="utf-8") == "# KEEP\n" |
| 327 | assert lock_origins(tmp_path)[".overseer/policy/tiers.yaml"] == ORIGIN_PRESERVED |
| 328 | assert lock_origins(tmp_path)["docs/OVERSEER-HANDOVER.md"] == ORIGIN_PRESERVED |
| 329 | |
| 330 | |
| 331 | def test_migrate_promote_overwrites_preserved_shared(tmp_path: Path) -> None: |
| 332 | seed_pilot_tree( |
| 333 | tmp_path, |
| 334 | handover_rel="docs/OVERSEER-HANDOVER.md", |
| 335 | handover_text="# H\n", |
| 336 | roadmap_rel="docs/ROADMAP.md", |
| 337 | roadmap_text="# R\n", |
| 338 | ) |
| 339 | policy = tmp_path / ".overseer" / "policy" |
| 340 | policy.mkdir(parents=True, exist_ok=True) |
| 341 | (policy / "tiers.yaml").write_text("altered: true\n", encoding="utf-8") |
| 342 | code = _migrate( |
| 343 | tmp_path, |
| 344 | "config-scooling.yaml", |
| 345 | "--force", |
| 346 | "--include-preserved", |
| 347 | "--preserve-shared-assets", |
| 348 | ) |
| 349 | assert code == 0 |
| 350 | assert (policy / "tiers.yaml").read_text(encoding="utf-8") != "altered: true\n" |
| 351 | assert lock_origins(tmp_path)[".overseer/policy/tiers.yaml"] == ORIGIN_KIT |
| 352 | |
| 353 | |
| 354 | def test_classify_preserve_shared_unit() -> None: |
| 355 | from cli.commands.init import MigrateClass, _classify_migrate |
| 356 | from cli.footprint import FootprintFile |
| 357 | |
| 358 | item = FootprintFile(destination=".overseer/policy/tiers.yaml", source="x", content=b"kit\n") |
| 359 | assert ( |
| 360 | _classify_migrate( |
| 361 | item=item, |
| 362 | existing=b"hand\n", |
| 363 | is_living=False, |
| 364 | promote=False, |
| 365 | kn_r2_pass=False, |
| 366 | force=True, |
| 367 | preserve_shared=True, |
| 368 | ) |
| 369 | == MigrateClass.PRESERVED |
| 370 | ) |
| 371 | assert ( |
| 372 | _classify_migrate( |
| 373 | item=item, |
| 374 | existing=b"hand\n", |
| 375 | is_living=False, |
| 376 | promote=True, |
| 377 | kn_r2_pass=False, |
| 378 | force=True, |
| 379 | preserve_shared=True, |
| 380 | ) |
| 381 | == MigrateClass.UPDATED |
| 382 | ) |
| 383 | |
| 384 | |
| 385 | def test_preserve_shared_assets_argparse_accepted() -> None: |
| 386 | from cli.main import build_parser |
| 387 | |
| 388 | ns = build_parser().parse_args(["init", "--preserve-shared-assets", "--non-interactive"]) |
| 389 | assert ns.preserve_shared_assets is True |
| 390 | |
| 391 | |
| 392 | def test_migrate_argparse_unknown_flag() -> None: |
| 393 | from cli.main import main |
| 394 | |
| 395 | assert main(["init", "--migrate", "--not-a-real-flag"]) == 1 |
File History
2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
52 days ago