"""主线 /goal 命令单测:发给主线的一句话自动建 goal。 不跑真实模型回合、不联网:server 侧走本地临时端口 + 临时 SQLite;worker 侧注入假 runner。覆盖:解析、followup 的 queued/running/已终态三态、已是 goal、空目标、plan 冲突、 普通消息不受影响、/continue 与 /thread/message 同款转换、 worker 在轮次边界把 running 主线转成 goal 并在下次认领走 goal-resume。 """ import http.client import json import os import sqlite3 import sys import tempfile import threading import time import unittest from pathlib import Path from types import SimpleNamespace try: from cloud.auth import AuthStore from cloud.goal_command import parse_goal_command from cloud.peer_messages import ensure_followups_table from cloud.server import create_server, init_db from cloud.worker import Worker except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.auth import AuthStore from cloud.goal_command import parse_goal_command from cloud.peer_messages import ensure_followups_table from cloud.server import create_server, init_db from cloud.worker import Worker class ParserTestCase(unittest.TestCase): def test_user_sentence(self): self.assertEqual( parse_goal_command("请在这条主线开启一个持久 /goal,目标是收xxx"), "收xxx") self.assertEqual( parse_goal_command("请在这条主线开启一个持久 /goal,目标是收xxx。"), "收xxx") def test_slash_forms(self): self.assertEqual(parse_goal_command("/goal 收xxx"), "收xxx") self.assertEqual(parse_goal_command("/goal,目标是收xxx"), "收xxx") self.assertEqual(parse_goal_command("/goal: 收xxx"), "收xxx") self.assertEqual(parse_goal_command("请开启/goal收xxx"), "收xxx") self.assertEqual(parse_goal_command("/goal"), "") def test_discussion_is_not_a_command(self): self.assertIsNone(parse_goal_command("讨论/goal的用法")) self.assertIsNone(parse_goal_command("请帮我完成目标收xxx")) self.assertIsNone(parse_goal_command("hello world")) def test_bare_goal_without_slash(self): # 不用斜杠:用户单发 goal 也触发同款目标。 self.assertEqual(parse_goal_command("goal 收xxx"), "收xxx") self.assertEqual(parse_goal_command("goal,目标是收xxx"), "收xxx") self.assertEqual(parse_goal_command("goal目标是收xxx"), "收xxx") self.assertEqual(parse_goal_command("goal收xxx"), "收xxx") self.assertEqual(parse_goal_command("发goal 收xxx"), "收xxx") self.assertEqual(parse_goal_command("goal"), "") def test_bare_goal_discussion_still_not_a_command(self): self.assertIsNone(parse_goal_command("goal的用法")) self.assertIsNone(parse_goal_command("goal是什么意思")) self.assertIsNone(parse_goal_command("讨论goal的用法")) self.assertIsNone(parse_goal_command("请问goal是什么意思")) self.assertIsNone(parse_goal_command("/goal的用法")) class ServerTestCase(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.db_path = os.path.join(self.tmp.name, "control.db") self.auth = AuthStore(self.db_path) self.owner_id, self.owner_key = self.auth.create_user("owner") self.server = create_server(self.db_path, "127.0.0.1", 0) self.port = self.server.server_address[1] self.thread = threading.Thread( target=self.server.serve_forever, daemon=True) self.thread.start() status, self.project = self._req( "POST", "/projects", {"name": "demo"}, self.owner_key) self.assertEqual(status, 201) def tearDown(self): self.server.shutdown() self.server.server_close() self.thread.join(timeout=5) try: self.auth.conn.close() except Exception: pass self.tmp.cleanup() def _req(self, method, path, body=None, key=None): conn = http.client.HTTPConnection( "127.0.0.1", self.port, timeout=10) hdrs = {} payload = None if key: hdrs["X-API-Key"] = key if body is not None: payload = json.dumps(body) hdrs["Content-Type"] = "application/json" conn.request(method, path, body=payload, headers=hdrs) resp = conn.getresponse() raw = resp.read() conn.close() try: return resp.status, json.loads(raw.decode("utf-8")) except ValueError: return resp.status, raw.decode("utf-8", errors="replace") def _create(self, **extra): body = {"project_id": self.project["id"], "prompt": "do work"} body.update(extra) status, task = self._req("POST", "/tasks", body, self.owner_key) self.assertEqual(status, 201, task) return task def _set_status(self, task_id, status): conn = sqlite3.connect(self.db_path) try: conn.execute("UPDATE tasks SET status = ? WHERE id = ?", (status, task_id)) conn.commit() finally: conn.close() def _get_task(self, task_id): status, task = self._req("GET", "/tasks/" + task_id, None, self.owner_key) self.assertEqual(status, 200, task) return task def test_followup_queued_converts_in_place(self): task = self._create() status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "请在这条主线开启一个持久 /goal,目标是收xxx"}, self.owner_key) self.assertEqual(status, 200, body) self.assertEqual(body["delivery"], "goal_converted") self.assertEqual(body["goal"], "收xxx") after = self._get_task(task["id"]) self.assertEqual(after["status"], "queued") self.assertEqual(after["goal_mode"], 1) self.assertEqual(after["goal"], "收xxx") self.assertIsNone(after["verify_command"]) self.assertEqual(after["goal_max_rounds"], 0) def test_followup_running_queues_for_worker(self): task = self._create() self._set_status(task["id"], "running") status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal 收xxx"}, self.owner_key) self.assertEqual(status, 201, body) self.assertEqual(body["delivery"], "goal_queued") self.assertEqual(body["goal"], "收xxx") # running 主线本轮不被打断:DB 里仍是 running,未转 goal。 after = self._get_task(task["id"]) self.assertEqual(after["status"], "running") self.assertEqual(after["goal_mode"], 0) def test_followup_terminal_revives_as_goal(self): task = self._create() self._set_status(task["id"], "completed") status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal 收xxx"}, self.owner_key) self.assertEqual(status, 200, body) self.assertEqual(body["delivery"], "goal_revived") after = self._get_task(task["id"]) self.assertEqual(after["status"], "queued") self.assertEqual(after["goal_mode"], 1) self.assertEqual(after["goal"], "收xxx") self.assertEqual(after["resume_message"], "收xxx") def test_followup_already_goal_409(self): task = self._create(goal_mode=1) status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal 收yyy"}, self.owner_key) self.assertEqual(status, 409, body) after = self._get_task(task["id"]) self.assertEqual(after["goal"], "do work") def test_followup_empty_goal_400(self): task = self._create() status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal"}, self.owner_key) self.assertEqual(status, 400, body) def test_followup_plan_conflict_400(self): task = self._create(plan=1) status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal 收xxx"}, self.owner_key) self.assertEqual(status, 400, body) def test_followup_merged_409(self): task = self._create() self._set_status(task["id"], "merged") status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "/goal 收xxx"}, self.owner_key) self.assertEqual(status, 409, body) def test_ordinary_followup_unchanged(self): task = self._create() status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "继续做"}, self.owner_key) self.assertEqual(status, 201, body) self.assertNotIn("goal", body) after = self._get_task(task["id"]) self.assertEqual(after["goal_mode"], 0) def test_create_task_prompt_goal_command(self): task = self._create(prompt="/goal 收xxx") self.assertEqual(task["goal_mode"], 1) self.assertEqual(task["goal"], "收xxx") self.assertEqual(task["prompt"], "收xxx") self.assertIsNone(task["verify_command"]) self.assertEqual(task["goal_max_rounds"], 0) def test_create_task_prompt_goal_user_sentence(self): task = self._create( prompt="请在这条主线开启一个持久 /goal,目标是收xxx") self.assertEqual(task["goal_mode"], 1) self.assertEqual(task["goal"], "收xxx") self.assertEqual(task["prompt"], "收xxx") def test_create_task_prompt_goal_empty_400(self): status, body = self._req( "POST", "/tasks", {"project_id": self.project["id"], "prompt": "/goal"}, self.owner_key) self.assertEqual(status, 400, body) def test_create_task_prompt_goal_conflicts_400(self): status, _body = self._req( "POST", "/tasks", {"project_id": self.project["id"], "prompt": "/goal 收xxx", "plan": 1}, self.owner_key) self.assertEqual(status, 400) status, _body = self._req( "POST", "/tasks", {"project_id": self.project["id"], "prompt": "/goal 收xxx", "verify_command": "true"}, self.owner_key) self.assertEqual(status, 400) def test_create_task_discussion_prompt_unchanged(self): task = self._create(prompt="讨论/goal的用法") self.assertEqual(task["goal_mode"], 0) def test_followup_bare_goal_converts(self): task = self._create() status, body = self._req( "POST", "/tasks/%s/followups" % task["id"], {"body": "goal 收xxx"}, self.owner_key) self.assertEqual(status, 200, body) self.assertEqual(body["delivery"], "goal_converted") self.assertEqual(body["goal"], "收xxx") def test_create_task_bare_goal(self): task = self._create(prompt="goal 收xxx") self.assertEqual(task["goal_mode"], 1) self.assertEqual(task["goal"], "收xxx") def test_continue_goal_command(self): task = self._create() self._set_status(task["id"], "completed") status, body = self._req( "POST", "/tasks/%s/continue" % task["id"], {"message": "请开启/goal,目标是收xxx"}, self.owner_key) self.assertEqual(status, 200, body) self.assertEqual(body["goal_mode"], 1) self.assertEqual(body["goal"], "收xxx") self.assertEqual(body["resume_message"], "收xxx") def test_thread_message_terminal_goal(self): task = self._create() self._set_status(task["id"], "failed") status, body = self._req( "POST", "/tasks/%s/thread/message" % task["id"], {"body": "/goal 收xxx"}, self.owner_key) self.assertEqual(status, 201, body) self.assertEqual(body["delivery"], "goal_revived") after = self._get_task(task["id"]) self.assertEqual(after["goal_mode"], 1) self.assertEqual(after["goal"], "收xxx") class WorkerTestCase(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.db_path = os.path.join(self.tmp.name, "control.db") init_db(self.db_path) self.env = {"XCM_API_KEY": "test-placeholder"} def tearDown(self): self.tmp.cleanup() def _insert_task(self, task_id, **extra): now = time.time() cols = ["id", "project_id", "prompt", "status", "idempotency_key", "depends_on", "priority", "created_by", "created_at", "updated_at", "last_heartbeat"] vals = [task_id, "p1", "do work", "queued", None, "[]", 0, "tester", now, now, None] for k, v in extra.items(): cols.append(k) vals.append(v) conn = sqlite3.connect(self.db_path) try: conn.execute( "INSERT INTO tasks(%s) VALUES(%s)" % (", ".join(cols), ", ".join("?" * len(cols))), vals) conn.commit() finally: conn.close() def _insert_followup(self, task_id, body): conn = sqlite3.connect(self.db_path) try: ensure_followups_table(conn) conn.execute( "INSERT INTO task_followups(id, task_id, project_id," " body, mode, status, created_by, created_at," " updated_at) VALUES(?, ?, ?, ?, 'queued', 'pending'," " ?, ?, ?)", ("fu1", task_id, "p1", body, "tester", time.time(), time.time())) conn.commit() finally: conn.close() def _row(self, task_id): conn = sqlite3.connect(self.db_path) try: row = conn.execute( "SELECT status, goal_mode, goal, resume_message" " FROM tasks WHERE id = ?", (task_id,)).fetchone() fu = conn.execute( "SELECT status FROM task_followups WHERE id = 'fu1'" ).fetchone() return row, (fu[0] if fu else None) finally: conn.close() def test_running_task_converts_at_turn_boundary_then_goal_runs(self): self._insert_task("T") self._insert_followup("T", "/goal 收xxx") seen = [] def fake_dispatch(cmd): seen.append(list(cmd)) name = cmd[cmd.index("--name") + 1] return SimpleNamespace( returncode=0, stdout=json.dumps({"task": name, "status": "completed", "text": "done"}), stderr="") worker = Worker(self.db_path, "/fake/host.py", runner=fake_dispatch, env=self.env) result = worker.run_one() self.assertEqual(result["status"], "queued") (status, goal_mode, goal, resume), fu_status = self._row("T") self.assertEqual(status, "queued") self.assertEqual(goal_mode, 1) self.assertEqual(goal, "收xxx") self.assertEqual(resume, "收xxx") self.assertEqual(fu_status, "done") self.assertTrue(any("dispatch" in c for c in seen)) self.assertFalse(any("goal" in c for c in seen)) # 下次认领走 goal-resume:goal 循环宣告达成即完成。 def fake_goal(cmd): seen.append(list(cmd)) # goal-resume 的命令元素是 "goal-resume",不是 "goal", # 用子串判,别用元素相等判。 if any("goal" in part for part in cmd): return SimpleNamespace( returncode=0, stdout="round 1: turn=completed verify_rc=-" " verify_out=\n" "goal met after rounds\n", stderr="") raise AssertionError("第二轮不应走 dispatch: %s" % cmd) worker2 = Worker(self.db_path, "/fake/host.py", runner=fake_goal, env=self.env) result2 = worker2.run_one() self.assertEqual(result2["status"], "completed") self.assertTrue(any(any("goal" in part for part in c) for c in seen)) if __name__ == "__main__": unittest.main()