mirror of
https://github.com/1c7/chinese-independent-developer.git
synced 2026-08-16 18:13:28 +08:00
fix: guard fallback submissions (#1082)
This commit is contained in:
70
.github/scripts/process_item.py
vendored
70
.github/scripts/process_item.py
vendored
@@ -89,47 +89,51 @@ def check_reactions(item):
|
||||
"""检查对象(Issue 或 IssueComment)是否有触发表情且没有成功标记"""
|
||||
reactions = item.get_reactions()
|
||||
has_trigger = any(r.content == TRIGGER_EMOJI and r.user.login == ADMIN_HANDLE for r in reactions)
|
||||
has_success = any(r.content == SUCCESS_EMOJI for r in reactions)
|
||||
has_success = any(
|
||||
r.content == SUCCESS_EMOJI and r.user.login == ADMIN_HANDLE
|
||||
for r in reactions
|
||||
)
|
||||
return has_trigger, has_success
|
||||
|
||||
def collect_pending_items(repo, now=None):
|
||||
"""收集管理员标记的 Issue 和评论,排除 Pull Request。"""
|
||||
pending_items = [] # 存储 (item_object, parent_issue_object)
|
||||
current_time = now or datetime.now(timezone.utc)
|
||||
|
||||
issue160 = repo.get_issue(ISSUE_NUMBER)
|
||||
time_threshold = current_time - timedelta(days=3)
|
||||
comments160 = issue160.get_comments(since=time_threshold)
|
||||
for comment in comments160:
|
||||
has_t, has_s = check_reactions(comment)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((comment, issue160))
|
||||
|
||||
comment_time_threshold = current_time - timedelta(days=7)
|
||||
for issue in repo.get_issues(state='open'):
|
||||
if issue.number == ISSUE_NUMBER or issue.pull_request is not None:
|
||||
continue
|
||||
|
||||
has_t, has_s = check_reactions(issue)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((issue, issue))
|
||||
|
||||
comments = issue.get_comments(since=comment_time_threshold)
|
||||
for comment in comments:
|
||||
has_t, has_s = check_reactions(comment)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((comment, issue))
|
||||
|
||||
return pending_items
|
||||
|
||||
def main():
|
||||
# 检查环境变量
|
||||
check_environment()
|
||||
|
||||
g = Github(PAT_TOKEN)
|
||||
repo = g.get_repo(REPO_NAME)
|
||||
|
||||
|
||||
# ===== 阶段 1:收集待处理项 (Issue 160 评论 + 其他 Open Issue) =====
|
||||
pending_items = [] # 存储 (item_object, parent_issue_object)
|
||||
|
||||
# 1.1 处理 Issue 160 的评论 (Legacy)
|
||||
issue160 = repo.get_issue(ISSUE_NUMBER)
|
||||
time_threshold = datetime.now(timezone.utc) - timedelta(days=3)
|
||||
comments160 = issue160.get_comments(since=time_threshold)
|
||||
for comment in comments160:
|
||||
has_t, has_s = check_reactions(comment)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((comment, issue160))
|
||||
|
||||
# 1.2 扫描所有其他 Open Issue
|
||||
open_issues = repo.get_issues(state='open')
|
||||
comment_time_threshold = datetime.now(timezone.utc) - timedelta(days=7)
|
||||
|
||||
for issue in open_issues:
|
||||
if issue.number == ISSUE_NUMBER:
|
||||
continue
|
||||
|
||||
# 1. 检查 Issue Body
|
||||
has_t, has_s = check_reactions(issue)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((issue, issue))
|
||||
|
||||
# 2. 检查最近 7 天的所有评论
|
||||
comments = issue.get_comments(since=comment_time_threshold)
|
||||
for comment in comments:
|
||||
has_t, has_s = check_reactions(comment)
|
||||
if has_t and not has_s:
|
||||
pending_items.append((comment, issue))
|
||||
pending_items = collect_pending_items(repo)
|
||||
|
||||
if not pending_items:
|
||||
print("无待处理项")
|
||||
@@ -277,4 +281,4 @@ def main():
|
||||
print(f"\n✅ 已在 {len(replies)} 个 Issue 中标记并回复")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
119
tests/test_process_item.py
Normal file
119
tests/test_process_item.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
SCRIPT_PATH = Path(__file__).parents[1] / ".github" / "scripts" / "process_item.py"
|
||||
|
||||
|
||||
def load_script():
|
||||
github_module = types.ModuleType("github")
|
||||
github_module.Github = object
|
||||
openai_module = types.ModuleType("openai")
|
||||
openai_module.OpenAI = object
|
||||
spec = importlib.util.spec_from_file_location("process_item", SCRIPT_PATH)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
|
||||
with patch.dict(sys.modules, {"github": github_module, "openai": openai_module}):
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self, login):
|
||||
self.login = login
|
||||
|
||||
|
||||
class Reaction:
|
||||
def __init__(self, content, login):
|
||||
self.content = content
|
||||
self.user = User(login)
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, reactions=(), comments=(), pull_request=None, number=1):
|
||||
self._reactions = reactions
|
||||
self._comments = comments
|
||||
self.pull_request = pull_request
|
||||
self.number = number
|
||||
|
||||
def get_reactions(self):
|
||||
return self._reactions
|
||||
|
||||
def get_comments(self, since):
|
||||
return self._comments
|
||||
|
||||
|
||||
class PullRequestItem(Item):
|
||||
def get_reactions(self):
|
||||
raise AssertionError("pull requests must not be scanned as issues")
|
||||
|
||||
def get_comments(self, since):
|
||||
raise AssertionError("pull request comments must not be scanned as issues")
|
||||
|
||||
|
||||
class Repository:
|
||||
def __init__(self, issue160, issues):
|
||||
self.issue160 = issue160
|
||||
self.issues = issues
|
||||
|
||||
def get_issue(self, number):
|
||||
return self.issue160
|
||||
|
||||
def get_issues(self, state):
|
||||
return self.issues
|
||||
|
||||
|
||||
class ProcessItemTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.script = load_script()
|
||||
|
||||
def test_contributor_success_reaction_does_not_suppress_submission(self):
|
||||
item = Item((
|
||||
Reaction("rocket", "1c7"),
|
||||
Reaction("hooray", "contributor"),
|
||||
))
|
||||
|
||||
self.assertEqual(self.script.check_reactions(item), (True, False))
|
||||
|
||||
def test_admin_success_reaction_marks_submission_complete(self):
|
||||
item = Item((
|
||||
Reaction("rocket", "1c7"),
|
||||
Reaction("hooray", "1c7"),
|
||||
))
|
||||
|
||||
self.assertEqual(self.script.check_reactions(item), (True, True))
|
||||
|
||||
def test_scanner_skips_pull_requests(self):
|
||||
issue160 = Item(number=160)
|
||||
pull_request = PullRequestItem(
|
||||
pull_request={"url": "https://api.github.com/repos/1c7/chinese-independent-developer/pulls/1"},
|
||||
number=1,
|
||||
)
|
||||
repository = Repository(issue160, (pull_request,))
|
||||
|
||||
pending_items = self.script.collect_pending_items(
|
||||
repository, datetime(2026, 7, 14, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(pending_items, [])
|
||||
|
||||
def test_scanner_keeps_admin_marked_issues(self):
|
||||
issue160 = Item(number=160)
|
||||
issue = Item(reactions=(Reaction("rocket", "1c7"),), number=2)
|
||||
repository = Repository(issue160, (issue,))
|
||||
|
||||
pending_items = self.script.collect_pending_items(
|
||||
repository, datetime(2026, 7, 14, tzinfo=timezone.utc)
|
||||
)
|
||||
|
||||
self.assertEqual(pending_items, [(issue, issue)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user