diff --git a/.github/scripts/process_item.py b/.github/scripts/process_item.py index aed243e..5f30f0e 100644 --- a/.github/scripts/process_item.py +++ b/.github/scripts/process_item.py @@ -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() \ No newline at end of file + main() diff --git a/tests/test_process_item.py b/tests/test_process_item.py new file mode 100644 index 0000000..c723bce --- /dev/null +++ b/tests/test_process_item.py @@ -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()