test: blocked-check 用 _req_safe 替代 _req 避免 API 错误误判 - Closes #58

- 新增 _req_safe():API 错误返回 None 而非 sys.exit(1)
- blocked_check / _unblock_issues_blocked_by / _get_blocking_refs 改用 _req_safe
- API 失败时保守处理:保持 blocked 状态(不误解除)
- 验证:#18 正确识别被 #57 阻塞

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 16:17:39 +08:00
parent 268520d453
commit d73da7cda9
+35 -17
View File
@@ -56,6 +56,27 @@ def _req(method, path, data=None):
sys.exit(1)
def _req_safe(method, path, data=None):
"""Like _req but returns None on HTTPError instead of crashing.
Used for probing issue/PR existence where the caller can handle absence.
"""
url = f"{BASE}{path}"
payload = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=payload, method=method)
req.add_header("Authorization", f"token {GITEA_TOKEN}")
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req) as resp:
raw = resp.read()
if not raw:
return {}
return json.loads(raw)
except urllib.error.HTTPError as e:
body = e.read().decode()
print(f"API Error {e.code}: {body}", file=sys.stderr)
return None
# ── Issue operations ─────────────────────────────────────────────────────────
def list_issues(labels: list[str] | None = None):
@@ -82,17 +103,17 @@ def _get_blocking_refs(issue_num: int) -> set[int]:
"""
refs: set[int] = set()
# Body
issue = _req("GET", f"/issues/{issue_num}")
issue = _req_safe("GET", f"/issues/{issue_num}")
if issue is None:
return refs # API error → return empty set, keep blocked
body = issue.get("body", "") or ""
refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', body))
# Comments
try:
comments = _req("GET", f"/issues/{issue_num}/comments")
comments = _req_safe("GET", f"/issues/{issue_num}/comments")
if comments:
for c in comments:
cbody = c.get("body", "") or ""
refs.update(int(m.group(1)) for m in re.finditer(r'#(\d+)', cbody))
except SystemExit:
pass
return refs
@@ -119,13 +140,13 @@ def blocked_check():
all_resolved = True
for blk in blocking_nums:
try:
blk_issue = _req("GET", f"/issues/{blk}")
blk_issue = _req_safe("GET", f"/issues/{blk}")
if blk_issue is None:
all_resolved = False # API error → keep blocked
break
if blk_issue.get("state") != "closed":
all_resolved = False
break
except SystemExit:
pass
if all_resolved:
current_label_names = [l["name"] for l in issue.get("labels", [])]
@@ -179,10 +200,7 @@ def _unblock_issues_blocked_by(closed_num):
in any blocked issue and all referenced issues are now closed,
removes the 'blocked' label and comments on the unblocked issue.
"""
try:
all_blocked = _req("GET", "/issues?state=open&labels=blocked")
except SystemExit:
return
all_blocked = _req_safe("GET", "/issues?state=open&labels=blocked")
if not all_blocked:
return
@@ -196,13 +214,13 @@ def _unblock_issues_blocked_by(closed_num):
for blk in blocking_nums:
if blk == closed_num:
continue
try:
blk_issue = _req("GET", f"/issues/{blk}")
blk_issue = _req_safe("GET", f"/issues/{blk}")
if blk_issue is None:
all_resolved = False # API error → keep blocked
break
if blk_issue.get("state") != "closed":
all_resolved = False
break
except SystemExit:
pass # Inaccessible → treat as resolved
if all_resolved:
current_label_names = [l["name"] for l in issue.get("labels", [])]