Files
edict/tests/test_kanban.py
狼哥 c958d06cb4 fix(flow): prevent premature task completion before review (#280)
cmd_done() 不再直接写 Done,改为校验 todos 完成度后路由到 Review;dashboard 准奏也增加 todo 完成度门控,防止子任务未完成就关闭任务
2026-04-20 00:17:01 +08:00

210 lines
7.4 KiB
Python

"""tests for scripts/kanban_update.py"""
import json
import pathlib
import sys
# Ensure scripts/ is importable
SCRIPTS = pathlib.Path(__file__).resolve().parent.parent / "scripts"
sys.path.insert(0, str(SCRIPTS))
import kanban_update as kb
def test_create_and_get(tmp_path):
"""kanban create + get round-trip."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text("[]", encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_create("TEST-001", "测试任务创建和查询功能验证", "Inbox", "工部", "工部尚书")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
assert any(t.get("id") == "TEST-001" for t in tasks)
task = next(t for t in tasks if t["id"] == "TEST-001")
assert task["title"] == "测试任务创建和查询功能验证"
assert task["state"] == "Inbox"
assert task["org"] == "工部"
finally:
kb.TASKS_FILE = original
def test_move_state(tmp_path):
"""kanban move changes task state."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-1", "title": "test", "state": "Inbox"}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_state("T-1", "Doing")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
assert tasks[0]["state"] == "Doing"
finally:
kb.TASKS_FILE = original
def test_block_and_unblock(tmp_path):
"""kanban block round-trip."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-2", "title": "blocker test", "state": "Doing"}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_block("T-2", "等待依赖")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
assert tasks[0]["state"] == "Blocked"
assert tasks[0]["block"] == "等待依赖"
finally:
kb.TASKS_FILE = original
def test_flow_log(tmp_path):
"""cmd_flow appends a flow_log entry."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-3", "title": "flow test", "state": "Zhongshu", "flow_log": []}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_flow("T-3", "中书省", "门下省", "规划方案提交审议")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
assert len(task["flow_log"]) == 1
assert task["flow_log"][0]["from"] == "中书省"
assert task["flow_log"][0]["to"] == "门下省"
finally:
kb.TASKS_FILE = original
def test_done_routes_to_review(tmp_path):
"""cmd_done should route execution output back to Review instead of direct Done."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{
"id": "T-4",
"title": "done test",
"state": "Doing",
"org": "兵部",
"flow_log": [],
"todos": [{"id": "1", "title": "收尾", "status": "completed"}],
}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_done("T-4", "/tmp/output.md", "功能已全部实现")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
assert task["state"] == "Review"
assert task["org"] == "尚书省"
assert task["output"] == "/tmp/output.md"
assert task["now"] == "功能已全部实现"
assert any("提交审查" in entry.get("remark", "") for entry in task["flow_log"])
finally:
kb.TASKS_FILE = original
def test_done_rejects_incomplete_todos(tmp_path):
"""cmd_done should be rejected when todos are still incomplete."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{
"id": "T-4B",
"title": "done gate test",
"state": "Doing",
"org": "工部",
"flow_log": [],
"todos": [
{"id": "1", "title": "已完成", "status": "completed"},
{"id": "2", "title": "未完成", "status": "in-progress"},
],
}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_done("T-4B", "/tmp/output.md", "试图提前收口")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
assert task["state"] == "Doing"
assert task.get("output", "") in ("", None)
assert task.get("flow_log", []) == []
finally:
kb.TASKS_FILE = original
def test_progress(tmp_path):
"""cmd_progress updates now text and appends to progress_log."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-5", "title": "progress test", "state": "Doing", "org": "工部"}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_progress("T-5", "正在实现核心模块", "已完成需求分析✅|正在写代码🔄|待测试")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
assert task["now"] == "正在实现核心模块"
assert len(task.get("progress_log", [])) == 1
todos = task.get("todos", [])
assert len(todos) == 3
statuses = {td["title"]: td["status"] for td in todos}
assert statuses["已完成需求分析"] == "completed"
assert statuses["正在写代码"] == "in-progress"
assert statuses["待测试"] == "not-started"
finally:
kb.TASKS_FILE = original
def test_todo(tmp_path):
"""cmd_todo adds and updates sub-tasks."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-6", "title": "todo test", "state": "Doing"}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
kb.cmd_todo("T-6", "1", "实现登录接口", "in-progress")
kb.cmd_todo("T-6", "2", "编写测试", "not-started")
kb.cmd_todo("T-6", "1", "", "completed")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
todos = {td["id"]: td for td in task.get("todos", [])}
assert todos["1"]["status"] == "completed"
assert todos["2"]["status"] == "not-started"
finally:
kb.TASKS_FILE = original
def test_progress_log_capped(tmp_path):
"""progress_log should not exceed MAX_PROGRESS_LOG entries."""
tasks_file = tmp_path / "tasks_source.json"
tasks_file.write_text(json.dumps([
{"id": "T-7", "title": "日志上限测试", "state": "Doing", "org": "礼部"}
], ensure_ascii=False), encoding="utf-8")
original = kb.TASKS_FILE
kb.TASKS_FILE = tasks_file
try:
for i in range(kb.MAX_PROGRESS_LOG + 5):
kb.cmd_progress("T-7", f"{i}次进展汇报内容,描述当前执行情况")
tasks = json.loads(tasks_file.read_text(encoding="utf-8"))
task = tasks[0]
assert len(task.get("progress_log", [])) == kb.MAX_PROGRESS_LOG
finally:
kb.TASKS_FILE = original