auto-sync: 2026-06-07 08:23:57
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions

This commit is contained in:
cfdaily
2026-06-07 08:23:57 +08:00
parent 0d7425b88c
commit 7e1bdec0b2
3 changed files with 100 additions and 20 deletions
+19
View File
@@ -2,6 +2,11 @@ import pytest
pytestmark = pytest.mark.e2e
skip_no_integration = pytest.mark.skipif(
not __import__("os").environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run E2E tests against real daemon",
)
"""v2.7 端到端测试 — 全链路真实环境
覆盖:项目管理 → Task CRUD → SubTask → Stage进度 → 状态聚合 → 依赖链 → 超时 → Mail → 真实Agent调度
@@ -62,6 +67,7 @@ def _tid() -> str:
# E1: 项目管理
# ===================================================================
@skip_no_integration
class TestE1ProjectManagement:
"""E1: 项目创建、列表、归档"""
@@ -117,6 +123,7 @@ class TestE1ProjectManagement:
# E2: Task CRUD + 状态机
# ===================================================================
@skip_no_integration
class TestE2TaskCRUD:
"""E2: Task 创建、查询、状态转换"""
@@ -198,6 +205,7 @@ class TestE2TaskCRUD:
# E3: SubTask 父子关系
# ===================================================================
@skip_no_integration
class TestE3SubTask:
"""E3: 父子 Task 关系"""
@@ -257,6 +265,7 @@ class TestE3SubTask:
# E4: Stage 进度
# ===================================================================
@skip_no_integration
class TestE4StageProgress:
"""E4: stages_json + stage 分组统计"""
@@ -325,6 +334,7 @@ class TestE4StageProgress:
# E5: 父 Task 状态聚合
# ===================================================================
@skip_no_integration
class TestE5ParentAggregation:
"""E5: compute_parent_status 聚合逻辑"""
@@ -418,6 +428,7 @@ class TestE5ParentAggregation:
# E6: 依赖链
# ===================================================================
@skip_no_integration
class TestE6DependencyChain:
"""E6: depends_on 依赖推进"""
@@ -475,6 +486,7 @@ class TestE6DependencyChain:
# E7: 超时回收
# ===================================================================
@skip_no_integration
class TestE7Timeout:
"""E7: claimed/working 超时回收"""
@@ -537,6 +549,7 @@ class TestE7Timeout:
# E8: Mail Tab 6 端点
# ===================================================================
@skip_no_integration
class TestE8MailTab:
"""E8: Mail 端到端"""
@@ -769,6 +782,7 @@ def _poll_task(pid, tid, timeout, terminal_states=None):
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE9RealAgentDispatch:
"""E9: 真实 Agent 调度测试
@@ -915,6 +929,7 @@ class TestE9RealAgentDispatch:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE10FullChain:
"""E10: 项目 → 父子Task → 生产Ticker → 聚合 → 依赖 → Mail → 前端API
@@ -1116,6 +1131,7 @@ class TestE10FullChain:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE11AcquireFirstE2E:
"""E11: #07.1 Acquire-First Phase 1-4 真实 Agent E2E
@@ -1284,6 +1300,7 @@ class TestE11AcquireFirstE2E:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE12TimeoutsUnifiedE2E:
"""E12: #07.2 _check_timeouts 统一超时 + crash_limit + updated_at fallback
@@ -1420,6 +1437,7 @@ class TestE12TimeoutsUnifiedE2E:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE13CompactHangingE2E:
"""E13: compact_hanging outcome → 任务保持 working(不标 failed
@@ -1513,6 +1531,7 @@ class TestE13CompactHangingE2E:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE14RollbackE2E:
"""E14: crash 后 current_agent 回退验证
+13
View File
@@ -2,6 +2,11 @@ import pytest
pytestmark = pytest.mark.e2e
skip_no_integration = pytest.mark.skipif(
not __import__("os").environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run E2E tests against real daemon",
)
"""v3.1 端到端测试 — 新增场景覆盖
覆盖 v3.1 新增功能
@@ -169,6 +174,7 @@ def _patch_db_claimed_at(pid: str, tid: str, claimed_at: str):
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE94BroadcastClaim:
"""E9-4: 无 assignee 任务 → 广播认领 → Agent 执行 → done"""
@@ -230,6 +236,7 @@ class TestE94BroadcastClaim:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE95PauseResume:
"""E9-5: 手动推状态到 working → paused → 恢复 → 验证 resumed_from"""
@@ -322,6 +329,7 @@ class TestE95PauseResume:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE96CancelledRestart:
"""E9-6: cancelled → pending(重新启动)→ Agent 执行 → done"""
@@ -388,6 +396,7 @@ class TestE96CancelledRestart:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE97ClaimedTimeout:
"""E9-7: claimed 超时 → pending (assignee 清空)"""
@@ -453,6 +462,7 @@ class TestE97ClaimedTimeout:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE98CacheHeaders:
"""E9-8: 验证 CachedStaticFiles 缓存头"""
@@ -524,6 +534,7 @@ class TestE98CacheHeaders:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE10cRetryChain:
"""E10c: failed → pending(手动重试)→ 广播 → 认领 → done"""
@@ -598,6 +609,7 @@ class TestE10cRetryChain:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE10dFullLifecycle:
"""E10d: 无 assignee → 广播认领 → claimed → working → review → done
@@ -690,6 +702,7 @@ class TestE10dFullLifecycle:
@pytest.mark.integration
@pytest.mark.skipif(not os.environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run real agent tests")
@skip_no_integration
class TestE15PromptV3Broadcast:
"""E15: Prompt v3.0 广播认领三级响应 E2E
+68 -20
View File
@@ -1,11 +1,12 @@
"""#11 Bootstrap 四段式拼装单元测试(v2.1)
覆盖
- T1: build(role, task_context) 4 段结构
- T1: build(task, role) 4 段结构
- T2: token 估算 + 预算告警
- T3: 缺失组件降级
- T4: build_for_task 便捷方法
- T5: _read_skill fallback
- T6: ROLE_SKILL_MAP 覆盖
"""
import pytest
@@ -23,52 +24,57 @@ def builder():
# ---------------------------------------------------------------------------
# T1: build(role, task_context) 4 段结构
# T1: build(task, role) 4 段结构
# ---------------------------------------------------------------------------
class TestFourSectionBuild:
def test_basic_executor_build(self, builder):
b = builder.build(
role="executor",
task_context={"task_id": "t1", "title": "Write tests", "description": "Write unit tests",
task={"task_id": "t1", "title": "Write tests", "description": "Write unit tests",
"must_haves": "100% coverage", "status": "claimed"},
role="executor",
)
# 段: 任务上下文
# 段 1: 任务上下文
assert "Write tests" in b
assert "t1" in b
assert "100% coverage" in b
# 段 4: 硬约束
assert "review" in b
assert "handoff" in b
def test_basic_reviewer_build(self, builder):
b = builder.build(
task={"task_id": "t2", "title": "Review PR"},
role="reviewer",
task_context={"task_id": "t2", "title": "Review PR"},
)
assert "Review PR" in b
# 段 4: reviewer 硬约束
assert "pass/fail" in b or "pass" in b
def test_planner_build(self, builder):
b = builder.build(
task={"task_id": "t3", "title": "Plan sprint"},
role="planner",
task_context={"task_id": "t3", "title": "Plan sprint"},
)
assert "Plan sprint" in b
def test_depends_on_outputs_injected(self, builder):
b = builder.build(
role="executor",
task_context={
task={
"title": "T",
"depends_on_outputs": [
{"task_id": "t0", "summary": "Data downloaded"},
],
},
role="executor",
)
assert "前序产出" in b
assert "Data downloaded" in b
def test_no_depends_on_omitted(self, builder):
b = builder.build(
task={"title": "T"},
role="executor",
task_context={"title": "T"},
)
assert "前序产出" not in b
@@ -93,12 +99,12 @@ class TestTokenEstimation:
class TestGracefulDegradation:
def test_empty_task(self, builder):
b = builder.build(role="executor", task_context={})
b = builder.build(task={}, role="executor")
assert b # 不为空
assert "# Role: executor" in b
assert "硬约束" in b
def test_partial_task(self, builder):
b = builder.build(role="executor", task_context={"title": "Only title"})
b = builder.build(task={"title": "Only title"}, role="executor")
assert "Only title" in b
@@ -114,30 +120,72 @@ class TestBuildForTask:
title = "Build Feature"
description = "Implement X with tests"
must_haves = "Unit tests, Documentation"
task_type = "coding"
risk_level = "low"
status = "claimed"
task = MockTask()
b = builder.build_for_task(task, role="executor")
assert "Build Feature" in b
assert "Implement X" in b
def test_build_for_task_ignores_kwargs(self, builder):
"""build_for_task accepts project_config and experiences"""
"""build_for_task 忽略旧参数"""
class MockTask:
id = "t1"
title = "T"
description = ""
must_haves = ""
task_type = "coding"
risk_level = "low"
status = ""
task = MockTask()
# project_config/experiences are valid kwargs for build_for_task
# 旧参数 project_config/experiences 不应报错
b = builder.build_for_task(
task, role="executor",
project_config={"name": "Old"},
experiences=[{"x": 1}],
)
assert "T" in b
# 不应出现旧参数内容
assert "Old" not in b
# ---------------------------------------------------------------------------
# T5: _read_skill fallback
# ---------------------------------------------------------------------------
class TestReadSkillFallback:
def test_missing_skill_file_returns_empty(self, builder):
"""Skill 文件不存在时返回空字符串,不抛异常"""
result = builder._read_skill("nonexistent-skill-xyz")
assert result == ""
def test_existing_skill_file_read(self, builder):
"""能读取实际存在的 Skill 文件"""
# blackboard-executor 应该存在(P1 已创建)
result = builder._read_skill("blackboard-executor")
assert "执行" in result or "executor" in result.lower()
# ---------------------------------------------------------------------------
# T6: ROLE_SKILL_MAP 覆盖
# ---------------------------------------------------------------------------
class TestRoleSkillMap:
def test_all_roles_mapped(self):
assert set(BootstrapBuilder.ROLE_SKILL_MAP.keys()) == {
"executor", "reviewer", "reviewer-simayi",
"reviewer-pangtong", "planner", "claim",
}
def test_unknown_role_warns(self, builder):
"""未映射的 role 输出 warning"""
import logging
with patch("src.daemon.bootstrap.logger") as mock_logger:
builder.build(task={"title": "T"}, role="unknown_role")
mock_logger.warning.assert_called_with(
"No skill mapping for role: %s", "unknown_role"
)
def test_discussion_role_no_warning(self, builder, caplog):
"""discussion 角色不应触发 warning"""
import logging
with caplog.at_level(logging.WARNING, logger="moziplus-v2.bootstrap"):
builder.build(task={"title": "T"}, role="discussion")
assert "No skill mapping" not in caplog.text