diff --git a/arbiter/backend.py b/arbiter/backend.py index e84c73c..fcf12c3 100644 --- a/arbiter/backend.py +++ b/arbiter/backend.py @@ -114,16 +114,16 @@ def delete_ref(self, ref: str) -> None: raise NotFoundError(f"ref 不存在: {ref}") raise InfraError(f"deleteRef HTTP {status}: {payload.get('message', '')}") + # 经典空树 SHA(git mktree str: """空树 commit,message 内嵌 JSON(租约/台账载体,ADR-0054 §2)。""" - status, payload = self._request( - "POST", f"/repos/{self._repo}/git/trees", {}) - if status != 201: - raise InfraError(f"createTree HTTP {status}: {payload.get('message', '')}") - tree = payload.get("sha") - if not tree: - raise InfraError(f"createTree 未返回 sha: {payload}") - body = {"message": message, "tree": tree} + body = {"message": message, "tree": self.EMPTY_TREE_SHA} if parent: body["parents"] = [parent] status, payload = self._request( diff --git a/tests/test_github_backend_wire.py b/tests/test_github_backend_wire.py new file mode 100644 index 0000000..470c7d9 --- /dev/null +++ b/tests/test_github_backend_wire.py @@ -0,0 +1,66 @@ +"""GitHubRefBackend 请求形状回归测试(W1-C2 补充)。 + +背景:create_commit 原实现先 POST /git/trees 空 body 建"空树",但 GitHub 对 +空树创建一律 422 "Invalid tree info"(e2e .github#206 演习实测, +conductor run 32493680834)——该路径在真实 API 上从未成功,单测因走 +LocalGitBackend 而未暴露。本测试在 HTTP 请求层(mock _request)锁定请求形状, +防再次引入"创建空树"调用。 +""" + +import unittest +from unittest.mock import patch + +from arbiter.backend import GitHubRefBackend + + +def make_backend(): + return GitHubRefBackend(token="t-test", repo="o/r") + + +class TestCreateCommitWire(unittest.TestCase): + def test_create_commit_uses_canonical_empty_tree_no_trees_call(self): + """create_commit 不得调用 /git/trees;commit 请求体 tree=经典空树 SHA。""" + calls = [] + + def fake_request(method, path, body=None): + calls.append((method, path, body)) + if path.endswith("/git/commits"): + return 201, {"sha": "c0ffee"} + raise AssertionError(f"意外调用: {method} {path}") + + b = make_backend() + with patch.object(b, "_request", side_effect=fake_request): + sha = b.create_commit("lease-meta-json", parent=None) + self.assertEqual(sha, "c0ffee") + self.assertEqual(len(calls), 1, "create_commit 应只发一次 POST /git/commits") + method, path, body = calls[0] + self.assertEqual(method, "POST") + self.assertTrue(path.endswith("/git/commits")) + self.assertEqual(body["tree"], GitHubRefBackend.EMPTY_TREE_SHA) + self.assertEqual(body["message"], "lease-meta-json") + self.assertNotIn("parents", body) # 无父提交(新租约) + + def test_create_commit_with_parent(self): + calls = [] + + def fake_request(method, path, body=None): + calls.append((method, path, body)) + return 201, {"sha": "beef"} + + b = make_backend() + with patch.object(b, "_request", side_effect=fake_request): + sha = b.create_commit("renew", parent="aaa") + self.assertEqual(sha, "beef") + self.assertEqual(calls[0][2]["parents"], ["aaa"]) # 接管=以旧租约为父 + + def test_empty_tree_sha_is_the_git_canonical_constant(self): + # 4b825dc…是空树的恒定 SHA(git mktree