test_workflow_manager.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import json
  4. import sys
  5. from pathlib import Path
  6. def _load_module():
  7. scripts_dir = Path(__file__).resolve().parents[2]
  8. if str(scripts_dir) not in sys.path:
  9. sys.path.insert(0, str(scripts_dir))
  10. import workflow_manager
  11. return workflow_manager
  12. def test_workflow_lifecycle_and_trace(tmp_path, monkeypatch):
  13. module = _load_module()
  14. monkeypatch.setattr(module, "find_project_root", lambda: tmp_path)
  15. webnovel_dir = tmp_path / ".webnovel"
  16. webnovel_dir.mkdir(parents=True, exist_ok=True)
  17. module.start_task("webnovel-write", {"chapter_num": 7})
  18. module.start_step("Step 1", "Context")
  19. module.complete_step("Step 1", json.dumps({"state_json_modified": True}, ensure_ascii=False))
  20. module.complete_task(json.dumps({"review_completed": True}, ensure_ascii=False))
  21. state = module.load_state()
  22. assert state["current_task"] is None
  23. assert state["history"][-1]["status"] == module.TASK_STATUS_COMPLETED
  24. assert state["last_stable_state"]["artifacts"]["review_completed"] is True
  25. trace_path = module.get_call_trace_path()
  26. assert trace_path.exists()
  27. lines = trace_path.read_text(encoding="utf-8").strip().splitlines()
  28. events = [json.loads(line)["event"] for line in lines if line.strip()]
  29. assert "task_started" in events
  30. assert "step_started" in events
  31. assert "step_completed" in events
  32. assert "task_completed" in events
  33. def test_start_task_reentry_increments_retry(tmp_path, monkeypatch):
  34. module = _load_module()
  35. monkeypatch.setattr(module, "find_project_root", lambda: tmp_path)
  36. webnovel_dir = tmp_path / ".webnovel"
  37. webnovel_dir.mkdir(parents=True, exist_ok=True)
  38. module.start_task("webnovel-write", {"chapter_num": 8})
  39. module.start_task("webnovel-write", {"chapter_num": 8})
  40. state = module.load_state()
  41. task = state["current_task"]
  42. assert task is not None
  43. assert task["status"] == module.TASK_STATUS_RUNNING
  44. assert int(task.get("retry_count", 0)) >= 1
  45. def test_complete_step_rejects_mismatch_step_id(tmp_path, monkeypatch):
  46. module = _load_module()
  47. monkeypatch.setattr(module, "find_project_root", lambda: tmp_path)
  48. webnovel_dir = tmp_path / ".webnovel"
  49. webnovel_dir.mkdir(parents=True, exist_ok=True)
  50. module.start_task("webnovel-write", {"chapter_num": 9})
  51. module.start_step("Step 2A", "Draft")
  52. module.complete_step("Step 2B")
  53. state = module.load_state()
  54. current_step = state["current_task"]["current_step"]
  55. assert current_step is not None
  56. assert current_step["id"] == "Step 2A"
  57. assert current_step["status"] == module.STEP_STATUS_RUNNING
  58. def test_workflow_step_owner_and_order_violation_trace(tmp_path, monkeypatch):
  59. module = _load_module()
  60. monkeypatch.setattr(module, "find_project_root", lambda: tmp_path)
  61. webnovel_dir = tmp_path / ".webnovel"
  62. webnovel_dir.mkdir(parents=True, exist_ok=True)
  63. assert module.expected_step_owner("webnovel-write", "Step 1") == "context-agent"
  64. assert module.expected_step_owner("webnovel-write", "Step 5") == "data-agent"
  65. module.start_task("webnovel-write", {"chapter_num": 12})
  66. module.start_step("Step 3", "Review")
  67. trace_path = module.get_call_trace_path()
  68. lines = [json.loads(line) for line in trace_path.read_text(encoding="utf-8").splitlines() if line.strip()]
  69. events = [row.get("event") for row in lines]
  70. assert "step_order_violation" in events
  71. step_started = [row for row in lines if row.get("event") == "step_started"]
  72. assert step_started
  73. assert step_started[-1].get("payload", {}).get("expected_owner") == "review-agents"