Files
codex-app-server/cloud/tests/test_coverage85_worker4.py

382 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""覆盖率 78%->85% 第 4 步:cloud/worker.py _deliver_marker_tools
剩余分支的真实行为测试。
父任务属主用 AuthStore 建真实 owner + 项目成员关系(权限
True/False 两路都走真实 check_permission);待办/自动化走
真实 SQLite 表;任务文件走临时 host.py 同级 work 目录。
绝不跑模型、不碰真实网关。
"""
import os
import sqlite3
import tempfile
import time
import unittest
from cloud.auth import AuthStore
from cloud.server import init_db
from cloud.worker import Worker
def _insert_task(db_path, task_id, status="queued", **kw):
conn = sqlite3.connect(db_path)
try:
cols = {"id": task_id, "project_id": kw.pop("project_id", "proj"),
"prompt": kw.pop("prompt", "do it"), "status": status,
"created_by": kw.pop("created_by", "owner1"),
"created_at": kw.pop("created_at", 1000.0),
"updated_at": kw.pop("updated_at", 1000.0)}
cols.update(kw)
names = ", ".join(cols)
placeholders = ", ".join("?" * len(cols))
conn.execute("INSERT INTO tasks(%s) VALUES(%s)"
% (names, placeholders), tuple(cols.values()))
conn.commit()
finally:
conn.close()
def _rows(db_path, sql, params=()):
conn = sqlite3.connect(db_path)
try:
return conn.execute(sql, params).fetchall()
finally:
conn.close()
def _set(db_path, sql, params=()):
conn = sqlite3.connect(db_path)
try:
conn.execute(sql, params)
conn.commit()
finally:
conn.close()
class MarkerBase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
auth = AuthStore(self.db_path)
self.owner_id, _ = auth.create_user("owner")
auth.create_project("proj", self.owner_id)
self.outsider_id, _ = auth.create_user("outsider")
auth.conn.close()
# host.py 替身放在临时目录:任务文件解析走它同级的
# work/host-worktrees/<任务名>,不碰真实 /fake 或仓库。
self.fake_host = os.path.join(self.tmp.name, "host.py")
with open(self.fake_host, "w") as f:
f.write("# fake host\n")
self.worker = Worker(
self.db_path, self.fake_host,
env={"XCM_API_KEY": "test-placeholder"},
heartbeat_file=os.path.join(self.tmp.name, "hb.json"),
worker_id="cov85-worker4", max_retries=0,
retry_backoff_seconds=0)
def _parent(self, pid="parent1", creator=None):
if self.worker.get_task(pid) is None:
_insert_task(self.db_path, pid, status="completed",
created_by=creator or self.owner_id)
return self.worker.get_task(pid)
def _followups(self, pid):
return [r[0] for r in _rows(
self.db_path,
"SELECT body FROM task_followups WHERE task_id = ?",
(pid,))]
def _deliver(self, pid, result, creator=None):
parent = self._parent(pid, creator)
self.worker._deliver_marker_tools(parent, result)
return self._followups(pid)
class CreatesGateTest(MarkerBase):
def test_creates_quota_reject(self):
_set(self.db_path,
"INSERT INTO user_quotas(user_id, quota_usd_micros,"
" updated_at) VALUES(?, 0, 0)", (self.owner_id,))
bodies = self._deliver("parent1", {
"status": "completed",
"task_creates": [{"prompt": "x", "name": "c",
"model": None, "effort": None,
"executor": None}]})
self.assertEqual(len(bodies), 1)
self.assertIn("创建跳过", bodies[0])
self.assertEqual(_rows(
self.db_path,
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = 'parent1'"),
[(0,)])
def test_creates_depth_and_direct_caps(self):
# 直接派生上限:_MARKER_MAX_DIRECT 个子任务已存在时
# 再创建被拒(报告写清上限,不炸)。
from cloud.worker import _MARKER_MAX_DIRECT
for i in range(_MARKER_MAX_DIRECT):
_insert_task(self.db_path, "kid-%d" % i, status="queued",
parent_task_id="parent1",
created_by=self.owner_id)
bodies = self._deliver("parent1", {
"status": "completed",
"task_creates": [{"prompt": "x", "name": "c",
"model": None, "effort": None,
"executor": None}]})
self.assertIn("直接派生子任务已达上限", bodies[0])
class AutomationCreateTest(MarkerBase):
def test_automation_auth_reject(self):
# 外人(非成员)当父任务属主:权限 False,整轮跳过。
bodies = self._deliver("parent1", {
"status": "completed",
"automation_creates": [{"prompt": "巡检", "name": "a1",
"interval_seconds": 3600}]},
creator=self.outsider_id)
self.assertIn("自动化创建跳过", bodies[0])
self.assertIn("无 create_task 权限", bodies[0])
def test_automation_past_first_run_reject_and_success(self):
bodies = self._deliver("parent1", {
"status": "completed",
"automation_creates": [
{"prompt": "旧的", "name": "old",
"interval_seconds": 3600,
"first_run_at": time.time() - 3600},
{"prompt": "巡检任务", "name": "new",
"interval_seconds": 3600,
"first_run_at": time.time() + 3600}]})
self.assertIn("首次运行时刻已过去", bodies[0])
self.assertIn("已创建自动化", bodies[0])
self.assertEqual(_rows(
self.db_path,
"SELECT COUNT(*) FROM automations WHERE project_id = 'proj'"),
[(1,)])
def test_automation_quota_reject(self):
_set(self.db_path,
"INSERT INTO user_quotas(user_id, quota_usd_micros,"
" updated_at) VALUES(?, 0, 0)", (self.owner_id,))
bodies = self._deliver("parent1", {
"status": "completed",
"automation_creates": [{"prompt": "巡检", "name": "a1",
"interval_seconds": 3600}]})
self.assertIn("自动化创建跳过", bodies[0])
self.assertIn("配额检查未通过", bodies[0])
class CancelRetryTest(MarkerBase):
def test_cancel_descendant_and_retry_failed(self):
_insert_task(self.db_path, "kid1", status="queued",
parent_task_id="parent1",
created_by=self.owner_id)
_insert_task(self.db_path, "kid2", status="failed",
parent_task_id="parent1",
created_by=self.owner_id)
bodies = self._deliver("parent1", {
"status": "completed",
"task_cancels": [{"task_id": "kid1"}],
"task_retries": [{"task_id": "kid2"}]})
self.assertIn("已取消任务 kid1", bodies[0])
self.assertIn("已重试任务 kid2", bodies[0])
statuses = dict(_rows(self.db_path,
"SELECT id, status FROM tasks"))
self.assertEqual(statuses["kid1"], "interrupted")
self.assertEqual(statuses["kid2"], "queued")
def test_cancel_other_project_rejected(self):
_insert_task(self.db_path, "alien", status="queued",
project_id="other", created_by="someone-else")
bodies = self._deliver("parent1", {
"status": "completed",
"task_cancels": [{"task_id": "alien"}]})
self.assertIn("拒绝", bodies[0])
self.assertEqual(_rows(
self.db_path,
"SELECT status FROM tasks WHERE id = 'alien'"),
[("queued",)])
def test_cancel_missing_target_rejected(self):
bodies = self._deliver("parent1", {
"status": "completed",
"task_cancels": [{"task_id": "ghost"}]})
self.assertIn("拒绝", bodies[0])
class TodoTaskBlocksTest(MarkerBase):
def test_todo_create_update_list(self):
bodies = self._deliver("parent1", {
"status": "completed",
"todo_creates": [{"title": "写周报", "description": "周五前",
"status": "pending",
"priority": "high"}]})
self.assertIn("已创建待办", bodies[0])
todo_id = _rows(self.db_path,
"SELECT id FROM todos")[0][0]
bodies = self._deliver("parent1", {
"status": "completed",
"todo_updates": [{"id": todo_id,
"fields": {"status": "in_progress"}}],
"todo_lists": [{"status": "in_progress"}]})
# 第二次 deliver 追加第二条报告:更新与列表都在 bodies[1]。
self.assertIn("已更新待办", bodies[1])
self.assertIn("待办列表", bodies[1])
self.assertIn("写周报", bodies[1])
def test_todo_auth_reject_and_bad_refs(self):
bodies = self._deliver("parent1", {
"status": "completed",
"todo_creates": [{"title": "挂别人任务",
"task_id": "ghost"}],
"todo_updates": [{"id": "ghost-todo",
"fields": {"status": "done"}}],
"todo_lists": [{}]}, creator=self.outsider_id)
self.assertIn("拒绝", bodies[0])
# 外人无 read/create:三类块都被拒,待办表根本未建。
self.assertEqual(_rows(
self.db_path,
"SELECT name FROM sqlite_master WHERE type = 'table'"
" AND name = 'todos'"), [])
def test_todo_create_cross_project_ref_rejected(self):
_insert_task(self.db_path, "alien", status="queued",
project_id="other", created_by="someone-else")
bodies = self._deliver("parent1", {
"status": "completed",
"todo_creates": [{"title": "挂跨项目",
"task_id": "alien"}]})
self.assertIn("拒绝", bodies[0])
def test_task_list_filters_and_cost_and_quota(self):
_insert_task(self.db_path, "kid1", status="completed",
parent_task_id="parent1",
created_by=self.owner_id, prompt="做加法")
bodies = self._deliver("parent1", {
"status": "completed",
"task_lists": [{"status": "completed", "q": "加法",
"limit": 10}],
"task_costs": [{"task_id": "kid1"}],
"quota_status": [{}]})
self.assertIn("任务列表", bodies[0])
self.assertIn("kid1", bodies[0])
self.assertIn("配额", bodies[0])
def test_task_list_empty_and_cost_missing(self):
bodies = self._deliver("parent1", {
"status": "completed",
"task_lists": [{"status": "failed", "q": None,
"limit": 10}],
"task_costs": [{"task_id": "ghost"}]})
self.assertIn("任务列表:无匹配项", bodies[0])
self.assertIn("不存在", bodies[0])
def test_quota_status_no_creator_rejected(self):
# created_by 为空:_marker_auth 返回 False,read 路先被拒。
_insert_task(self.db_path, "parent1", status="completed",
created_by="")
parent = self.worker.get_task("parent1")
self.worker._deliver_marker_tools(parent, {
"status": "completed", "quota_status": [{}]})
bodies = self._followups("parent1")
self.assertIn("拒绝", bodies[0])
class TaskFilesTest(MarkerBase):
def _worktree(self, tid):
d = os.path.join(self.tmp.name, "work", "host-worktrees", tid)
os.makedirs(d, exist_ok=True)
return d
def test_file_read_dir_list_and_missing(self):
d = self._worktree("kid1")
with open(os.path.join(d, "a.txt"), "w") as f:
f.write("hello file")
with open(os.path.join(d, "b.bin"), "wb") as f:
f.write(b"\x00\x01\x02binary")
_insert_task(self.db_path, "kid1", status="completed",
parent_task_id="parent1",
created_by=self.owner_id)
bodies = self._deliver("parent1", {
"status": "completed",
"task_files": [{"task_id": "kid1", "path": "a.txt"},
{"task_id": "kid1", "path": ""},
{"task_id": "kid1", "path": "nope.txt"},
{"task_id": "kid1", "path": "b.bin"},
{"task_id": "kid1", "path": "../evil"}]})
body = bodies[0]
self.assertIn("hello file", body)
self.assertIn("a.txt", body)
self.assertIn("不存在", body)
self.assertIn("二进制文件", body)
self.assertIn("路径逃逸", body)
def test_file_no_worktree_rejected(self):
_insert_task(self.db_path, "kid1", status="completed",
parent_task_id="parent1",
created_by=self.owner_id)
bodies = self._deliver("parent1", {
"status": "completed",
"task_files": [{"task_id": "kid1", "path": "a.txt"}]})
self.assertIn("没有本地 worktree", bodies[0])
def test_file_auth_reject(self):
bodies = self._deliver("parent1", {
"status": "completed",
"task_files": [{"task_id": "kid1", "path": "a.txt"}]},
creator=self.outsider_id)
self.assertIn("拒绝", bodies[0])
class PollWaitTest(MarkerBase):
def test_wait_mixed_targets(self):
_insert_task(self.db_path, "kid1", status="completed",
parent_task_id="parent1",
created_by=self.owner_id)
_insert_task(self.db_path, "alien", status="completed",
project_id="other", created_by="someone-else")
bodies = self._deliver("parent1", {
"status": "completed",
"wait_threads": [{"task_ids": ["kid1", "ghost", "alien",
"parent1"],
"interval_seconds": 0.05,
"timeout_seconds": 5, "chars": 200}]})
body = bodies[0]
self.assertIn("kid1", body)
self.assertIn("不存在", body)
self.assertIn("跨项目", body)
self.assertIn("不能等待自己", body)
def test_poll_self_and_cross_project(self):
_insert_task(self.db_path, "alien", status="completed",
project_id="other", created_by="someone-else")
bodies = self._deliver("parent1", {
"status": "completed",
"task_polls": [{"task_id": "parent1",
"interval_seconds": 0.05,
"timeout_seconds": 5, "chars": 200},
{"task_id": "alien",
"interval_seconds": 0.05,
"timeout_seconds": 5, "chars": 200},
{"task_id": "ghost",
"interval_seconds": 0.05,
"timeout_seconds": 5, "chars": 200}]})
body = bodies[0]
self.assertIn("不能轮询自己", body)
self.assertIn("跨项目", body)
self.assertIn("不存在", body)
def test_goal_convert_queues_goal_followup_after_report(self):
bodies = self._deliver("parent1", {
"status": "completed",
"todo_lists": [{}],
"goal_converts": [{"goal": "收齐周报"}]})
self.assertEqual(len(bodies), 2)
self.assertIn("待办列表", bodies[0])
self.assertEqual(bodies[1], "/goal 收齐周报")
if __name__ == "__main__":
unittest.main()