Files
edict/tests/test_state_machine_consistency.py
cft0808 74d8130391 feat: Week 0-4 optimizations - event bus, state machine, dispatch, outbox relay
- EventBus: Redis Streams pub/sub for decoupled service communication
- State machine: strict lifecycle transitions with audit logging
- Dispatch worker: parallel execution, retry with backoff, resource locking
- Orchestrator: DAG-based task decomposition and dependency resolution
- Outbox relay: transactional outbox pattern for reliable event delivery
- Auth: dashboard authentication module
- Agent groups: sansheng/liubu agent configuration
- CI/CD: Docker publish workflow, systemd service, start script
- Frontend: dashboard build assets
- Tests: state machine consistency tests
2026-04-04 12:16:32 +08:00

104 lines
3.7 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.
"""CI 守卫 — 确保 kanban_update.py 和 task.py 的状态转换表完全一致。
如果此测试失败,说明有人只改了一侧的状态机而没有同步另一侧。
kanban_update.py 应通过 _load_canonical_transitions() 从 task.py 动态加载,
但 fallback 定义也必须保持一致。
"""
import ast
import pathlib
import re
import sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
SCRIPTS = ROOT / "scripts"
sys.path.insert(0, str(SCRIPTS))
def _load_pg_transitions() -> dict[str, set[str]]:
"""从 task.py 源码解析 STATE_TRANSITIONS无需 import避免 SQLAlchemy 依赖)。"""
task_py = ROOT / "edict" / "backend" / "app" / "models" / "task.py"
source = task_py.read_text(encoding="utf-8")
# 提取 TaskState enum 成员名
enum_names = set(re.findall(r"^\s+(\w+)\s*=\s*\"(\w+)\"", source, re.MULTILINE))
name_set = {name for name, _ in enum_names}
# 提取 STATE_TRANSITIONS block — from "STATE_TRANSITIONS = {" to the closing "}"
m = re.search(r"STATE_TRANSITIONS\s*=\s*\{", source)
if not m:
raise ValueError("Cannot find STATE_TRANSITIONS in task.py")
start = m.start()
# Find the matching closing brace
depth = 0
end = start
for i, ch in enumerate(source[start:], start):
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
end = i + 1
break
block = source[start:end]
# Parse: replace TaskState.XXX with "XXX"
cleaned = re.sub(r"TaskState\.(\w+)", r'"\1"', block)
# Replace set literal {x, y} that appears as values → ["x", "y"] isn't needed,
# we can eval as Python since cleaned is now pure str/set literals
# But we need to replace the outer dict assignment
cleaned = cleaned.replace("STATE_TRANSITIONS =", "result =")
local_ns: dict = {}
exec(cleaned, {}, local_ns)
raw = local_ns["result"]
return {state: targets for state, targets in raw.items()}
def test_state_transitions_consistent():
"""kanban _VALID_TRANSITIONS 必须与 task.py STATE_TRANSITIONS 完全一致。"""
import kanban_update as kb
pg = _load_pg_transitions()
json_t = kb._VALID_TRANSITIONS
# 检查 Postgres 侧每个状态在 JSON 侧都有且一致
for state, pg_targets in pg.items():
json_targets = json_t.get(state, set())
assert json_targets == pg_targets, (
f"State machine drift at '{state}': "
f"JSON allows {sorted(json_targets)}, "
f"Postgres allows {sorted(pg_targets)}"
)
# 检查 JSON 侧没有 Postgres 侧不存在的非终态
for state, json_targets in json_t.items():
if not json_targets: # 终态 (Done, Cancelled) 可以只在 JSON 侧有
continue
assert state in pg, (
f"State '{state}' exists in JSON transitions but not in Postgres"
)
def test_pending_confirm_exists():
"""PendingConfirm 必须在两侧都存在。"""
import kanban_update as kb
pg = _load_pg_transitions()
assert "PendingConfirm" in pg, "PendingConfirm missing from task.py STATE_TRANSITIONS"
assert "PendingConfirm" in kb._VALID_TRANSITIONS, "PendingConfirm missing from kanban _VALID_TRANSITIONS"
def test_pending_has_outgoing_edges():
"""Pending 不能是死胡同。"""
pg = _load_pg_transitions()
assert pg.get("Pending"), "Pending has no outgoing edges in task.py (dead-end)"
def test_terminal_states_have_no_outgoing():
"""Done 和 Cancelled 不应有出边。"""
pg = _load_pg_transitions()
assert not pg.get("Done", set()), "Done should have no outgoing edges"
assert not pg.get("Cancelled", set()), "Cancelled should have no outgoing edges"