feat: Persist Variables for Enhanced Debugging Workflow (#20699)
This pull request introduces a feature aimed at improving the debugging experience during workflow editing. With the addition of variable persistence, the system will automatically retain the output variables from previously executed nodes. These persisted variables can then be reused when debugging subsequent nodes, eliminating the need for repetitive manual input. By streamlining this aspect of the workflow, the feature minimizes user errors and significantly reduces debugging effort, offering a smoother and more efficient experience. Key highlights of this change: - Automatic persistence of output variables for executed nodes. - Reuse of persisted variables to simplify input steps for nodes requiring them (e.g., `code`, `template`, `variable_assigner`). - Enhanced debugging experience with reduced friction. Closes #19735.
This commit is contained in:
@@ -1,165 +0,0 @@
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from core.variables import (
|
||||
ArrayNumberVariable,
|
||||
ArrayObjectVariable,
|
||||
ArrayStringVariable,
|
||||
FloatVariable,
|
||||
IntegerVariable,
|
||||
ObjectSegment,
|
||||
SecretVariable,
|
||||
StringVariable,
|
||||
)
|
||||
from core.variables.exc import VariableError
|
||||
from core.variables.segments import ArrayAnySegment
|
||||
from factories import variable_factory
|
||||
|
||||
|
||||
def test_string_variable():
|
||||
test_data = {"value_type": "string", "name": "test_text", "value": "Hello, World!"}
|
||||
result = variable_factory.build_conversation_variable_from_mapping(test_data)
|
||||
assert isinstance(result, StringVariable)
|
||||
|
||||
|
||||
def test_integer_variable():
|
||||
test_data = {"value_type": "number", "name": "test_int", "value": 42}
|
||||
result = variable_factory.build_conversation_variable_from_mapping(test_data)
|
||||
assert isinstance(result, IntegerVariable)
|
||||
|
||||
|
||||
def test_float_variable():
|
||||
test_data = {"value_type": "number", "name": "test_float", "value": 3.14}
|
||||
result = variable_factory.build_conversation_variable_from_mapping(test_data)
|
||||
assert isinstance(result, FloatVariable)
|
||||
|
||||
|
||||
def test_secret_variable():
|
||||
test_data = {"value_type": "secret", "name": "test_secret", "value": "secret_value"}
|
||||
result = variable_factory.build_conversation_variable_from_mapping(test_data)
|
||||
assert isinstance(result, SecretVariable)
|
||||
|
||||
|
||||
def test_invalid_value_type():
|
||||
test_data = {"value_type": "unknown", "name": "test_invalid", "value": "value"}
|
||||
with pytest.raises(VariableError):
|
||||
variable_factory.build_conversation_variable_from_mapping(test_data)
|
||||
|
||||
|
||||
def test_build_a_blank_string():
|
||||
result = variable_factory.build_conversation_variable_from_mapping(
|
||||
{
|
||||
"value_type": "string",
|
||||
"name": "blank",
|
||||
"value": "",
|
||||
}
|
||||
)
|
||||
assert isinstance(result, StringVariable)
|
||||
assert result.value == ""
|
||||
|
||||
|
||||
def test_build_a_object_variable_with_none_value():
|
||||
var = variable_factory.build_segment(
|
||||
{
|
||||
"key1": None,
|
||||
}
|
||||
)
|
||||
assert isinstance(var, ObjectSegment)
|
||||
assert var.value["key1"] is None
|
||||
|
||||
|
||||
def test_object_variable():
|
||||
mapping = {
|
||||
"id": str(uuid4()),
|
||||
"value_type": "object",
|
||||
"name": "test_object",
|
||||
"description": "Description of the variable.",
|
||||
"value": {
|
||||
"key1": "text",
|
||||
"key2": 2,
|
||||
},
|
||||
}
|
||||
variable = variable_factory.build_conversation_variable_from_mapping(mapping)
|
||||
assert isinstance(variable, ObjectSegment)
|
||||
assert isinstance(variable.value["key1"], str)
|
||||
assert isinstance(variable.value["key2"], int)
|
||||
|
||||
|
||||
def test_array_string_variable():
|
||||
mapping = {
|
||||
"id": str(uuid4()),
|
||||
"value_type": "array[string]",
|
||||
"name": "test_array",
|
||||
"description": "Description of the variable.",
|
||||
"value": [
|
||||
"text",
|
||||
"text",
|
||||
],
|
||||
}
|
||||
variable = variable_factory.build_conversation_variable_from_mapping(mapping)
|
||||
assert isinstance(variable, ArrayStringVariable)
|
||||
assert isinstance(variable.value[0], str)
|
||||
assert isinstance(variable.value[1], str)
|
||||
|
||||
|
||||
def test_array_number_variable():
|
||||
mapping = {
|
||||
"id": str(uuid4()),
|
||||
"value_type": "array[number]",
|
||||
"name": "test_array",
|
||||
"description": "Description of the variable.",
|
||||
"value": [
|
||||
1,
|
||||
2.0,
|
||||
],
|
||||
}
|
||||
variable = variable_factory.build_conversation_variable_from_mapping(mapping)
|
||||
assert isinstance(variable, ArrayNumberVariable)
|
||||
assert isinstance(variable.value[0], int)
|
||||
assert isinstance(variable.value[1], float)
|
||||
|
||||
|
||||
def test_array_object_variable():
|
||||
mapping = {
|
||||
"id": str(uuid4()),
|
||||
"value_type": "array[object]",
|
||||
"name": "test_array",
|
||||
"description": "Description of the variable.",
|
||||
"value": [
|
||||
{
|
||||
"key1": "text",
|
||||
"key2": 1,
|
||||
},
|
||||
{
|
||||
"key1": "text",
|
||||
"key2": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
variable = variable_factory.build_conversation_variable_from_mapping(mapping)
|
||||
assert isinstance(variable, ArrayObjectVariable)
|
||||
assert isinstance(variable.value[0], dict)
|
||||
assert isinstance(variable.value[1], dict)
|
||||
assert isinstance(variable.value[0]["key1"], str)
|
||||
assert isinstance(variable.value[0]["key2"], int)
|
||||
assert isinstance(variable.value[1]["key1"], str)
|
||||
assert isinstance(variable.value[1]["key2"], int)
|
||||
|
||||
|
||||
def test_variable_cannot_large_than_200_kb():
|
||||
with pytest.raises(VariableError):
|
||||
variable_factory.build_conversation_variable_from_mapping(
|
||||
{
|
||||
"id": str(uuid4()),
|
||||
"value_type": "string",
|
||||
"name": "test_text",
|
||||
"value": "a" * 1024 * 201,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_array_none_variable():
|
||||
var = variable_factory.build_segment([None, None, None, None])
|
||||
assert isinstance(var, ArrayAnySegment)
|
||||
assert var.value == [None, None, None, None]
|
||||
25
api/tests/unit_tests/core/file/test_models.py
Normal file
25
api/tests/unit_tests/core/file/test_models.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from core.file import File, FileTransferMethod, FileType
|
||||
|
||||
|
||||
def test_file():
|
||||
file = File(
|
||||
id="test-file",
|
||||
tenant_id="test-tenant-id",
|
||||
type=FileType.IMAGE,
|
||||
transfer_method=FileTransferMethod.TOOL_FILE,
|
||||
related_id="test-related-id",
|
||||
filename="image.png",
|
||||
extension=".png",
|
||||
mime_type="image/png",
|
||||
size=67,
|
||||
storage_key="test-storage-key",
|
||||
url="https://example.com/image.png",
|
||||
)
|
||||
assert file.tenant_id == "test-tenant-id"
|
||||
assert file.type == FileType.IMAGE
|
||||
assert file.transfer_method == FileTransferMethod.TOOL_FILE
|
||||
assert file.related_id == "test-related-id"
|
||||
assert file.filename == "image.png"
|
||||
assert file.extension == ".png"
|
||||
assert file.mime_type == "image/png"
|
||||
assert file.size == 67
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.variables.segments import ArrayAnySegment, ArrayStringSegment
|
||||
from core.workflow.entities.node_entities import NodeRunResult
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
|
||||
@@ -197,7 +198,7 @@ def test_run():
|
||||
count += 1
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": ["dify 123", "dify 123"]}
|
||||
assert item.run_result.outputs == {"output": ArrayStringSegment(value=["dify 123", "dify 123"])}
|
||||
|
||||
assert count == 20
|
||||
|
||||
@@ -413,7 +414,7 @@ def test_run_parallel():
|
||||
count += 1
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": ["dify 123", "dify 123"]}
|
||||
assert item.run_result.outputs == {"output": ArrayStringSegment(value=["dify 123", "dify 123"])}
|
||||
|
||||
assert count == 32
|
||||
|
||||
@@ -654,7 +655,7 @@ def test_iteration_run_in_parallel_mode():
|
||||
parallel_arr.append(item)
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": ["dify 123", "dify 123"]}
|
||||
assert item.run_result.outputs == {"output": ArrayStringSegment(value=["dify 123", "dify 123"])}
|
||||
assert count == 32
|
||||
|
||||
for item in sequential_result:
|
||||
@@ -662,7 +663,7 @@ def test_iteration_run_in_parallel_mode():
|
||||
count += 1
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": ["dify 123", "dify 123"]}
|
||||
assert item.run_result.outputs == {"output": ArrayStringSegment(value=["dify 123", "dify 123"])}
|
||||
assert count == 64
|
||||
|
||||
|
||||
@@ -846,7 +847,7 @@ def test_iteration_run_error_handle():
|
||||
count += 1
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": [None, None]}
|
||||
assert item.run_result.outputs == {"output": ArrayAnySegment(value=[None, None])}
|
||||
|
||||
assert count == 14
|
||||
# execute remove abnormal output
|
||||
@@ -857,5 +858,5 @@ def test_iteration_run_error_handle():
|
||||
count += 1
|
||||
if isinstance(item, RunCompletedEvent):
|
||||
assert item.run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
assert item.run_result.outputs == {"output": []}
|
||||
assert item.run_result.outputs == {"output": ArrayAnySegment(value=[])}
|
||||
assert count == 14
|
||||
|
||||
@@ -7,6 +7,7 @@ from docx.oxml.text.paragraph import CT_P
|
||||
|
||||
from core.file import File, FileTransferMethod
|
||||
from core.variables import ArrayFileSegment
|
||||
from core.variables.segments import ArrayStringSegment
|
||||
from core.variables.variables import StringVariable
|
||||
from core.workflow.entities.node_entities import NodeRunResult
|
||||
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
|
||||
@@ -69,7 +70,13 @@ def test_run_invalid_variable_type(document_extractor_node, mock_graph_runtime_s
|
||||
@pytest.mark.parametrize(
|
||||
("mime_type", "file_content", "expected_text", "transfer_method", "extension"),
|
||||
[
|
||||
("text/plain", b"Hello, world!", ["Hello, world!"], FileTransferMethod.LOCAL_FILE, ".txt"),
|
||||
(
|
||||
"text/plain",
|
||||
b"Hello, world!",
|
||||
["Hello, world!"],
|
||||
FileTransferMethod.LOCAL_FILE,
|
||||
".txt",
|
||||
),
|
||||
(
|
||||
"application/pdf",
|
||||
b"%PDF-1.5\n%Test PDF content",
|
||||
@@ -84,7 +91,13 @@ def test_run_invalid_variable_type(document_extractor_node, mock_graph_runtime_s
|
||||
FileTransferMethod.REMOTE_URL,
|
||||
"",
|
||||
),
|
||||
("text/plain", b"Remote content", ["Remote content"], FileTransferMethod.REMOTE_URL, None),
|
||||
(
|
||||
"text/plain",
|
||||
b"Remote content",
|
||||
["Remote content"],
|
||||
FileTransferMethod.REMOTE_URL,
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_run_extract_text(
|
||||
@@ -131,7 +144,7 @@ def test_run_extract_text(
|
||||
assert isinstance(result, NodeRunResult)
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED, result.error
|
||||
assert result.outputs is not None
|
||||
assert result.outputs["text"] == expected_text
|
||||
assert result.outputs["text"] == ArrayStringSegment(value=expected_text)
|
||||
|
||||
if transfer_method == FileTransferMethod.REMOTE_URL:
|
||||
mock_ssrf_proxy_get.assert_called_once_with("https://example.com/file.txt")
|
||||
|
||||
@@ -115,7 +115,7 @@ def test_filter_files_by_type(list_operator_node):
|
||||
},
|
||||
]
|
||||
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||
for expected_file, result_file in zip(expected_files, result.outputs["result"]):
|
||||
for expected_file, result_file in zip(expected_files, result.outputs["result"].value):
|
||||
assert expected_file["filename"] == result_file.filename
|
||||
assert expected_file["type"] == result_file.type
|
||||
assert expected_file["tenant_id"] == result_file.tenant_id
|
||||
|
||||
@@ -5,6 +5,7 @@ from uuid import uuid4
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.variables import ArrayStringVariable, StringVariable
|
||||
from core.workflow.conversation_variable_updater import ConversationVariableUpdater
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.enums import SystemVariableKey
|
||||
from core.workflow.graph_engine.entities.graph import Graph
|
||||
@@ -63,10 +64,11 @@ def test_overwrite_string_variable():
|
||||
name="test_string_variable",
|
||||
value="the second value",
|
||||
)
|
||||
conversation_id = str(uuid.uuid4())
|
||||
|
||||
# construct variable pool
|
||||
variable_pool = VariablePool(
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"},
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: conversation_id},
|
||||
user_inputs={},
|
||||
environment_variables=[],
|
||||
conversation_variables=[conversation_variable],
|
||||
@@ -77,6 +79,9 @@ def test_overwrite_string_variable():
|
||||
input_variable,
|
||||
)
|
||||
|
||||
mock_conv_var_updater = mock.Mock(spec=ConversationVariableUpdater)
|
||||
mock_conv_var_updater_factory = mock.Mock(return_value=mock_conv_var_updater)
|
||||
|
||||
node = VariableAssignerNode(
|
||||
id=str(uuid.uuid4()),
|
||||
graph_init_params=init_params,
|
||||
@@ -91,11 +96,20 @@ def test_overwrite_string_variable():
|
||||
"input_variable_selector": [DEFAULT_NODE_ID, input_variable.name],
|
||||
},
|
||||
},
|
||||
conv_var_updater_factory=mock_conv_var_updater_factory,
|
||||
)
|
||||
|
||||
with mock.patch("core.workflow.nodes.variable_assigner.common.helpers.update_conversation_variable") as mock_run:
|
||||
list(node.run())
|
||||
mock_run.assert_called_once()
|
||||
list(node.run())
|
||||
expected_var = StringVariable(
|
||||
id=conversation_variable.id,
|
||||
name=conversation_variable.name,
|
||||
description=conversation_variable.description,
|
||||
selector=conversation_variable.selector,
|
||||
value_type=conversation_variable.value_type,
|
||||
value=input_variable.value,
|
||||
)
|
||||
mock_conv_var_updater.update.assert_called_once_with(conversation_id=conversation_id, variable=expected_var)
|
||||
mock_conv_var_updater.flush.assert_called_once()
|
||||
|
||||
got = variable_pool.get(["conversation", conversation_variable.name])
|
||||
assert got is not None
|
||||
@@ -148,9 +162,10 @@ def test_append_variable_to_array():
|
||||
name="test_string_variable",
|
||||
value="the second value",
|
||||
)
|
||||
conversation_id = str(uuid.uuid4())
|
||||
|
||||
variable_pool = VariablePool(
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"},
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: conversation_id},
|
||||
user_inputs={},
|
||||
environment_variables=[],
|
||||
conversation_variables=[conversation_variable],
|
||||
@@ -160,6 +175,9 @@ def test_append_variable_to_array():
|
||||
input_variable,
|
||||
)
|
||||
|
||||
mock_conv_var_updater = mock.Mock(spec=ConversationVariableUpdater)
|
||||
mock_conv_var_updater_factory = mock.Mock(return_value=mock_conv_var_updater)
|
||||
|
||||
node = VariableAssignerNode(
|
||||
id=str(uuid.uuid4()),
|
||||
graph_init_params=init_params,
|
||||
@@ -174,11 +192,22 @@ def test_append_variable_to_array():
|
||||
"input_variable_selector": [DEFAULT_NODE_ID, input_variable.name],
|
||||
},
|
||||
},
|
||||
conv_var_updater_factory=mock_conv_var_updater_factory,
|
||||
)
|
||||
|
||||
with mock.patch("core.workflow.nodes.variable_assigner.common.helpers.update_conversation_variable") as mock_run:
|
||||
list(node.run())
|
||||
mock_run.assert_called_once()
|
||||
list(node.run())
|
||||
expected_value = list(conversation_variable.value)
|
||||
expected_value.append(input_variable.value)
|
||||
expected_var = ArrayStringVariable(
|
||||
id=conversation_variable.id,
|
||||
name=conversation_variable.name,
|
||||
description=conversation_variable.description,
|
||||
selector=conversation_variable.selector,
|
||||
value_type=conversation_variable.value_type,
|
||||
value=expected_value,
|
||||
)
|
||||
mock_conv_var_updater.update.assert_called_once_with(conversation_id=conversation_id, variable=expected_var)
|
||||
mock_conv_var_updater.flush.assert_called_once()
|
||||
|
||||
got = variable_pool.get(["conversation", conversation_variable.name])
|
||||
assert got is not None
|
||||
@@ -225,13 +254,17 @@ def test_clear_array():
|
||||
value=["the first value"],
|
||||
)
|
||||
|
||||
conversation_id = str(uuid.uuid4())
|
||||
variable_pool = VariablePool(
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: "conversation_id"},
|
||||
system_variables={SystemVariableKey.CONVERSATION_ID: conversation_id},
|
||||
user_inputs={},
|
||||
environment_variables=[],
|
||||
conversation_variables=[conversation_variable],
|
||||
)
|
||||
|
||||
mock_conv_var_updater = mock.Mock(spec=ConversationVariableUpdater)
|
||||
mock_conv_var_updater_factory = mock.Mock(return_value=mock_conv_var_updater)
|
||||
|
||||
node = VariableAssignerNode(
|
||||
id=str(uuid.uuid4()),
|
||||
graph_init_params=init_params,
|
||||
@@ -246,11 +279,20 @@ def test_clear_array():
|
||||
"input_variable_selector": [],
|
||||
},
|
||||
},
|
||||
conv_var_updater_factory=mock_conv_var_updater_factory,
|
||||
)
|
||||
|
||||
with mock.patch("core.workflow.nodes.variable_assigner.common.helpers.update_conversation_variable") as mock_run:
|
||||
list(node.run())
|
||||
mock_run.assert_called_once()
|
||||
list(node.run())
|
||||
expected_var = ArrayStringVariable(
|
||||
id=conversation_variable.id,
|
||||
name=conversation_variable.name,
|
||||
description=conversation_variable.description,
|
||||
selector=conversation_variable.selector,
|
||||
value_type=conversation_variable.value_type,
|
||||
value=[],
|
||||
)
|
||||
mock_conv_var_updater.update.assert_called_once_with(conversation_id=conversation_id, variable=expected_var)
|
||||
mock_conv_var_updater.flush.assert_called_once()
|
||||
|
||||
got = variable_pool.get(["conversation", conversation_variable.name])
|
||||
assert got is not None
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from core.file import File, FileTransferMethod, FileType
|
||||
from core.variables import FileSegment, StringSegment
|
||||
from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID, ENVIRONMENT_VARIABLE_NODE_ID
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.enums import SystemVariableKey
|
||||
from factories.variable_factory import build_segment, segment_to_variable
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -44,3 +48,38 @@ def test_use_long_selector(pool):
|
||||
result = pool.get(("node_1", "part_1", "part_2"))
|
||||
assert result is not None
|
||||
assert result.value == "test_value"
|
||||
|
||||
|
||||
class TestVariablePool:
|
||||
def test_constructor(self):
|
||||
pool = VariablePool()
|
||||
pool = VariablePool(
|
||||
variable_dictionary={},
|
||||
user_inputs={},
|
||||
system_variables={},
|
||||
environment_variables=[],
|
||||
conversation_variables=[],
|
||||
)
|
||||
|
||||
pool = VariablePool(
|
||||
user_inputs={"key": "value"},
|
||||
system_variables={SystemVariableKey.WORKFLOW_ID: "test_workflow_id"},
|
||||
environment_variables=[
|
||||
segment_to_variable(
|
||||
segment=build_segment(1),
|
||||
selector=[ENVIRONMENT_VARIABLE_NODE_ID, "env_var_1"],
|
||||
name="env_var_1",
|
||||
)
|
||||
],
|
||||
conversation_variables=[
|
||||
segment_to_variable(
|
||||
segment=build_segment("1"),
|
||||
selector=[CONVERSATION_VARIABLE_NODE_ID, "conv_var_1"],
|
||||
name="conv_var_1",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def test_constructor_with_invalid_system_variable_key(self):
|
||||
with pytest.raises(ValidationError):
|
||||
VariablePool(system_variables={"invalid_key": "value"}) # type: ignore
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
from core.variables import SecretVariable
|
||||
import dataclasses
|
||||
|
||||
from core.workflow.entities.variable_entities import VariableSelector
|
||||
from core.workflow.entities.variable_pool import VariablePool
|
||||
from core.workflow.enums import SystemVariableKey
|
||||
from core.workflow.utils import variable_template_parser
|
||||
|
||||
|
||||
def test_extract_selectors_from_template():
|
||||
variable_pool = VariablePool(
|
||||
system_variables={
|
||||
SystemVariableKey("user_id"): "fake-user-id",
|
||||
},
|
||||
user_inputs={},
|
||||
environment_variables=[
|
||||
SecretVariable(name="secret_key", value="fake-secret-key"),
|
||||
],
|
||||
conversation_variables=[],
|
||||
)
|
||||
variable_pool.add(("node_id", "custom_query"), "fake-user-query")
|
||||
template = (
|
||||
"Hello, {{#sys.user_id#}}! Your query is {{#node_id.custom_query#}}. And your key is {{#env.secret_key#}}."
|
||||
)
|
||||
@@ -26,3 +14,35 @@ def test_extract_selectors_from_template():
|
||||
VariableSelector(variable="#node_id.custom_query#", value_selector=["node_id", "custom_query"]),
|
||||
VariableSelector(variable="#env.secret_key#", value_selector=["env", "secret_key"]),
|
||||
]
|
||||
|
||||
|
||||
def test_invalid_references():
|
||||
@dataclasses.dataclass
|
||||
class TestCase:
|
||||
name: str
|
||||
template: str
|
||||
|
||||
cases = [
|
||||
TestCase(
|
||||
name="lack of closing brace",
|
||||
template="Hello, {{#sys.user_id#",
|
||||
),
|
||||
TestCase(
|
||||
name="lack of opening brace",
|
||||
template="Hello, #sys.user_id#}}",
|
||||
),
|
||||
TestCase(
|
||||
name="lack selector name",
|
||||
template="Hello, {{#sys#}}",
|
||||
),
|
||||
TestCase(
|
||||
name="empty node name part",
|
||||
template="Hello, {{#.user_id#}}",
|
||||
),
|
||||
]
|
||||
for idx, c in enumerate(cases, 1):
|
||||
fail_msg = f"Test case {c.name} failed, index={idx}"
|
||||
selectors = variable_template_parser.extract_selectors_from_template(c.template)
|
||||
assert selectors == [], fail_msg
|
||||
parser = variable_template_parser.VariableTemplateParser(c.template)
|
||||
assert parser.extract_variable_selectors() == [], fail_msg
|
||||
|
||||
Reference in New Issue
Block a user