gabriel / muse public
test_push_force_flag.py python
233 lines 8.2 KB
Raw
sha256:79ffe87f5fe2ec146e35f05521218bbf54dffdb0440c07f970bad05f16efb89f chore: merge main — carry all urllib/typing/test fixes from dev Sonnet 4.6 minor ⚠ breaking 21 days ago
1 """TDD — --force flag must appear in the unpack-mpack request body.
2
3 Gap 2: _run_mpack_path() never includes "force" in the unpack body. The
4 server receives the unpack request with no force field and has no way to
5 distinguish a force push from a normal push.
6
7 Test plan
8 ---------
9 F1 (RED) _push_mpack with force=True sends {"force": True} in the
10 unpack-mpack POST body.
11 F2 (RED) _push_mpack with force=False sends {"force": False} (or omits it
12 and server defaults to False).
13 F3 _push_mpack without --force does NOT set force=True by accident.
14 F4 The "force" key is present in the unpack body regardless of the
15 force value (protocol stability).
16 """
17 from __future__ import annotations
18
19 import asyncio
20 import datetime
21 import json
22 import pathlib
23 from unittest.mock import MagicMock, patch
24
25 import msgpack
26 import pytest
27
28 from muse._version import __version__
29 from muse.core.mpack import PushResult, RemoteInfo
30 from muse.core.object_store import write_object
31 from muse.core.paths import heads_dir, muse_dir
32
33 _Headers = dict[str, str] # HTTP header map
34 _JsonDict = dict[str, str | int | float | bool | None | list[str]] # JSON object
35 from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id
36 from muse.core.commits import (
37 CommitRecord,
38 write_commit,
39 )
40 from muse.core.snapshots import (
41 SnapshotRecord,
42 write_snapshot,
43 )
44 from muse.core.types import Manifest, blob_id
45
46
47 # ---------------------------------------------------------------------------
48 # Helpers
49 # ---------------------------------------------------------------------------
50
51 def _bare_repo(tmp_path: pathlib.Path) -> pathlib.Path:
52 muse = muse_dir(tmp_path)
53 for d in ("commits", "snapshots", "objects", "refs/heads", "remotes"):
54 (muse / d).mkdir(parents=True, exist_ok=True)
55 (muse / "HEAD").write_text("ref: refs/heads/main\n")
56 (muse / "repo.json").write_text(
57 json.dumps({"repo_id": "test-repo", "schema_version": __version__, "domain": "code"})
58 )
59 (muse / "config.toml").write_text('[remotes.origin]\nurl = "https://hub.example.com/r"\n')
60 return tmp_path
61
62
63 def _make_commit(
64 root: pathlib.Path,
65 label: str,
66 parent_id: str | None = None,
67 content: bytes | None = None,
68 ) -> CommitRecord:
69 raw = content if content is not None else f"content-{label}".encode()
70 oid = blob_id(raw)
71 write_object(root, oid, raw)
72 manifest: Manifest = {"file.txt": oid}
73 snap_id = compute_snapshot_id(manifest)
74 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
75 committed_at = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
76 parent_ids = [parent_id] if parent_id else []
77 cid = compute_commit_id(
78 parent_ids=parent_ids,
79 snapshot_id=snap_id,
80 message=f"commit {label}",
81 committed_at_iso=committed_at.isoformat(),
82 )
83 write_commit(root, CommitRecord(
84 commit_id=cid,
85 branch="main",
86 snapshot_id=snap_id,
87 message=f"commit {label}",
88 committed_at=committed_at,
89 parent_commit_id=parent_id,
90 ))
91 return CommitRecord(
92 commit_id=cid,
93 branch="main",
94 snapshot_id=snap_id,
95 message=f"commit {label}",
96 committed_at=committed_at,
97 parent_commit_id=parent_id,
98 )
99
100
101 def _fake_resp(body: bytes, status: int = 200) -> MagicMock:
102 r = MagicMock()
103 r.status_code = status
104 r.content = body
105 r.headers = {"content-type": "application/x-msgpack"}
106 r.text = ""
107 return r
108
109
110 def _run_push_mpack(
111 root: pathlib.Path,
112 local_head: str,
113 remote_head: str | None,
114 force: bool,
115 ) -> MagicMock:
116 """Run _push_mpack with a fake transport and return the transport mock.
117
118 Callers inspect transport.push_mpack_unpack.call_args to verify the
119 force flag was passed correctly.
120 """
121 from muse.cli.commands.push import _push_mpack
122
123 transport = MagicMock()
124 transport.fetch_remote_info.return_value = RemoteInfo(
125 domain="code",
126 default_branch="main",
127 branch_heads={"main": remote_head} if remote_head else {},
128 )
129 mock_req = MagicMock()
130 mock_req.headers = {"Authorization": "MSign stub", "Content-Type": "application/x-msgpack"}
131 transport._build_request.return_value = mock_req
132 transport.push_mpack_presign.return_value = {
133 "upload_url": "https://minio.example.com/put?sig=x",
134 "mpack_key": "sha256:fake",
135 }
136 transport.push_mpack_put.return_value = None
137 transport.push_mpack_unpack.return_value = {
138 "job_id": "j", "head": local_head, "branch": "main",
139 "blobs_in_mpack": 0, "commits_in_mpack": 0,
140 }
141
142 _push_mpack(
143 transport,
144 "https://hub.example.com/repos/r1",
145 None,
146 root,
147 local_head,
148 [],
149 "main",
150 force,
151 )
152
153 return transport
154
155
156 # ---------------------------------------------------------------------------
157 # F1 — force=True appears in unpack body (currently RED)
158 # ---------------------------------------------------------------------------
159
160 def test_f1_force_true_sent_in_unpack_body(
161 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
162 ) -> None:
163 """_push_mpack(force=True) must send {"force": True} in the unpack body.
164
165 This is currently RED: _run_mpack_path does not include "force" in the
166 unpack payload.
167 """
168 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
169 monkeypatch.chdir(tmp_path)
170 root = _bare_repo(tmp_path)
171 commit = _make_commit(root, "f1", content=b"force flag test")
172 (heads_dir(root) / "main").write_text(commit.commit_id)
173
174 transport = _run_push_mpack(root, commit.commit_id, remote_head=None, force=True)
175
176 assert transport.push_mpack_unpack.called, "push_mpack_unpack was not called"
177 kwargs = transport.push_mpack_unpack.call_args.kwargs
178 assert "force" in kwargs, (
179 f"'force' kwarg missing from push_mpack_unpack call. Got: {list(kwargs.keys())}"
180 )
181 assert kwargs["force"] is True, (
182 f"Expected force=True, got {kwargs['force']!r}"
183 )
184
185
186 # ---------------------------------------------------------------------------
187 # F2 — force=False is also sent explicitly (currently RED)
188 # ---------------------------------------------------------------------------
189
190 def test_f2_force_false_sent_in_unpack_body(
191 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
192 ) -> None:
193 """_push_mpack(force=False) must send {"force": False} in the unpack body."""
194 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
195 monkeypatch.chdir(tmp_path)
196 root = _bare_repo(tmp_path)
197 commit = _make_commit(root, "f2", content=b"no force test")
198 (heads_dir(root) / "main").write_text(commit.commit_id)
199
200 transport = _run_push_mpack(root, commit.commit_id, remote_head=None, force=False)
201
202 assert transport.push_mpack_unpack.called, "push_mpack_unpack was not called"
203 kwargs = transport.push_mpack_unpack.call_args.kwargs
204 assert "force" in kwargs, (
205 f"'force' kwarg missing from push_mpack_unpack call. Got: {list(kwargs.keys())}"
206 )
207 assert kwargs["force"] is False, (
208 f"Expected force=False, got {kwargs['force']!r}"
209 )
210
211
212 # ---------------------------------------------------------------------------
213 # F3 — "force" key is always present regardless of value
214 # ---------------------------------------------------------------------------
215
216 def test_f3_force_key_present_in_all_pushes(
217 tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
218 ) -> None:
219 """The 'force' key must always be in the unpack body (protocol stability)."""
220 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
221 monkeypatch.chdir(tmp_path)
222 root = _bare_repo(tmp_path)
223 commit = _make_commit(root, "f3", content=b"always force key")
224 (heads_dir(root) / "main").write_text(commit.commit_id)
225
226 for force_value in (True, False):
227 transport = _run_push_mpack(root, commit.commit_id, remote_head=None, force=force_value)
228 assert transport.push_mpack_unpack.called, f"force={force_value}: push_mpack_unpack not called"
229 kwargs = transport.push_mpack_unpack.call_args.kwargs
230 assert "force" in kwargs, f"force={force_value}: 'force' missing from push_mpack_unpack kwargs"
231 assert isinstance(kwargs["force"], bool), (
232 f"force={force_value}: kwargs['force'] must be bool, got {type(kwargs['force'])}"
233 )
File History 2 commits
sha256:79ffe87f5fe2ec146e35f05521218bbf54dffdb0440c07f970bad05f16efb89f chore: merge main — carry all urllib/typing/test fixes from dev Sonnet 4.6 minor 21 days ago
sha256:0bea7600d1eee83e87950be49933b1006fa9dc2c71e7c4ee748d324f61138156 chore: bump version to 0.2.0rc11; fix typing audit violatio… Sonnet 4.6 minor 21 days ago