vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vellum-ai might be problematic. Click here for more details.
- vellum/__init__.py +18 -0
- vellum/client/README.md +1 -1
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/core/force_multipart.py +4 -2
- vellum/client/core/http_response.py +1 -1
- vellum/client/core/pydantic_utilities.py +7 -4
- vellum/client/errors/too_many_requests_error.py +1 -2
- vellum/client/reference.md +677 -76
- vellum/client/resources/container_images/client.py +299 -0
- vellum/client/resources/container_images/raw_client.py +286 -0
- vellum/client/resources/documents/client.py +20 -10
- vellum/client/resources/documents/raw_client.py +20 -10
- vellum/client/resources/events/raw_client.py +4 -4
- vellum/client/resources/integration_auth_configs/client.py +2 -0
- vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
- vellum/client/resources/integration_providers/client.py +28 -2
- vellum/client/resources/integration_providers/raw_client.py +24 -0
- vellum/client/resources/integrations/client.py +52 -4
- vellum/client/resources/integrations/raw_client.py +61 -0
- vellum/client/resources/workflow_deployments/client.py +156 -0
- vellum/client/resources/workflow_deployments/raw_client.py +334 -0
- vellum/client/resources/workflows/client.py +212 -8
- vellum/client/resources/workflows/raw_client.py +343 -6
- vellum/client/types/__init__.py +18 -0
- vellum/client/types/api_actor_type_enum.py +1 -1
- vellum/client/types/check_workflow_execution_status_error.py +21 -0
- vellum/client/types/check_workflow_execution_status_response.py +29 -0
- vellum/client/types/code_execution_package_request.py +21 -0
- vellum/client/types/composio_execute_tool_request.py +5 -0
- vellum/client/types/composio_tool_definition.py +1 -0
- vellum/client/types/container_image_build_config.py +1 -0
- vellum/client/types/container_image_container_image_tag.py +1 -0
- vellum/client/types/dataset_row_push_request.py +3 -0
- vellum/client/types/document_document_to_document_index.py +1 -0
- vellum/client/types/integration_name.py +24 -0
- vellum/client/types/node_execution_fulfilled_body.py +1 -0
- vellum/client/types/node_execution_log_body.py +24 -0
- vellum/client/types/node_execution_log_event.py +47 -0
- vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
- vellum/client/types/runner_config_request.py +24 -0
- vellum/client/types/severity_enum.py +5 -0
- vellum/client/types/slim_composio_tool_definition.py +1 -0
- vellum/client/types/slim_document_document_to_document_index.py +2 -0
- vellum/client/types/type_checker_enum.py +5 -0
- vellum/client/types/vellum_audio.py +5 -1
- vellum/client/types/vellum_audio_request.py +5 -1
- vellum/client/types/vellum_document.py +5 -1
- vellum/client/types/vellum_document_request.py +5 -1
- vellum/client/types/vellum_image.py +5 -1
- vellum/client/types/vellum_image_request.py +5 -1
- vellum/client/types/vellum_node_execution_event.py +2 -0
- vellum/client/types/vellum_variable.py +5 -0
- vellum/client/types/vellum_variable_extensions.py +1 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/client/types/vellum_video.py +5 -1
- vellum/client/types/vellum_video_request.py +5 -1
- vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
- vellum/client/types/workflow_event.py +2 -0
- vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
- vellum/client/types/workflow_result_event_output_data_array.py +1 -1
- vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
- vellum/client/types/workflow_result_event_output_data_error.py +1 -1
- vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
- vellum/client/types/workflow_result_event_output_data_json.py +1 -1
- vellum/client/types/workflow_result_event_output_data_number.py +1 -1
- vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
- vellum/client/types/workflow_result_event_output_data_string.py +1 -1
- vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
- vellum/plugins/vellum_mypy.py +37 -2
- vellum/types/check_workflow_execution_status_error.py +3 -0
- vellum/types/check_workflow_execution_status_response.py +3 -0
- vellum/types/code_execution_package_request.py +3 -0
- vellum/types/node_execution_log_body.py +3 -0
- vellum/types/node_execution_log_event.py +3 -0
- vellum/types/runner_config_request.py +3 -0
- vellum/types/severity_enum.py +3 -0
- vellum/types/type_checker_enum.py +3 -0
- vellum/types/workflow_sandbox_execute_node_response.py +3 -0
- vellum/utils/files/mixin.py +26 -0
- vellum/utils/files/tests/test_mixin.py +62 -0
- vellum/utils/tests/test_vellum_client.py +95 -0
- vellum/utils/uuid.py +19 -2
- vellum/utils/vellum_client.py +10 -3
- vellum/workflows/__init__.py +7 -1
- vellum/workflows/descriptors/base.py +86 -0
- vellum/workflows/descriptors/tests/test_utils.py +9 -0
- vellum/workflows/errors/tests/__init__.py +0 -0
- vellum/workflows/errors/tests/test_types.py +52 -0
- vellum/workflows/errors/types.py +1 -0
- vellum/workflows/events/node.py +24 -0
- vellum/workflows/events/tests/test_event.py +123 -0
- vellum/workflows/events/types.py +2 -1
- vellum/workflows/events/workflow.py +28 -2
- vellum/workflows/expressions/add.py +3 -0
- vellum/workflows/expressions/tests/test_add.py +24 -0
- vellum/workflows/graph/graph.py +26 -5
- vellum/workflows/graph/tests/test_graph.py +228 -1
- vellum/workflows/inputs/base.py +22 -6
- vellum/workflows/inputs/dataset_row.py +121 -16
- vellum/workflows/inputs/tests/test_inputs.py +3 -3
- vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
- vellum/workflows/integrations/vellum_integration_service.py +12 -1
- vellum/workflows/loaders/base.py +2 -0
- vellum/workflows/nodes/bases/base.py +37 -16
- vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -5
- vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
- vellum/workflows/nodes/core/retry_node/node.py +1 -0
- vellum/workflows/nodes/core/try_node/node.py +1 -0
- vellum/workflows/nodes/displayable/api_node/node.py +3 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
- vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
- vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
- vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
- vellum/workflows/nodes/displayable/note_node/node.py +4 -1
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
- vellum/workflows/nodes/mocks.py +47 -213
- vellum/workflows/nodes/tests/test_mocks.py +0 -177
- vellum/workflows/nodes/utils.py +23 -8
- vellum/workflows/outputs/base.py +36 -3
- vellum/workflows/references/environment_variable.py +1 -11
- vellum/workflows/references/lazy.py +8 -0
- vellum/workflows/references/state_value.py +24 -1
- vellum/workflows/references/tests/test_lazy.py +58 -0
- vellum/workflows/references/trigger.py +8 -3
- vellum/workflows/references/workflow_input.py +8 -0
- vellum/workflows/resolvers/resolver.py +13 -3
- vellum/workflows/resolvers/tests/test_resolver.py +31 -0
- vellum/workflows/runner/runner.py +159 -14
- vellum/workflows/runner/tests/__init__.py +0 -0
- vellum/workflows/runner/tests/test_runner.py +170 -0
- vellum/workflows/sandbox.py +7 -8
- vellum/workflows/state/base.py +89 -30
- vellum/workflows/state/context.py +74 -3
- vellum/workflows/state/tests/test_state.py +269 -1
- vellum/workflows/tests/test_dataset_row.py +8 -7
- vellum/workflows/tests/test_sandbox.py +97 -8
- vellum/workflows/triggers/__init__.py +2 -1
- vellum/workflows/triggers/base.py +160 -28
- vellum/workflows/triggers/chat_message.py +141 -0
- vellum/workflows/triggers/integration.py +12 -0
- vellum/workflows/triggers/manual.py +3 -1
- vellum/workflows/triggers/schedule.py +3 -1
- vellum/workflows/triggers/tests/test_chat_message.py +257 -0
- vellum/workflows/types/core.py +18 -0
- vellum/workflows/types/definition.py +6 -13
- vellum/workflows/types/generics.py +12 -0
- vellum/workflows/types/tests/test_utils.py +12 -0
- vellum/workflows/types/utils.py +32 -2
- vellum/workflows/types/workflow_metadata.py +124 -0
- vellum/workflows/utils/functions.py +152 -16
- vellum/workflows/utils/pydantic_schema.py +19 -1
- vellum/workflows/utils/tests/test_functions.py +123 -8
- vellum/workflows/utils/tests/test_validate.py +79 -0
- vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
- vellum/workflows/utils/uuids.py +90 -0
- vellum/workflows/utils/validate.py +108 -0
- vellum/workflows/utils/vellum_variables.py +96 -16
- vellum/workflows/workflows/base.py +177 -35
- vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
- vellum_cli/__init__.py +21 -0
- vellum_cli/config.py +16 -2
- vellum_cli/pull.py +2 -0
- vellum_cli/push.py +23 -10
- vellum_cli/tests/conftest.py +8 -13
- vellum_cli/tests/test_image_push.py +4 -11
- vellum_cli/tests/test_pull.py +83 -68
- vellum_cli/tests/test_push.py +251 -2
- vellum_ee/assets/node-definitions.json +225 -12
- vellum_ee/scripts/generate_node_definitions.py +15 -3
- vellum_ee/workflows/display/base.py +4 -3
- vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
- vellum_ee/workflows/display/nodes/types.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
- vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
- vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
- vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
- vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
- vellum_ee/workflows/display/tests/test_mocks.py +912 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
- vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
- vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
- vellum_ee/workflows/display/utils/exceptions.py +34 -0
- vellum_ee/workflows/display/utils/expressions.py +463 -52
- vellum_ee/workflows/display/utils/metadata.py +98 -33
- vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
- vellum_ee/workflows/display/utils/triggers.py +153 -0
- vellum_ee/workflows/display/utils/vellum.py +59 -5
- vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
- vellum_ee/workflows/server/namespaces.py +18 -0
- vellum_ee/workflows/tests/test_display_meta.py +2 -0
- vellum_ee/workflows/tests/test_serialize_module.py +174 -7
- vellum_ee/workflows/tests/test_server.py +0 -3
- vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Tests for IntegrationTrigger attribute type validation during serialization."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from vellum.workflows import BaseWorkflow
|
|
8
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
9
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
10
|
+
from vellum.workflows.state.base import BaseState
|
|
11
|
+
from vellum.workflows.triggers.integration import IntegrationTrigger
|
|
12
|
+
from vellum_ee.workflows.display.utils.exceptions import TriggerValidationError
|
|
13
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LinearIssueTrigger(IntegrationTrigger):
|
|
17
|
+
"""Custom Linear issue trigger for testing."""
|
|
18
|
+
|
|
19
|
+
action: str
|
|
20
|
+
data: dict
|
|
21
|
+
type: str
|
|
22
|
+
url: str
|
|
23
|
+
|
|
24
|
+
class Config:
|
|
25
|
+
provider = "COMPOSIO"
|
|
26
|
+
integration_name = "LINEAR"
|
|
27
|
+
slug = "LINEAR_ISSUE_CREATED_TRIGGER"
|
|
28
|
+
setup_attributes = {"team_id": "test-team-id"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProcessNode(BaseNode):
|
|
32
|
+
"""Node that processes the trigger."""
|
|
33
|
+
|
|
34
|
+
class Outputs(BaseNode.Outputs):
|
|
35
|
+
result = LinearIssueTrigger.action
|
|
36
|
+
|
|
37
|
+
def run(self) -> Outputs:
|
|
38
|
+
return self.Outputs()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
42
|
+
graph = LinearIssueTrigger >> ProcessNode
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _create_mock_tool_definition(properties: Dict[str, dict]) -> MagicMock:
|
|
46
|
+
"""Helper to create a mock tool definition with output_parameters as JSON Schema.
|
|
47
|
+
|
|
48
|
+
For triggers, output_parameters contains the webhook payload schema,
|
|
49
|
+
while input_parameters contains setup/config arguments.
|
|
50
|
+
"""
|
|
51
|
+
mock_tool_def = MagicMock()
|
|
52
|
+
mock_tool_def.name = "LINEAR_ISSUE_CREATED_TRIGGER"
|
|
53
|
+
# output_parameters is a JSON Schema object containing the payload schema
|
|
54
|
+
mock_tool_def.output_parameters = {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": properties,
|
|
57
|
+
"required": list(properties.keys()),
|
|
58
|
+
}
|
|
59
|
+
return mock_tool_def
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_integration_trigger_validation__matching_types():
|
|
63
|
+
"""
|
|
64
|
+
Tests that serialization succeeds when trigger attribute types match the API definition.
|
|
65
|
+
"""
|
|
66
|
+
# GIVEN a tool definition from the API with matching types (JSON Schema format)
|
|
67
|
+
mock_tool_def = _create_mock_tool_definition(
|
|
68
|
+
{
|
|
69
|
+
"action": {"type": "string"},
|
|
70
|
+
"data": {"type": "object"},
|
|
71
|
+
"type": {"type": "string"},
|
|
72
|
+
"url": {"type": "string"},
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# WHEN we serialize the workflow with mocked API
|
|
77
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
78
|
+
|
|
79
|
+
with patch.object(
|
|
80
|
+
workflow_display._client.integrations,
|
|
81
|
+
"retrieve_integration_tool_definition",
|
|
82
|
+
return_value=mock_tool_def,
|
|
83
|
+
):
|
|
84
|
+
# THEN serialization should succeed without raising an error
|
|
85
|
+
result = workflow_display.serialize()
|
|
86
|
+
|
|
87
|
+
# AND the trigger should be serialized correctly
|
|
88
|
+
assert "triggers" in result
|
|
89
|
+
triggers = result["triggers"]
|
|
90
|
+
assert isinstance(triggers, list)
|
|
91
|
+
assert len(triggers) == 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_integration_trigger_validation__type_mismatch():
|
|
95
|
+
"""
|
|
96
|
+
Tests that serialization raises TriggerValidationError when attribute types don't match.
|
|
97
|
+
"""
|
|
98
|
+
# GIVEN a tool definition from the API with mismatched types (JSON Schema format)
|
|
99
|
+
# The API says 'action' should be object (JSON), but our trigger defines it as STRING
|
|
100
|
+
mock_tool_def = _create_mock_tool_definition(
|
|
101
|
+
{
|
|
102
|
+
"action": {"type": "object"},
|
|
103
|
+
"data": {"type": "object"},
|
|
104
|
+
"type": {"type": "string"},
|
|
105
|
+
"url": {"type": "string"},
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# WHEN we serialize the workflow with mocked API
|
|
110
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
111
|
+
|
|
112
|
+
with patch.object(
|
|
113
|
+
workflow_display._client.integrations,
|
|
114
|
+
"retrieve_integration_tool_definition",
|
|
115
|
+
return_value=mock_tool_def,
|
|
116
|
+
):
|
|
117
|
+
# THEN serialization should raise TriggerValidationError
|
|
118
|
+
with pytest.raises(TriggerValidationError) as exc_info:
|
|
119
|
+
workflow_display.serialize()
|
|
120
|
+
|
|
121
|
+
# AND the error message should indicate the type mismatch with expected and actual types
|
|
122
|
+
assert "action" in str(exc_info.value)
|
|
123
|
+
assert "STRING" in str(exc_info.value)
|
|
124
|
+
assert "expected type 'JSON'" in str(exc_info.value)
|
|
125
|
+
assert "The trigger configuration is invalid or contains unsupported values" in str(exc_info.value)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_integration_trigger_validation__api_error():
|
|
129
|
+
"""
|
|
130
|
+
Tests that serialization continues gracefully when the API returns an error.
|
|
131
|
+
"""
|
|
132
|
+
# GIVEN an API that raises an exception
|
|
133
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
134
|
+
|
|
135
|
+
with patch.object(
|
|
136
|
+
workflow_display._client.integrations,
|
|
137
|
+
"retrieve_integration_tool_definition",
|
|
138
|
+
side_effect=Exception("API Error"),
|
|
139
|
+
):
|
|
140
|
+
# WHEN we serialize the workflow
|
|
141
|
+
# THEN serialization should succeed (validation is skipped on API error)
|
|
142
|
+
result = workflow_display.serialize()
|
|
143
|
+
|
|
144
|
+
# AND the trigger should still be serialized
|
|
145
|
+
assert "triggers" in result
|
|
146
|
+
triggers = result["triggers"]
|
|
147
|
+
assert isinstance(triggers, list)
|
|
148
|
+
assert len(triggers) == 1
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_integration_trigger_validation__no_properties():
|
|
152
|
+
"""
|
|
153
|
+
Tests that serialization continues gracefully when the API response has no properties.
|
|
154
|
+
"""
|
|
155
|
+
# GIVEN a tool definition with empty properties in JSON Schema
|
|
156
|
+
mock_tool_def = _create_mock_tool_definition({})
|
|
157
|
+
|
|
158
|
+
# WHEN we serialize the workflow with mocked API
|
|
159
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
160
|
+
|
|
161
|
+
with patch.object(
|
|
162
|
+
workflow_display._client.integrations,
|
|
163
|
+
"retrieve_integration_tool_definition",
|
|
164
|
+
return_value=mock_tool_def,
|
|
165
|
+
):
|
|
166
|
+
# THEN serialization should succeed (validation is skipped when no output_parameters)
|
|
167
|
+
result = workflow_display.serialize()
|
|
168
|
+
|
|
169
|
+
# AND the trigger should still be serialized
|
|
170
|
+
assert "triggers" in result
|
|
171
|
+
triggers = result["triggers"]
|
|
172
|
+
assert isinstance(triggers, list)
|
|
173
|
+
assert len(triggers) == 1
|
|
@@ -16,9 +16,13 @@ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class imp
|
|
|
16
16
|
def test_integration_trigger_with_explicit_entrypoint_node_id():
|
|
17
17
|
"""
|
|
18
18
|
Tests that a workflow with an IntegrationTrigger and explicit entrypoint_node_id
|
|
19
|
-
|
|
19
|
+
does NOT create an ENTRYPOINT node when all branches are trigger-sourced.
|
|
20
|
+
|
|
21
|
+
Even with explicit IDs provided, if all graph branches originate from triggers,
|
|
22
|
+
the ENTRYPOINT node should be skipped.
|
|
20
23
|
"""
|
|
21
24
|
|
|
25
|
+
# GIVEN an IntegrationTrigger workflow with explicit entrypoint_node_id
|
|
22
26
|
class SlackMessageTrigger(IntegrationTrigger):
|
|
23
27
|
message: str
|
|
24
28
|
|
|
@@ -53,8 +57,10 @@ def test_integration_trigger_with_explicit_entrypoint_node_id():
|
|
|
53
57
|
)
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
# WHEN we serialize the workflow
|
|
56
61
|
result: dict = get_workflow_display(workflow_class=TestWorkflow).serialize()
|
|
57
62
|
|
|
63
|
+
# THEN the trigger should be serialized
|
|
58
64
|
assert "workflow_raw_data" in result
|
|
59
65
|
workflow_raw_data = result["workflow_raw_data"]
|
|
60
66
|
assert isinstance(workflow_raw_data, dict)
|
|
@@ -67,22 +73,19 @@ def test_integration_trigger_with_explicit_entrypoint_node_id():
|
|
|
67
73
|
assert isinstance(trigger, dict)
|
|
68
74
|
trigger_id = trigger["id"]
|
|
69
75
|
|
|
76
|
+
# AND there should be NO ENTRYPOINT node (all branches are trigger-sourced)
|
|
70
77
|
nodes = workflow_raw_data["nodes"]
|
|
71
78
|
assert isinstance(nodes, list)
|
|
72
79
|
|
|
73
80
|
entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
|
|
74
81
|
|
|
75
|
-
assert (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
entrypoint_node = entrypoint_nodes[0]
|
|
80
|
-
assert isinstance(entrypoint_node, dict)
|
|
81
|
-
entrypoint_node_id = entrypoint_node["id"]
|
|
82
|
-
|
|
83
|
-
assert entrypoint_node_id == str(explicit_entrypoint_node_id), (
|
|
84
|
-
f"Entrypoint node ID should be {explicit_entrypoint_node_id} from workflow_display, "
|
|
85
|
-
f"not {trigger_id} from trigger"
|
|
82
|
+
assert len(entrypoint_nodes) == 0, (
|
|
83
|
+
"IntegrationTrigger-only workflows should NOT create an ENTRYPOINT node "
|
|
84
|
+
"even with explicit entrypoint_node_id, since all branches are trigger-sourced"
|
|
86
85
|
)
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
# AND edges should use trigger ID as source_node_id
|
|
88
|
+
edges = workflow_raw_data["edges"]
|
|
89
|
+
assert isinstance(edges, list)
|
|
90
|
+
trigger_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == trigger_id]
|
|
91
|
+
assert len(trigger_edges) > 0, "Should have edges from trigger ID"
|
vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py
CHANGED
|
@@ -49,7 +49,11 @@ def test_serialize_workflow_with_list_vellum_document():
|
|
|
49
49
|
input_var = input_variables[0]
|
|
50
50
|
assert input_var["key"] == "documents"
|
|
51
51
|
assert input_var["type"] == "JSON"
|
|
52
|
-
#
|
|
52
|
+
# The schema field now includes the OpenAPI spec for the input type
|
|
53
|
+
assert input_var["schema"] == {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"items": {"$ref": "#/$defs/vellum.client.types.vellum_document.VellumDocument"},
|
|
56
|
+
}
|
|
53
57
|
assert input_var["required"] is True
|
|
54
58
|
assert input_var["default"] is None
|
|
55
59
|
assert input_var["extensions"] == {"color": None}
|
vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py
CHANGED
|
@@ -24,7 +24,12 @@ def test_manual_trigger_serialization():
|
|
|
24
24
|
assert isinstance(triggers, list)
|
|
25
25
|
|
|
26
26
|
assert len(triggers) == 1
|
|
27
|
-
assert triggers[0] == {
|
|
27
|
+
assert triggers[0] == {
|
|
28
|
+
"id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6",
|
|
29
|
+
"type": "MANUAL",
|
|
30
|
+
"name": "manual",
|
|
31
|
+
"attributes": [],
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
def test_manual_trigger_multiple_entrypoints():
|
|
@@ -54,7 +59,12 @@ def test_manual_trigger_multiple_entrypoints():
|
|
|
54
59
|
assert isinstance(triggers, list)
|
|
55
60
|
|
|
56
61
|
assert len(triggers) == 1
|
|
57
|
-
assert triggers[0] == {
|
|
62
|
+
assert triggers[0] == {
|
|
63
|
+
"id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6",
|
|
64
|
+
"type": "MANUAL",
|
|
65
|
+
"name": "manual",
|
|
66
|
+
"attributes": [],
|
|
67
|
+
}
|
|
58
68
|
|
|
59
69
|
|
|
60
70
|
def test_unknown_trigger_type():
|
|
@@ -97,3 +97,114 @@ def test_manual_and_slack_trigger_same_node():
|
|
|
97
97
|
assert slack_edge is not None, (
|
|
98
98
|
f"Should have edge from Slack trigger ({slack_trigger_id}) " f"to ProcessNode ({process_node_id})"
|
|
99
99
|
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_two_integration_triggers_same_node_unique_edge_ids():
|
|
103
|
+
"""
|
|
104
|
+
Tests that when two IntegrationTriggers point to the same node,
|
|
105
|
+
each trigger edge gets a unique ID (not duplicates).
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# GIVEN two different IntegrationTriggers
|
|
109
|
+
class SlackMessageTrigger(IntegrationTrigger):
|
|
110
|
+
message: str
|
|
111
|
+
|
|
112
|
+
class Config(IntegrationTrigger.Config):
|
|
113
|
+
provider = VellumIntegrationProviderType.COMPOSIO
|
|
114
|
+
integration_name = "SLACK"
|
|
115
|
+
slug = "slack_new_message"
|
|
116
|
+
|
|
117
|
+
class GithubPRTrigger(IntegrationTrigger):
|
|
118
|
+
pr_title: str
|
|
119
|
+
|
|
120
|
+
class Config(IntegrationTrigger.Config):
|
|
121
|
+
provider = VellumIntegrationProviderType.COMPOSIO
|
|
122
|
+
integration_name = "GITHUB"
|
|
123
|
+
slug = "github_pr_opened"
|
|
124
|
+
|
|
125
|
+
class ProcessNode(BaseNode):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# AND a workflow where both triggers point to the same node
|
|
129
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
130
|
+
graph = {
|
|
131
|
+
SlackMessageTrigger >> ProcessNode,
|
|
132
|
+
GithubPRTrigger >> ProcessNode,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# WHEN we serialize the workflow
|
|
136
|
+
result = get_workflow_display(workflow_class=TestWorkflow).serialize()
|
|
137
|
+
|
|
138
|
+
# THEN we should have two triggers
|
|
139
|
+
triggers = result["triggers"]
|
|
140
|
+
assert isinstance(triggers, list)
|
|
141
|
+
assert len(triggers) == 2
|
|
142
|
+
|
|
143
|
+
def get_integration_name(trigger: dict) -> str:
|
|
144
|
+
exec_config = trigger.get("exec_config")
|
|
145
|
+
if isinstance(exec_config, dict):
|
|
146
|
+
name = exec_config.get("integration_name")
|
|
147
|
+
return str(name) if name else ""
|
|
148
|
+
return ""
|
|
149
|
+
|
|
150
|
+
slack_trigger = next(
|
|
151
|
+
(t for t in triggers if isinstance(t, dict) and get_integration_name(t) == "SLACK"),
|
|
152
|
+
None,
|
|
153
|
+
)
|
|
154
|
+
assert slack_trigger is not None
|
|
155
|
+
assert isinstance(slack_trigger, dict)
|
|
156
|
+
slack_trigger_id = slack_trigger["id"]
|
|
157
|
+
|
|
158
|
+
github_trigger = next(
|
|
159
|
+
(t for t in triggers if isinstance(t, dict) and get_integration_name(t) == "GITHUB"),
|
|
160
|
+
None,
|
|
161
|
+
)
|
|
162
|
+
assert github_trigger is not None
|
|
163
|
+
assert isinstance(github_trigger, dict)
|
|
164
|
+
github_trigger_id = github_trigger["id"]
|
|
165
|
+
|
|
166
|
+
# AND we should have edges from both triggers
|
|
167
|
+
workflow_raw_data = result["workflow_raw_data"]
|
|
168
|
+
assert isinstance(workflow_raw_data, dict)
|
|
169
|
+
edges = workflow_raw_data["edges"]
|
|
170
|
+
assert isinstance(edges, list)
|
|
171
|
+
|
|
172
|
+
nodes = workflow_raw_data["nodes"]
|
|
173
|
+
assert isinstance(nodes, list)
|
|
174
|
+
process_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") not in ("TERMINAL", "ENTRYPOINT")]
|
|
175
|
+
assert len(process_nodes) > 0
|
|
176
|
+
process_node = process_nodes[0]
|
|
177
|
+
assert isinstance(process_node, dict)
|
|
178
|
+
process_node_id = process_node["id"]
|
|
179
|
+
|
|
180
|
+
slack_edge = next(
|
|
181
|
+
(
|
|
182
|
+
e
|
|
183
|
+
for e in edges
|
|
184
|
+
if isinstance(e, dict)
|
|
185
|
+
and e.get("source_node_id") == slack_trigger_id
|
|
186
|
+
and e.get("target_node_id") == process_node_id
|
|
187
|
+
),
|
|
188
|
+
None,
|
|
189
|
+
)
|
|
190
|
+
assert slack_edge is not None, "Should have edge from Slack trigger to ProcessNode"
|
|
191
|
+
assert isinstance(slack_edge, dict)
|
|
192
|
+
|
|
193
|
+
github_edge = next(
|
|
194
|
+
(
|
|
195
|
+
e
|
|
196
|
+
for e in edges
|
|
197
|
+
if isinstance(e, dict)
|
|
198
|
+
and e.get("source_node_id") == github_trigger_id
|
|
199
|
+
and e.get("target_node_id") == process_node_id
|
|
200
|
+
),
|
|
201
|
+
None,
|
|
202
|
+
)
|
|
203
|
+
assert github_edge is not None, "Should have edge from GitHub trigger to ProcessNode"
|
|
204
|
+
assert isinstance(github_edge, dict)
|
|
205
|
+
|
|
206
|
+
# AND the edge IDs should be unique (not duplicates)
|
|
207
|
+
assert slack_edge["id"] != github_edge["id"], (
|
|
208
|
+
f"Edge IDs should be unique but both are {slack_edge['id']}. "
|
|
209
|
+
"Multiple triggers targeting the same node should have distinct edge IDs."
|
|
210
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Tests for validation when a workflow has no triggers and no entrypoint node."""
|
|
2
|
+
|
|
3
|
+
from vellum.workflows import BaseWorkflow
|
|
4
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
5
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
6
|
+
from vellum.workflows.state.base import BaseState
|
|
7
|
+
from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
|
|
8
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_workflow_serialization_error__no_triggers_no_entrypoint():
|
|
12
|
+
"""
|
|
13
|
+
Tests that serialization adds an error when a workflow has no triggers and no entrypoint node.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# GIVEN a workflow with an empty graph (no triggers and no entrypoint nodes)
|
|
17
|
+
class EmptyWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
18
|
+
graph = set()
|
|
19
|
+
|
|
20
|
+
# WHEN we serialize the workflow
|
|
21
|
+
workflow_display = get_workflow_display(workflow_class=EmptyWorkflow)
|
|
22
|
+
workflow_display.serialize()
|
|
23
|
+
|
|
24
|
+
# THEN the display_context should contain a WorkflowValidationError
|
|
25
|
+
errors = list(workflow_display.display_context.errors)
|
|
26
|
+
validation_errors = [e for e in errors if isinstance(e, WorkflowValidationError)]
|
|
27
|
+
assert len(validation_errors) == 1
|
|
28
|
+
|
|
29
|
+
# AND the error message should be exact and descriptive
|
|
30
|
+
error = validation_errors[0]
|
|
31
|
+
assert str(error) == (
|
|
32
|
+
"Workflow validation error in EmptyWorkflow: "
|
|
33
|
+
"Workflow has no triggers and no entrypoint nodes. "
|
|
34
|
+
"A workflow must have at least one trigger or one node in its graph."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_workflow_serialization_error__no_triggers_no_entrypoint_with_unused_graphs():
|
|
39
|
+
"""
|
|
40
|
+
Tests that serialization adds an error even when a workflow has nodes in unused_graphs
|
|
41
|
+
but no triggers and no entrypoint nodes in the main graph.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# GIVEN a workflow with nodes only in unused_graphs (no triggers and no entrypoint nodes in main graph)
|
|
45
|
+
class UnusedNode(BaseNode):
|
|
46
|
+
class Outputs(BaseNode.Outputs):
|
|
47
|
+
result: str
|
|
48
|
+
|
|
49
|
+
class WorkflowWithOnlyUnusedGraphs(BaseWorkflow[BaseInputs, BaseState]):
|
|
50
|
+
graph = set()
|
|
51
|
+
unused_graphs = {UnusedNode}
|
|
52
|
+
|
|
53
|
+
# WHEN we serialize the workflow
|
|
54
|
+
workflow_display = get_workflow_display(workflow_class=WorkflowWithOnlyUnusedGraphs)
|
|
55
|
+
workflow_display.serialize()
|
|
56
|
+
|
|
57
|
+
# THEN the display_context should contain a WorkflowValidationError
|
|
58
|
+
errors = list(workflow_display.display_context.errors)
|
|
59
|
+
validation_errors = [e for e in errors if isinstance(e, WorkflowValidationError)]
|
|
60
|
+
assert len(validation_errors) == 1
|
|
61
|
+
|
|
62
|
+
# AND the error message should indicate the workflow has no triggers and no entrypoint nodes
|
|
63
|
+
error = validation_errors[0]
|
|
64
|
+
assert "no triggers and no entrypoint nodes" in str(error)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tests for partial WorkflowMetaDisplay override serialization."""
|
|
2
|
+
|
|
3
|
+
from vellum.workflows import BaseWorkflow
|
|
4
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
5
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
6
|
+
from vellum.workflows.state.base import BaseState
|
|
7
|
+
from vellum_ee.workflows.display.base import WorkflowDisplayData, WorkflowDisplayDataViewport, WorkflowMetaDisplay
|
|
8
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
9
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_triggerless_workflow_with_partial_display_override_creates_entrypoint():
|
|
13
|
+
"""
|
|
14
|
+
Tests that a triggerless workflow with partial WorkflowMetaDisplay overrides
|
|
15
|
+
(only display_data set) still creates an ENTRYPOINT node with backfilled IDs.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# GIVEN a simple triggerless workflow
|
|
19
|
+
class ProcessNode(BaseNode):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
23
|
+
graph = ProcessNode
|
|
24
|
+
|
|
25
|
+
# AND a display class that only overrides display_data (viewport), not entrypoint IDs
|
|
26
|
+
class TestWorkflowDisplay(BaseWorkflowDisplay[TestWorkflow]):
|
|
27
|
+
workflow_display = WorkflowMetaDisplay(
|
|
28
|
+
display_data=WorkflowDisplayData(viewport=WorkflowDisplayDataViewport(x=100.0, y=200.0, zoom=1.5))
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# WHEN we serialize the workflow
|
|
32
|
+
result = get_workflow_display(workflow_class=TestWorkflow).serialize()
|
|
33
|
+
|
|
34
|
+
# THEN the workflow should have an ENTRYPOINT node
|
|
35
|
+
workflow_raw_data = result["workflow_raw_data"]
|
|
36
|
+
assert isinstance(workflow_raw_data, dict)
|
|
37
|
+
nodes = workflow_raw_data["nodes"]
|
|
38
|
+
assert isinstance(nodes, list)
|
|
39
|
+
entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
|
|
40
|
+
assert len(entrypoint_nodes) == 1, "Triggerless workflow with partial overrides should have an ENTRYPOINT node"
|
|
41
|
+
|
|
42
|
+
# AND the ENTRYPOINT node should have valid IDs (not None)
|
|
43
|
+
entrypoint_node = entrypoint_nodes[0]
|
|
44
|
+
assert isinstance(entrypoint_node, dict)
|
|
45
|
+
assert entrypoint_node["id"] is not None, "ENTRYPOINT node should have a non-None id"
|
|
46
|
+
entrypoint_data = entrypoint_node["data"]
|
|
47
|
+
assert isinstance(entrypoint_data, dict)
|
|
48
|
+
source_handle_id = entrypoint_data["source_handle_id"]
|
|
49
|
+
assert source_handle_id is not None, "ENTRYPOINT node should have a non-None source_handle_id"
|
|
50
|
+
|
|
51
|
+
# AND there should be an edge from ENTRYPOINT to the process node
|
|
52
|
+
edges = workflow_raw_data["edges"]
|
|
53
|
+
assert isinstance(edges, list)
|
|
54
|
+
entrypoint_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == entrypoint_node["id"]]
|
|
55
|
+
assert len(entrypoint_edges) > 0, "Should have edges from ENTRYPOINT node"
|