Files
2026-07-13 13:32:32 +08:00

127 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""端到端测试 kanban_update.py 的清洗+创建+流转全流程
既可以 pytest 运行,也可以 python3 直接运行。
"""
import sys, os, json, pathlib, pytest
# 切换到 scripts 目录(file_lock 依赖)
_SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), '..', 'scripts')
os.chdir(_SCRIPTS_DIR)
sys.path.insert(0, '.')
from kanban_update import (
_sanitize_title, _sanitize_remark, _is_valid_task_title,
cmd_create, cmd_flow, cmd_state, cmd_done, load, TASKS_FILE
)
# ── 确保 data 目录和 tasks_source.json 存在(CI 环境可能没有)
data_dir = TASKS_FILE.parent
if data_dir.exists() and not data_dir.is_dir():
data_dir.unlink()
data_dir.mkdir(parents=True, exist_ok=True)
if not TASKS_FILE.exists():
TASKS_FILE.write_text('[]')
def _get_task(tid):
return next((x for x in load() if x['id'] == tid), None)
@pytest.fixture(autouse=True)
def _backup_and_restore():
"""每个测试前备份数据,测试后恢复并清理测试任务。"""
backup = TASKS_FILE.read_text()
yield
TASKS_FILE.write_text(backup)
tasks = json.loads(TASKS_FILE.read_text())
tasks = [t for t in tasks if not t.get('id', '').startswith('JJC-TEST-')]
TASKS_FILE.write_text(json.dumps(tasks, ensure_ascii=False, indent=2))
# ── TEST 1: 脏标题(含文件路径+Conversation)应被清洗后创建
def test_dirty_title_cleaned():
cmd_create('JJC-TEST-E2E-01',
'全面审查/Users/bingsen/clawd/openclaw-sansheng-liubu/这个项目\nConversation info (xxx)',
'Zhongshu', '中书省', '中书令',
'下旨(自动预建):全面审查/Users/bingsen/clawd/项目')
t = _get_task('JJC-TEST-E2E-01')
assert t is not None, "任务应被创建"
assert '/Users' not in t['title'], f"标题不应含路径: {t['title']}"
assert 'Conversation' not in t['title'], f"标题不应含Conversation: {t['title']}"
assert '自动预建' not in t['flow_log'][0]['remark'], f"remark不应含自动预建: {t['flow_log'][0]['remark']}"
assert '/Users' not in t['flow_log'][0]['remark'], f"remark不应含路径: {t['flow_log'][0]['remark']}"
# ── TEST 2: 纯文件路径标题被拒绝
def test_pure_path_rejected():
cmd_create('JJC-TEST-E2E-02', '/Users/bingsen/clawd/openclaw-sansheng-liubu/', 'Zhongshu', '中书省', '中书令')
assert _get_task('JJC-TEST-E2E-02') is None, "纯路径标题应被拒绝"
# ── TEST 3: 正常标题正常创建
def test_normal_title():
cmd_create('JJC-TEST-E2E-03', '调研工业数据分析大模型应用方案', 'Zhongshu', '中书省', '中书令', '太子整理旨意')
t = _get_task('JJC-TEST-E2E-03')
assert t is not None, "正常任务应被创建"
assert t['title'] == '调研工业数据分析大模型应用方案', f"标题应完整保留: {t['title']}"
# ── TEST 4: flow remark 清洗
def test_flow_remark_cleaned():
cmd_create('JJC-TEST-E2E-04', '调研工业数据分析大模型应用方案', 'Zhongshu', '中书省', '中书令')
cmd_flow('JJC-TEST-E2E-04', '太子', '中书省', '旨意传达:审查/Users/bingsen/clawd/xxx项目 Conversation blah')
t = _get_task('JJC-TEST-E2E-04')
assert t is not None
last_flow = t['flow_log'][-1]
assert '/Users' not in last_flow['remark'], f"remark不应含路径: {last_flow['remark']}"
assert 'Conversation' not in last_flow['remark'], f"remark不应含Conversation: {last_flow['remark']}"
# ── TEST 5: 太短标题拒绝
def test_short_title_rejected():
cmd_create('JJC-TEST-E2E-05', '好的', 'Zhongshu', '中书省', '中书令')
assert _get_task('JJC-TEST-E2E-05') is None, "短标题应被拒绝"
# ── TEST 6: 传旨前缀剥离
def test_prefix_stripped():
cmd_create('JJC-TEST-E2E-06', '传旨:帮我写技术博客文章关于智能体架构', 'Zhongshu', '中书省', '中书令')
t = _get_task('JJC-TEST-E2E-06')
assert t is not None, "任务应被创建"
assert not t['title'].startswith('传旨'), f"前缀应被剥离: {t['title']}"
# ── TEST 7: state 更新 + org 自动联动
def test_state_update():
cmd_create('JJC-TEST-E2E-07', '测试状态更新与组织联动功能', 'Zhongshu', '中书省', '中书令')
cmd_state('JJC-TEST-E2E-07', 'Menxia', '方案提交门下省审议')
t = _get_task('JJC-TEST-E2E-07')
assert t is not None
assert t['state'] == 'Menxia', f"state应为Menxia: {t['state']}"
assert t['org'] == '门下省', f"org应为门下省: {t['org']}"
# ── TEST 8: done 完成
def test_done():
cmd_create('JJC-TEST-E2E-08', '测试任务完成状态标记功能', 'Zhongshu', '中书省', '中书令')
cmd_done('JJC-TEST-E2E-08', '/tmp/output.md', '任务已完成')
t = _get_task('JJC-TEST-E2E-08')
assert t is not None
assert t['state'] == 'Done', f"state应为Done: {t['state']}"
# ── TEST 9: 已完成任务不可覆盖
def test_done_not_overwritable():
cmd_create('JJC-TEST-E2E-09', '测试已完成任务不可覆盖保护', 'Zhongshu', '中书省', '中书令')
cmd_done('JJC-TEST-E2E-09', '/tmp/output.md', '任务已完成')
cmd_create('JJC-TEST-E2E-09', '试图覆盖已完成的任务标题', 'Zhongshu', '中书省', '中书令')
t = _get_task('JJC-TEST-E2E-09')
assert t is not None
assert t['state'] == 'Done', f"仍应为Done: {t['state']}"
# ── 支持直接运行 python3 tests/test_e2e_kanban.py
if __name__ == '__main__':
sys.exit(pytest.main([__file__, '-v']))