refactor: remaining longfunc small extractions after #231 (no module split)

Only pure computation/validation sub-blocks of _handle_github_webhook,
_create_followup, _deliver_to_thread and _task_chat_history are extracted;
all DB writes, audits, external calls and side-effect blocks stay in the
callers, error strings/codes and response shapes verbatim. _deliver_to_thread
uses the shared goal classifier only for /goal input: its non-goal terminal
path still goes to CAS revive (not 409) and its merged path keeps its own
wording. New tests: test_longfunc_remaining.py (15).
This commit is contained in:
host committed 2026-11-03 02:48:55 +00:00
1 parent 1accfac844
commit b4a69e1e71
2 files changed
+460 -171

No files matched your search

+208
View File
@@ -0,0 +1,208 @@
"""剩余长函数小提取单测:只钉住新抽出的纯块,不跑真实模型回合、
不联网、不连生产密钥。
覆盖(均取自原函数逐字逻辑):
- webhook:评论解析的 review 回退、fix-review prompt 模板与 2000 字截断;
- followup:请求体校验的成功/错误串、/goal 前置判定的各分支;
- deliver:非 goal 门禁(goal 任务 409、merged 409),以及关键语义——
门禁绝不拦截终态任务(终态走下面的 CAS 复活,不是 409);
- chat_history:秒->毫秒换算、用户条目、链路条目(fu- 回声跳过、
无 ts 贴上一条有 ts 的条目之后且 ts_ms 为 null)。
原路由行为不变由已有的 webhook / followup / thread / chat_history
套件覆盖,本文件不重复测路由,只测抽出子块本身。
"""
import os
import sys
import unittest
from pathlib import Path
try:
from cloud.server import (
_chat_history_trace_entries,
_chat_history_user_entries,
_classify_goal_followup,
_parse_github_comment_with_review_fallback,
_seconds_to_ms,
_thread_non_goal_guard,
_validate_followup_body,
_webhook_fix_review_prompt,
)
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.server import (
_chat_history_trace_entries,
_chat_history_user_entries,
_classify_goal_followup,
_parse_github_comment_with_review_fallback,
_seconds_to_ms,
_thread_non_goal_guard,
_validate_followup_body,
_webhook_fix_review_prompt,
)
def _task(**over):
task = {"status": "running", "goal_mode": False,
"verify_command": None, "plan": None,
"goal": None, "prompt": "p"}
task.update(over)
return task
class WebhookExtractionTestCase(unittest.TestCase):
def test_review_payload_verbatim_semantics(self):
# 原样语义(注意,不是“修”成 7):现行 parser 里 issue 缺失
# 并不使首解返回 None(只使 pr_number 为 None),故回退块
# 在当前 parser 下不可达;抽出原样保留该块,不擅自改行为。
payload = {
"action": "created",
"comment": {"body": "/fix-review 请修", "user": {"login": "a"}},
"pull_request": {"number": 7},
"repository": {"full_name": "o/r"},
}
result = _parse_github_comment_with_review_fallback(payload)
self.assertEqual(result["pr_number"], None)
self.assertEqual(result["comment"], "/fix-review 请修")
self.assertIsNone(_parse_github_comment_with_review_fallback(
{"action": "edited",
"comment": {"body": "/fix-review"},
"pull_request": {"number": 7}}))
def test_issue_comment_direct_and_non_trigger(self):
direct = {
"action": "created",
"comment": {"body": "/fix-review", "user": {"login": "a"}},
"issue": {"number": 3},
"repository": {"full_name": "o/r"},
}
self.assertEqual(
_parse_github_comment_with_review_fallback(direct)["pr_number"],
3)
self.assertIsNone(_parse_github_comment_with_review_fallback(
{"action": "created", "comment": {"body": "hello"}}))
def test_fix_review_prompt_truncates_comment(self):
result = {"repo": "o/r", "pr_number": 9, "author": "a",
"comment": "x" * 2500}
prompt = _webhook_fix_review_prompt(result)
self.assertTrue(prompt.startswith(
"[webhook /fix-review] repo=o/r pr=9 author=a\n"))
self.assertEqual(len(prompt.split("\n", 1)[1]), 2000)
class FollowupBodyTestCase(unittest.TestCase):
def test_ok_strips_and_defaults_queued(self):
text, mode, err = _validate_followup_body({"body": " hi "})
self.assertEqual((text, mode, err), ("hi", "queued", None))
def test_direct_mode_kept(self):
text, mode, err = _validate_followup_body(
{"body": "hi", "mode": "direct"})
self.assertEqual((text, mode, err), ("hi", "direct", None))
def test_body_required_variants(self):
for body in ({}, {"body": ""}, {"body": " "}, {"body": 123}):
_t, _m, err = _validate_followup_body(body)
self.assertEqual(err, (400, {"error": "body required"}))
def test_too_long_and_bad_mode(self):
_t, _m, err = _validate_followup_body({"body": "x" * 20001})
self.assertEqual(err, (400, {"error": "body too long"}))
_t, _m, err = _validate_followup_body(
{"body": "hi", "mode": "weird"})
self.assertEqual(err, (400, {
"error": "mode must be queued or direct"}))
class GoalClassifyTestCase(unittest.TestCase):
def test_goal_empty_and_mainline_and_plan(self):
self.assertEqual(
_classify_goal_followup(_task(), ""),
("error", 400, {"error": "goal 目标不能为空"}))
action, code, payload = _classify_goal_followup(
_task(goal_mode=True, goal="g"), "新目标")
self.assertEqual((action, code), ("error", 409))
self.assertEqual(payload["error"], "主线已有 goal,/goal 不会重复创建")
self.assertEqual(payload["goal"], "g")
self.assertEqual(
_classify_goal_followup(_task(plan="p"), "新目标"),
("error", 400, {"error": "plan 模式与 goal 模式冲突"}))
def test_goal_merged_queued_terminal_running(self):
self.assertEqual(
_classify_goal_followup(_task(status="merged"), "新目标")[0],
"error")
self.assertEqual(
_classify_goal_followup(_task(status="queued"), "新目标")[0],
"convert_queued")
self.assertEqual(
_classify_goal_followup(_task(status="completed"), "新目标")[0],
"revive_terminal")
self.assertEqual(
_classify_goal_followup(_task(status="running"), "新目标")[0],
"continue")
def test_non_goal_only_queued_or_running_continues(self):
self.assertEqual(
_classify_goal_followup(_task(status="queued"), None)[0],
"continue")
action, code, payload = _classify_goal_followup(
_task(status="completed"), None)
self.assertEqual((action, code), ("error", 409))
self.assertEqual(payload["error"], "后续消息仅支持进行中任务")
class ThreadGuardTestCase(unittest.TestCase):
def test_goal_task_and_merged_blocked(self):
self.assertEqual(
_thread_non_goal_guard(_task(verify_command="v")),
(409, {"error": "goal 任务暂不支持后续消息"}))
self.assertEqual(
_thread_non_goal_guard(_task(status="merged"))[0], 409)
def test_terminal_not_blocked_by_guard(self):
# 关键:门禁只拦 goal/merged,终态任务必须放行到 CAS 复活。
self.assertIsNone(_thread_non_goal_guard(_task(status="completed")))
self.assertIsNone(_thread_non_goal_guard(_task(status="running")))
class ChatHistoryExtractionTestCase(unittest.TestCase):
def test_seconds_to_ms(self):
self.assertEqual(_seconds_to_ms(1.5), 1500)
self.assertEqual(_seconds_to_ms(0), 0)
self.assertIsNone(_seconds_to_ms(None))
self.assertIsNone(_seconds_to_ms("bad"))
def test_user_entries(self):
task = {"prompt": "做一件事", "created_at": 10.0}
cont = [{"id": "c1", "task_id": "t", "project_id": "p",
"body": "继续", "created_by": "u", "created_at": 11.0}]
fu = [{"id": "f1", "task_id": "t", "project_id": "p",
"body": "后续", "mode": "queued", "status": "pending",
"created_by": "u", "sender_task_id": None,
"created_at": 12.0, "updated_at": 12.0}]
entries = _chat_history_user_entries(task, cont, fu)
self.assertEqual(
[e[2]["id"] for e in entries],
["task-prompt", "continue-c1", "followup-f1"])
self.assertEqual(entries[0][2]["ts_ms"], 10000)
self.assertEqual(entries[2][2]["label"], "后续消息")
def test_trace_entries_skip_echo_and_inherit_ts(self):
entries = _chat_history_trace_entries([
{"id": "fu-abc", "kind": "message", "text": "回声"},
"not-a-dict",
{"id": "m1", "kind": "message", "text": "你好", "ts": 2000},
{"id": "m2", "kind": "message", "text": "无 ts"},
], start_ms=1000)
self.assertEqual([e[2]["id"] for e in entries], ["m1", "m2"])
self.assertEqual(entries[0][1], 2000)
self.assertEqual(entries[0][2]["ts_ms"], 2000)
self.assertEqual(entries[1][1], 2000) # 贴在上一条有 ts 之后
self.assertIsNone(entries[1][2]["ts_ms"])
if __name__ == "__main__":
unittest.main()