208 lines
7.6 KiB
Python
208 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Regression tests for the Todo Skill Golden Routes and deterministic scripts."""
|
|
|
|
import contextlib
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from datetime import timedelta
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
|
|
sys.dont_write_bytecode = True
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
TODO_ROOT = ROOT / "skills" / "multi" / "dingtalk-todo"
|
|
|
|
|
|
def load_script(filename):
|
|
path = TODO_ROOT / "scripts" / filename
|
|
spec = importlib.util.spec_from_file_location(f"todo_test_{path.stem}", path)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f"cannot load {path}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
DAILY = load_script("todo_daily_summary.py")
|
|
OVERDUE = load_script("todo_overdue_check.py")
|
|
BATCH = load_script("todo_batch_create.py")
|
|
|
|
|
|
class TodoSkillAlignmentTest(unittest.TestCase):
|
|
def test_golden_routes_prefer_verified_shortcuts(self):
|
|
skill = (TODO_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
for route in (
|
|
"todo +remind",
|
|
"todo +assign",
|
|
"todo +create",
|
|
"todo +get-my-tasks",
|
|
"todo +search",
|
|
"todo +complete",
|
|
"todo +update",
|
|
"todo +reminder",
|
|
):
|
|
with self.subTest(route=route):
|
|
self.assertIn(route, skill)
|
|
self.assertIn("## Golden Routes", skill)
|
|
self.assertIn("一次最多加载一个操作 Reference", skill)
|
|
self.assertLessEqual(len(skill.encode("utf-8")), 16000)
|
|
|
|
def test_references_are_todo_only_and_reminder_contract_is_consistent(self):
|
|
combined = "\n".join(
|
|
path.read_text(encoding="utf-8")
|
|
for path in (TODO_ROOT / "references").rglob("*.md")
|
|
)
|
|
self.assertNotIn("## #7 听记与会后", combined)
|
|
self.assertNotIn("minutes list all", combined)
|
|
self.assertNotIn("当前不支持单独的 `reminder`", combined)
|
|
self.assertIn("提醒写入目前没有对应的查询接口", combined)
|
|
|
|
def test_markdown_links_resolve(self):
|
|
missing = []
|
|
pattern = re.compile(r"\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)")
|
|
for document in TODO_ROOT.rglob("*.md"):
|
|
for target in pattern.findall(document.read_text(encoding="utf-8")):
|
|
if "://" not in target and not (document.parent / target).resolve().exists():
|
|
missing.append(f"{document.relative_to(ROOT)} -> {target}")
|
|
self.assertEqual([], missing)
|
|
|
|
|
|
class TodoDailySummaryTest(unittest.TestCase):
|
|
def test_uses_bounded_shortcut_and_excludes_missing_due(self):
|
|
start, _ = DAILY.date_range("today")
|
|
inside = int((start + timedelta(hours=9)).timestamp() * 1000)
|
|
outside = int((start + timedelta(days=2)).timestamp() * 1000)
|
|
calls = []
|
|
|
|
def fake_run(args, dws="dws"):
|
|
calls.append(args)
|
|
return {
|
|
"ok": True,
|
|
"outcome": "success",
|
|
"data": {
|
|
"complete": True,
|
|
"count": 3,
|
|
"todos": [
|
|
{"taskId": "in", "subject": "inside", "dueTime": inside},
|
|
{"taskId": "none", "subject": "no due"},
|
|
{"taskId": "out", "subject": "outside", "dueTime": outside},
|
|
],
|
|
},
|
|
}
|
|
|
|
stdout = io.StringIO()
|
|
with mock.patch.object(DAILY, "run_dws_json", side_effect=fake_run):
|
|
with contextlib.redirect_stdout(stdout):
|
|
code = DAILY.run(["today"])
|
|
self.assertEqual(0, code)
|
|
self.assertEqual(1, len(calls))
|
|
self.assertEqual(["todo", "+get-my-tasks"], calls[0][:2])
|
|
self.assertIn("--all", calls[0])
|
|
self.assertIn("--plan-finish-start", calls[0])
|
|
self.assertEqual(
|
|
["in"], [item["taskId"] for item in json.loads(stdout.getvalue())["todos"]]
|
|
)
|
|
|
|
def test_incomplete_traversal_fails_closed(self):
|
|
payload = {"ok": True, "data": {"complete": False, "todos": []}}
|
|
stdout = io.StringIO()
|
|
with mock.patch.object(DAILY, "run_dws_json", return_value=payload):
|
|
with contextlib.redirect_stdout(stdout):
|
|
code = DAILY.run(["today"])
|
|
self.assertEqual(2, code)
|
|
self.assertFalse(json.loads(stdout.getvalue())["complete"])
|
|
|
|
|
|
class TodoOverdueTest(unittest.TestCase):
|
|
def test_uses_overdue_shortcut_and_empty_is_success(self):
|
|
calls = []
|
|
|
|
def fake_run(args, dws="dws"):
|
|
calls.append(args)
|
|
return {"ok": True, "outcome": "success", "data": {"overdue": []}}
|
|
|
|
stdout = io.StringIO()
|
|
with mock.patch.object(OVERDUE, "run_dws_json", side_effect=fake_run):
|
|
with contextlib.redirect_stdout(stdout):
|
|
code = OVERDUE.run([])
|
|
self.assertEqual(0, code)
|
|
self.assertEqual(["todo", "+overdue", "--format", "json"], calls[0])
|
|
self.assertEqual(0, json.loads(stdout.getvalue())["count"])
|
|
|
|
|
|
class TodoBatchCreateTest(unittest.TestCase):
|
|
def test_batch_uses_iso_due_captures_id_and_reads_back(self):
|
|
with tempfile.TemporaryDirectory() as raw:
|
|
source = Path(raw) / "todos.json"
|
|
source.write_text(
|
|
json.dumps(
|
|
[
|
|
{
|
|
"title": "reviewed task",
|
|
"executors": "user1",
|
|
"priority": 40,
|
|
"due": "2026-08-18",
|
|
}
|
|
]
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
calls = []
|
|
|
|
def fake_run(args, dws="dws", **kwargs):
|
|
calls.append(args)
|
|
if args[:3] == ["todo", "task", "create"]:
|
|
return {"result": {"taskId": "task-1"}}
|
|
return {
|
|
"ok": True,
|
|
"data": {
|
|
"todoDetailModel": {
|
|
"taskId": "task-1",
|
|
"subject": "reviewed task",
|
|
}
|
|
},
|
|
}
|
|
|
|
stdout = io.StringIO()
|
|
with mock.patch.object(BATCH, "run_dws_json", side_effect=fake_run):
|
|
with contextlib.redirect_stdout(stdout):
|
|
code = BATCH.run([str(source)])
|
|
self.assertEqual(0, code)
|
|
self.assertEqual(2, len(calls))
|
|
due = calls[0][calls[0].index("--due") + 1]
|
|
self.assertIn("T23:59:59+08:00", due)
|
|
self.assertEqual(
|
|
["todo", "task", "get", "--task-id", "task-1"], calls[1][:5]
|
|
)
|
|
payload = json.loads(stdout.getvalue())
|
|
self.assertTrue(payload["complete"])
|
|
self.assertEqual("verified", payload["ledger"][0]["status"])
|
|
|
|
def test_possible_commit_is_preserved_as_unknown(self):
|
|
with tempfile.TemporaryDirectory() as raw:
|
|
source = Path(raw) / "todos.json"
|
|
source.write_text(
|
|
'[{"title":"x","executors":"u"}]', encoding="utf-8"
|
|
)
|
|
failure = BATCH.ScriptError("timeout", commit_unknown=True)
|
|
stdout = io.StringIO()
|
|
with mock.patch.object(BATCH, "run_dws_json", side_effect=failure):
|
|
with contextlib.redirect_stdout(stdout):
|
|
code = BATCH.run([str(source)])
|
|
self.assertEqual(2, code)
|
|
payload = json.loads(stdout.getvalue())
|
|
self.assertEqual(1, payload["unknownCount"])
|
|
self.assertEqual("unknown", payload["ledger"][0]["status"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|