test_muse_git_mirror.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Unit tests for muse+git-mirror adapter fail-closed branches.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | from adapters.errors import ReadError |
| 8 | from tests.support import adapter_for, fail, make_runner, ok |
| 9 | |
| 10 | |
| 11 | def _bridge_toml( |
| 12 | from_sha: str = "aaa111", |
| 13 | export_sha: str = "bbb222", |
| 14 | *, |
| 15 | muse_commit_id: str | None = "sha256:exportmuse", |
| 16 | ) -> str: |
| 17 | export_muse = "" |
| 18 | if muse_commit_id is not None: |
| 19 | export_muse = f'muse_commit_id = "{muse_commit_id}"\n' |
| 20 | return f"""[last_import] |
| 21 | git_sha = "{from_sha}" |
| 22 | |
| 23 | [last_export] |
| 24 | {export_muse}git_sha = "{export_sha}" |
| 25 | """ |
| 26 | |
| 27 | |
| 28 | def test_status_reads_both_histories(muse_git_mirror_config, repo_root) -> None: |
| 29 | root = str(repo_root) |
| 30 | runner = make_runner( |
| 31 | { |
| 32 | f"muse -C {root} rev-parse --abbrev-ref HEAD": ok("main"), |
| 33 | f"muse -C {root} status --json": ok('{"dirty": false}'), |
| 34 | f"muse -C {root} status --porcelain": ok(""), |
| 35 | "git rev-parse": ok("main"), |
| 36 | "git status": ok(" M file"), |
| 37 | } |
| 38 | ) |
| 39 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 40 | result = adapter.status() |
| 41 | assert result.dirty is True |
| 42 | assert "sd-14" in result.notes[2] |
| 43 | |
| 44 | |
| 45 | def test_read_canonical_anchor_from_bridge(muse_git_mirror_config, repo_root) -> None: |
| 46 | bridge = repo_root / ".muse" |
| 47 | bridge.mkdir(parents=True) |
| 48 | (bridge / "git-bridge.toml").write_text(_bridge_toml(), encoding="utf-8") |
| 49 | adapter = adapter_for(muse_git_mirror_config, repo_root, make_runner({})) |
| 50 | result = adapter.read_canonical_anchor() |
| 51 | assert result.anchor_sha == "sha256:exportmuse" |
| 52 | assert result.source.endswith("muse_commit_id") |
| 53 | assert result.anchor_sha != "bbb222" |
| 54 | |
| 55 | |
| 56 | def test_read_canonical_anchor_missing_muse_commit_id(muse_git_mirror_config, repo_root) -> None: |
| 57 | bridge = repo_root / ".muse" |
| 58 | bridge.mkdir(parents=True) |
| 59 | (bridge / "git-bridge.toml").write_text( |
| 60 | _bridge_toml(muse_commit_id=None), encoding="utf-8" |
| 61 | ) |
| 62 | adapter = adapter_for(muse_git_mirror_config, repo_root, make_runner({})) |
| 63 | result = adapter.read_canonical_anchor() |
| 64 | assert isinstance(result, ReadError) |
| 65 | assert "muse_commit_id" in str(result) |
| 66 | |
| 67 | |
| 68 | def test_read_canonical_anchor_fails_without_bridge(muse_git_mirror_config, repo_root) -> None: |
| 69 | runner = make_runner({"git rev-parse": fail("unknown ref")}) |
| 70 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 71 | result = adapter.read_canonical_anchor() |
| 72 | assert isinstance(result, ReadError) |
| 73 | |
| 74 | |
| 75 | def test_realign_dry_run_counts_commits(muse_git_mirror_config, repo_root) -> None: |
| 76 | bridge = repo_root / ".muse" |
| 77 | bridge.mkdir(parents=True) |
| 78 | (bridge / "git-bridge.toml").write_text(_bridge_toml(from_sha="aaa"), encoding="utf-8") |
| 79 | runner = make_runner( |
| 80 | { |
| 81 | "git rev-parse": ok("ccc333"), |
| 82 | "git rev-list": ok("3"), |
| 83 | } |
| 84 | ) |
| 85 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 86 | result = adapter.realign(dry_run=True, max_commits=50) |
| 87 | assert result.would_import == 3 |
| 88 | assert result.applied is False |
| 89 | assert result.reason == "dry-run" |
| 90 | |
| 91 | |
| 92 | def test_realign_refuses_over_max_commits(muse_git_mirror_config, repo_root) -> None: |
| 93 | bridge = repo_root / ".muse" |
| 94 | bridge.mkdir(parents=True) |
| 95 | (bridge / "git-bridge.toml").write_text(_bridge_toml(), encoding="utf-8") |
| 96 | runner = make_runner( |
| 97 | { |
| 98 | "git rev-parse": ok("ccc333"), |
| 99 | "git rev-list": ok("99"), |
| 100 | } |
| 101 | ) |
| 102 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 103 | result = adapter.realign(dry_run=False, max_commits=50) |
| 104 | assert result.would_import == 99 |
| 105 | assert result.applied is False |
| 106 | assert "max_commits" in (result.reason or "") |
| 107 | |
| 108 | |
| 109 | def test_realign_apply_invokes_git_import(muse_git_mirror_config, repo_root) -> None: |
| 110 | bridge = repo_root / ".muse" |
| 111 | bridge.mkdir(parents=True) |
| 112 | (bridge / "git-bridge.toml").write_text(_bridge_toml(), encoding="utf-8") |
| 113 | root = str(repo_root) |
| 114 | runner = make_runner( |
| 115 | { |
| 116 | "git rev-parse": ok("ccc333"), |
| 117 | "git rev-list": ok("2"), |
| 118 | f"muse -C {root} bridge": ok("imported"), |
| 119 | } |
| 120 | ) |
| 121 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 122 | result = adapter.realign(dry_run=False, max_commits=50) |
| 123 | assert result.applied is True |
| 124 | assert any("git-import" in call[0] for call in runner.calls) |
| 125 | |
| 126 | |
| 127 | def test_mirror_dry_run_reports_delta(muse_git_mirror_config, repo_root) -> None: |
| 128 | root = str(repo_root) |
| 129 | runner = make_runner({f"muse -C {root} bridge": ok("2 commits ahead")}) |
| 130 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 131 | result = adapter.mirror(dry_run=True) |
| 132 | assert result.diff_summary == "2 commits ahead" |
| 133 | assert result.pushed is False |
| 134 | |
| 135 | |
| 136 | def test_mirror_requires_operator_before_push(muse_git_mirror_config, repo_root) -> None: |
| 137 | root = str(repo_root) |
| 138 | runner = make_runner({f"muse -C {root} bridge": ok("ready")}) |
| 139 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 140 | result = adapter.mirror(dry_run=False) |
| 141 | assert result.pushed is False |
| 142 | assert result.reason == "operator-authorization-required" |
| 143 | |
| 144 | |
| 145 | def test_commit_feature_uses_rev_parse_for_branch_probe(muse_git_mirror_config, repo_root) -> None: |
| 146 | """muse 0.2.0rc15 lacks ``muse log --format=%H``; use ``muse rev-parse`` (returns plain SHA on success).""" |
| 147 | root = str(repo_root) |
| 148 | runner = make_runner( |
| 149 | { |
| 150 | f"muse -C {root} checkout": ok(""), |
| 151 | f"muse -C {root} rev-parse --abbrev-ref HEAD": ok("feat/k7"), |
| 152 | f"muse -C {root} commit": ok(""), |
| 153 | f"muse -C {root} rev-parse HEAD": ok('sha256:abc'), |
| 154 | } |
| 155 | ) |
| 156 | adapter = adapter_for(muse_git_mirror_config, repo_root, runner) |
| 157 | result = adapter.commit_feature(branch="feat/k7", message="test", paths=[]) |
| 158 | assert result.committed is True |
| 159 | assert any("rev-parse --abbrev-ref HEAD" in call[0] for call in runner.calls) |
| 160 | assert not any("log -1 --format" in call[0] for call in runner.calls) |
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