Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions arbiter/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 </dev/null 的恒定值)——租约/台账 commit 不携带
# 任何文件内容,元数据全在 commit message。e2e 实测(.github#206 演习,
# conductor run 32493680834):POST /git/trees 对空 body {}/{"tree":[]}
# 一律 422 "Invalid tree info"(GitHub 不接受创建空树),该路径在真实 API
# 上从未成功过——单测走 LocalGitBackend 故未暴露。直接引用空树 SHA 即可。
EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"

def create_commit(self, message: str, parent: str | None = None) -> 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(
Expand Down
66 changes: 66 additions & 0 deletions tests/test_github_backend_wire.py
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +10 to +13


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"}

Comment on lines +46 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Parent 分支未锁请求形状 🐞 Bug ⚙ Maintainability

tests/test_github_backend_wire.py 的 parent 用例未断言只发生一次请求、且请求路径必须是 /git/commits,因此未来若仅在 parent
分支回归引入额外调用(例如再调用 /git/trees)可能不会被该用例稳定捕获。该测试文件的意图是“HTTP 请求形状回归锁定”,建议对 parent 场景同样加上请求次数与路径断言。
Agent Prompt
## Issue description
`test_create_commit_with_parent` 目前只断言了 `parents` 透传,但没有锁定:
- 仅一次 HTTP 调用
- 调用路径必须是 `.../git/commits`
- 不允许任何额外调用(例如回归引入 `/git/trees`)

这与该文件的“请求形状回归测试”目标不完全一致。

## Issue Context
`create_commit` 存在 `if parent:` 分支(parent 场景与非 parent 场景可能在未来被人改出差异),因此需要在 parent 用例里也做同等强度的 wire-level 断言。

## Fix Focus Areas
- tests/test_github_backend_wire.py[43-55]
- arbiter/backend.py[126-129]

## Suggested change
在 `test_create_commit_with_parent` 中:
1) 让 `fake_request` 对非 `/git/commits` 的调用直接 `AssertionError`(与第一个用例一致)。
2) 在调用后断言 `len(calls) == 1`。
3) 断言 `method == "POST"` 且 `path.endswith("/git/commits")`。
4) 同时断言 `body["tree"] == GitHubRefBackend.EMPTY_TREE_SHA`,确保 parent 场景也使用空树常量。

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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 </dev/null),任何 git 环境可复验;
# 若此断言失败说明有人改动了常量——需附带真实 API 证据再改
self.assertEqual(
GitHubRefBackend.EMPTY_TREE_SHA,
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
)


if __name__ == "__main__":
unittest.main()
Loading