test_workflow_designer_debugger.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from __future__ import annotations
  2. from tests.conftest import build_fastapi_test_client, prepare_known_service_import
  3. prepare_known_service_import("workflow-service")
  4. from app.api.routes import get_workflow_application_service
  5. from app.application.designer import WorkflowDebugPlan, build_debug_plan
  6. from app.bootstrap.app import create_app
  7. def test_workflow_designer_validate_returns_graph_diagnostics() -> None:
  8. client = build_fastapi_test_client(create_app())
  9. response = client.post(
  10. "/workflows/designer/validate",
  11. json={
  12. "dsl_json": {
  13. "code": "order_flow",
  14. "name": "Order Flow",
  15. "nodes": [
  16. {"id": "start", "type": "llm", "name": "Start"},
  17. {"id": "answer", "type": "answer", "name": "Answer"},
  18. {"id": "orphan", "type": "tool", "name": "Orphan Tool"},
  19. ],
  20. "edges": [{"source": "start", "target": "answer"}],
  21. }
  22. })
  23. assert response.status_code == 200
  24. payload = response.json()
  25. assert payload["valid"] is True
  26. assert payload["node_count"] == 3
  27. assert payload["edge_count"] == 1
  28. assert payload["entry_node_ids"] == ["start", "orphan"]
  29. assert payload["terminal_node_ids"] == ["answer", "orphan"]
  30. assert payload["isolated_node_ids"] == ["orphan"]
  31. assert payload["unreachable_node_ids"] == []
  32. assert payload["diagnostics"][0]["code"] == "workflow.multiple_entry_nodes"
  33. assert payload["normalized_dsl_json"]["nodes"][0]["config"] == {}
  34. def test_workflow_designer_validate_reports_blocking_errors() -> None:
  35. client = build_fastapi_test_client(create_app())
  36. response = client.post(
  37. "/workflows/designer/validate",
  38. json={
  39. "dsl_json": {
  40. "code": "broken_flow",
  41. "nodes": [{"id": "start", "type": "llm"}],
  42. "edges": [{"source": "start", "target": "missing"}],
  43. }
  44. })
  45. assert response.status_code == 200
  46. payload = response.json()
  47. assert payload["valid"] is False
  48. assert payload["edges"][0]["valid_source"] is True
  49. assert payload["edges"][0]["valid_target"] is False
  50. assert any(item["code"] == "edge.target_missing" for item in payload["diagnostics"])
  51. def test_workflow_debugger_returns_execution_preview() -> None:
  52. client = build_fastapi_test_client(create_app())
  53. response = client.post(
  54. "/workflows/designer/debug",
  55. json={
  56. "dsl_json": {
  57. "code": "debug_flow",
  58. "nodes": [
  59. {"id": "start", "type": "llm"},
  60. {"id": "tool", "type": "tool"},
  61. {"id": "answer", "type": "answer"},
  62. ],
  63. "edges": [
  64. {"source": "start", "target": "tool"},
  65. {"source": "tool", "target": "answer"},
  66. ],
  67. },
  68. "max_preview_steps": 10,
  69. })
  70. assert response.status_code == 200
  71. payload = response.json()
  72. assert payload["valid"] is True
  73. assert payload["truncated"] is False
  74. assert [item["node_id"] for item in payload["execution_preview"]] == [
  75. "start",
  76. "tool",
  77. "answer",
  78. ]
  79. assert payload["execution_preview"][0]["next_node_ids"] == ["tool"]
  80. class FakeWorkflowDebugService:
  81. def build_version_debug_plan(
  82. self,
  83. *,
  84. workflow_version_id: str,
  85. max_preview_steps: int) -> WorkflowDebugPlan | None:
  86. assert workflow_version_id == "version-1"
  87. return build_debug_plan(
  88. {
  89. "code": "persisted_flow",
  90. "nodes": [
  91. {"id": "start", "type": "llm"},
  92. {"id": "answer", "type": "answer"},
  93. ],
  94. "edges": [{"source": "start", "target": "answer"}],
  95. },
  96. max_preview_steps=max_preview_steps)
  97. def test_workflow_version_debugger_api_uses_persisted_dsl_service() -> None:
  98. app = create_app()
  99. app.dependency_overrides[get_workflow_application_service] = lambda: FakeWorkflowDebugService()
  100. client = build_fastapi_test_client(app)
  101. debug_response = client.get(
  102. "/workflows/versions/version-1/debug")
  103. assert debug_response.status_code == 200
  104. debug_payload = debug_response.json()
  105. assert debug_payload["valid"] is True
  106. assert [item["node_id"] for item in debug_payload["execution_preview"]] == [
  107. "start",
  108. "answer",
  109. ]