refactor(api): rename placeholder to default_value in various parts

Previously the fields / classes are named with `placeholder`. However,
the actual purpose is to use as default values. This commit addresses
this problem by correcting names for relevant fields / classes.

- FormInputPlaceholder
- FormInput.placeholder
- HumanInputRequiredResponse.resolved_placeholder_values
- HumanInputFormDefinition.resolved_placeholder_values
- FormCreateParams.resolved_placeholder_values
- HumanInputRequired.resolved_placeholder_values
- The `resolved_placeholder_values` argument of _create_human_input_delivery_test_form
- The `resolved_placeholder_values` inside _jsonify_form_definition
This commit is contained in:
QuantumGhost
2026-01-23 15:04:46 +08:00
parent 9e56d65612
commit 33a830cbc9
27 changed files with 103 additions and 139 deletions

View File

@ -21,7 +21,7 @@ from services.human_input_service import Form, FormNotFoundError, HumanInputServ
logger = logging.getLogger(__name__)
def _stringify_placeholder_values(values: dict[str, object]) -> dict[str, str]:
def _stringify_default_values(values: dict[str, object]) -> dict[str, str]:
result: dict[str, str] = {}
for key, value in values.items():
if value is None:
@ -43,7 +43,7 @@ def _jsonify_form_definition(form: Form, site_payload: dict | None = None) -> Re
payload = {
"form_content": definition_payload["rendered_content"],
"inputs": definition_payload["inputs"],
"resolved_placeholder_values": _stringify_placeholder_values(definition_payload["placeholder_values"]),
"resolved_default_values": _stringify_default_values(definition_payload["default_values"]),
"user_actions": definition_payload["user_actions"],
"expiration_time": _to_timestamp(form.expiration_time),
}

View File

@ -302,7 +302,7 @@ class WorkflowResponseConverter:
actions=reason.actions,
display_in_ui=reason.display_in_ui,
form_token=reason.form_token,
resolved_placeholder_values=reason.resolved_placeholder_values,
resolved_default_values=reason.resolved_default_values,
),
)
)

View File

@ -281,7 +281,7 @@ class HumanInputRequiredResponse(StreamResponse):
actions: Sequence[UserAction] = Field(default_factory=list)
display_in_ui: bool = False
form_token: str | None = None
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
event: StreamEvent = StreamEvent.HUMAN_INPUT_REQUIRED
workflow_run_id: str

View File

@ -20,7 +20,7 @@ class HumanInputFormDefinition(BaseModel):
actions: Sequence[UserAction] = Field(default_factory=list)
display_in_ui: bool = False
form_token: str | None = None
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
class HumanInputFormSubmissionData(BaseModel):

View File

@ -337,7 +337,7 @@ class HumanInputFormRepositoryImpl:
rendered_content=params.rendered_content,
timeout=form_config.timeout,
timeout_unit=form_config.timeout_unit,
placeholder_values=dict(params.resolved_placeholder_values),
default_values=dict(params.resolved_default_values),
display_in_ui=params.display_in_ui,
node_title=form_config.title,
)

View File

@ -1,6 +1,5 @@
from libs.exception import BaseHTTPException
from core.tools.entities.tool_entities import ToolInvokeMeta
from libs.exception import BaseHTTPException
class ToolProviderNotFoundError(ValueError):

View File

@ -22,16 +22,16 @@ class HumanInputRequired(BaseModel):
node_id: str
node_title: str
# The `resolved_placeholder_values` stores the resolved values of variable placeholders. It's a mapping from
# The `resolved_default_values` stores the resolved values of variable defaults. It's a mapping from
# `output_variable_name` to their resolved values.
#
# For example, The form contains a input with output variable name `name` and placeholder type `VARIABLE`, its
# selector is ["start", "name"]. While the HumanInputNode is executed, the correspond value of variable
# `start.name` in variable pool is `John`. Thus, the resolved value of the output variable `name` is `John`. The
# `resolved_placeholder_values` is `{"name": "John"}`.
# `resolved_default_values` is `{"name": "John"}`.
#
# Only form inputs with placeholder type `VARIABLE` will be resolved and stored in `resolved_placeholder_values`.
resolved_placeholder_values: Mapping[str, Any] = Field(default_factory=dict)
# Only form inputs with default value type `VARIABLE` will be resolved and stored in `resolved_default_values`.
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
# The `form_token` is the token used to submit the form via UI surfaces. It corresponds to
# `HumanInputFormRecipient.access_token`.

View File

@ -153,20 +153,20 @@ def apply_debug_email_recipient(
return method.model_copy(update={"config": debug_config})
class FormInputPlaceholder(BaseModel):
"""Placeholder configuration for form inputs."""
class FormInputDefault(BaseModel):
"""Default configuration for form inputs."""
# NOTE: Ideally, a discriminated union would be used to model
# FormInputPlaceholder. However, the UI requires preserving the previous
# FormInputDefault. However, the UI requires preserving the previous
# value when switching between `VARIABLE` and `CONSTANT` types. This
# necessitates retaining all fields, making a discriminated union unsuitable.
type: PlaceholderType
# The selector of placeholder variable, used when `type` is `VARIABLE`
# The selector of default variable, used when `type` is `VARIABLE`.
selector: Sequence[str] = Field(default_factory=tuple) #
# The value of the placeholder, used when `type` is `CONSTANT`.
# The value of the default, used when `type` is `CONSTANT`.
# TODO: How should we express JSON values?
value: str = ""
@ -184,7 +184,7 @@ class FormInput(BaseModel):
type: FormInputType
output_variable_name: str
placeholder: Optional[FormInputPlaceholder] = None
default: Optional[FormInputDefault] = None
_IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
@ -286,14 +286,14 @@ class HumanInputNodeData(BaseNodeData):
_add_variable_selectors(delivery_method.extract_variable_selectors())
for input in self.inputs:
placeholder = input.placeholder
if placeholder is None:
default_value = input.default
if default_value is None:
continue
if placeholder.type == PlaceholderType.CONSTANT:
if default_value.type == PlaceholderType.CONSTANT:
continue
placeholder_key = ".".join(placeholder.selector)
qualified_variable_mapping_key = f"{node_id}.#{placeholder_key}#"
variable_mappings[qualified_variable_mapping_key] = placeholder.selector
default_value_key = ".".join(default_value.selector)
qualified_variable_mapping_key = f"{node_id}.#{default_value_key}#"
variable_mappings[qualified_variable_mapping_key] = default_value.selector
return variable_mappings
@ -316,8 +316,8 @@ class FormDefinition(BaseModel):
timeout: int
timeout_unit: TimeoutUnit
# this is used to store the values of the placeholders
placeholder_values: dict[str, Any] = Field(default_factory=dict)
# this is used to store the resolved default values
default_values: dict[str, Any] = Field(default_factory=dict)
# node_title records the title of the HumanInput node.
node_title: str | None = None

View File

@ -51,7 +51,7 @@ class FormInputType(enum.StrEnum):
class PlaceholderType(enum.StrEnum):
"""Placeholder types for form inputs."""
"""Default value types for form inputs."""
VARIABLE = enum.auto()
CONSTANT = enum.auto()

View File

@ -136,23 +136,23 @@ class HumanInputNode(Node[HumanInputNodeData]):
pause_requested_event = PauseRequestedEvent(reason=required_event)
return pause_requested_event
def _resolve_inputs(self) -> Mapping[str, Any]:
def _resolve_default_values(self) -> Mapping[str, Any]:
variable_pool = self.graph_runtime_state.variable_pool
resolved_inputs = {}
resolved_defaults: dict[str, Any] = {}
for input in self._node_data.inputs:
if (placeholder := input.placeholder) is None:
if (default_value := input.default) is None:
continue
if placeholder.type == PlaceholderType.CONSTANT:
if default_value.type == PlaceholderType.CONSTANT:
continue
placeholder_value = variable_pool.get(placeholder.selector)
if placeholder_value is None:
resolved_value = variable_pool.get(default_value.selector)
if resolved_value is None:
# TODO: How should we handle this?
continue
resolved_inputs[input.output_variable_name] = (
WorkflowRuntimeTypeConverter().value_to_json_encodable_recursive(placeholder_value.value)
resolved_defaults[input.output_variable_name] = (
WorkflowRuntimeTypeConverter().value_to_json_encodable_recursive(resolved_value.value)
)
return resolved_inputs
return resolved_defaults
def _should_require_console_recipient(self) -> bool:
if self.invoke_from == InvokeFrom.DEBUGGER:
@ -181,7 +181,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
def _human_input_required_event(self, form_entity: HumanInputFormEntity) -> HumanInputRequired:
node_data = self._node_data
resolved_placeholder_values = self._resolve_inputs()
resolved_default_values = self._resolve_default_values()
display_in_ui = self._display_in_ui()
form_token = form_entity.web_app_token
if display_in_ui and form_token is None:
@ -195,7 +195,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
node_id=self.id,
node_title=node_data.title,
form_token=form_token,
resolved_placeholder_values=resolved_placeholder_values,
resolved_default_values=resolved_default_values,
)
def _run(self) -> Generator[NodeEventBase, None, None]:
@ -222,7 +222,7 @@ class HumanInputNode(Node[HumanInputNodeData]):
rendered_content=self._render_form_content_before_submission(),
delivery_methods=self._effective_delivery_methods(),
display_in_ui=display_in_ui,
resolved_placeholder_values=self._resolve_inputs(),
resolved_default_values=self._resolve_default_values(),
console_recipient_required=self._should_require_console_recipient(),
console_creator_account_id=(
self.user_id if self.invoke_from in {InvokeFrom.DEBUGGER, InvokeFrom.EXPLORE} else None
@ -330,11 +330,11 @@ class HumanInputNode(Node[HumanInputNodeData]):
node_data: Mapping[str, Any],
) -> Mapping[str, Sequence[str]]:
"""
Extract variable selectors referenced in form content and input placeholders.
Extract variable selectors referenced in form content and input default values.
This method should parse:
1. Variables referenced in form_content ({{#node_name.var_name#}})
2. Variables referenced in input placeholders
2. Variables referenced in input default values
"""
validated_node_data = HumanInputNodeData.model_validate(node_data)
return validated_node_data.extract_variable_selector_to_variable_mapping(node_id)

View File

@ -36,11 +36,11 @@ class FormCreateParams:
# UI display flag computed by runtime context.
display_in_ui: bool
# resolved_placeholder_values saves the values for placeholders with
# resolved_default_values saves the values for defaults with
# type = VARIABLE.
#
# For type = CONSTANT, the value is not stored inside `resolved_placeholder_values`
resolved_placeholder_values: Mapping[str, Any]
# For type = CONSTANT, the value is not stored inside `resolved_default_values`
resolved_default_values: Mapping[str, Any]
form_kind: HumanInputFormKind = HumanInputFormKind.RUNTIME
# Force creating a console-only recipient for submission in Console.

View File

@ -80,7 +80,7 @@ def _build_human_input_required_reason(
inputs = []
actions = []
display_in_ui = False
resolved_placeholder_values: dict[str, Any] = {}
resolved_default_values: dict[str, Any] = {}
node_title = "Human Input"
form_id = reason_model.form_id
node_id = reason_model.node_id
@ -98,7 +98,7 @@ def _build_human_input_required_reason(
inputs = list(definition.inputs)
actions = list(definition.user_actions)
display_in_ui = bool(definition.display_in_ui)
resolved_placeholder_values = dict(definition.placeholder_values)
resolved_default_values = dict(definition.default_values)
node_title = definition.node_title or node_title
form_token = (
@ -116,7 +116,7 @@ def _build_human_input_required_reason(
node_id=node_id,
node_title=node_title,
form_token=form_token,
resolved_placeholder_values=resolved_placeholder_values,
resolved_default_values=resolved_default_values,
)

View File

@ -134,7 +134,7 @@ class SQLAlchemyExecutionExtraContentRepository(ExecutionExtraContentRepository)
actions=form_definition.user_actions,
display_in_ui=display_in_ui,
form_token=form_token,
resolved_placeholder_values=form_definition.placeholder_values,
resolved_default_values=form_definition.default_values,
),
)

View File

@ -807,7 +807,7 @@ class WorkflowService:
)
rendered_content = node._render_form_content_before_submission()
resolved_placeholder_values = node._resolve_inputs()
resolved_default_values = node._resolve_default_values()
node_data = node.node_data
human_input_required = HumanInputRequired(
form_id=node_id,
@ -816,7 +816,7 @@ class WorkflowService:
actions=node_data.user_actions,
node_id=node_id,
node_title=node.title,
resolved_placeholder_values=resolved_placeholder_values,
resolved_default_values=resolved_default_values,
form_token=None,
)
return human_input_required.model_dump(mode="json")
@ -944,14 +944,14 @@ class WorkflowService:
variable_pool=variable_pool,
)
rendered_content = node._render_form_content_before_submission()
resolved_placeholder_values = node._resolve_inputs()
resolved_default_values = node._resolve_default_values()
form_id, recipients = self._create_human_input_delivery_test_form(
app_model=app_model,
node_id=node_id,
node_data=node_data,
delivery_method=delivery_method,
rendered_content=rendered_content,
resolved_placeholder_values=resolved_placeholder_values,
resolved_default_values=resolved_default_values,
)
test_service = HumanInputDeliveryTestService()
context = DeliveryTestContext(
@ -990,7 +990,7 @@ class WorkflowService:
node_data: HumanInputNodeData,
delivery_method: DeliveryChannelConfig,
rendered_content: str,
resolved_placeholder_values: Mapping[str, Any],
resolved_default_values: Mapping[str, Any],
) -> tuple[str, list[DeliveryTestEmailRecipient]]:
repo = HumanInputFormRepositoryImpl(session_factory=db.engine, tenant_id=app_model.tenant_id)
params = FormCreateParams(
@ -1001,7 +1001,7 @@ class WorkflowService:
rendered_content=rendered_content,
delivery_methods=[delivery_method],
display_in_ui=False,
resolved_placeholder_values=resolved_placeholder_values,
resolved_default_values=resolved_default_values,
form_kind=HumanInputFormKind.DELIVERY_TEST,
)
form_entity = repo.create_form(params)

View File

@ -74,7 +74,7 @@ def _build_form_params(delivery_methods: list[EmailDeliveryMethod]) -> FormCreat
rendered_content="<p>Approve?</p>",
delivery_methods=delivery_methods,
display_in_ui=False,
resolved_placeholder_values={},
resolved_default_values={},
)
@ -164,7 +164,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
assert len(external_payloads) == 1
assert external_payloads[0].email == "external@example.com"
def test_create_form_persists_placeholder_values(self, db_session_with_containers: Session) -> None:
def test_create_form_persists_default_values(self, db_session_with_containers: Session) -> None:
engine = db_session_with_containers.get_bind()
assert isinstance(engine, Engine)
tenant, _ = _create_tenant_with_members(
@ -187,7 +187,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
rendered_content="<p>Approve?</p>",
delivery_methods=[],
display_in_ui=False,
resolved_placeholder_values=resolved_values,
resolved_default_values=resolved_values,
)
form_entity = repository.create_form(params)
@ -199,7 +199,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
assert form_model is not None
definition = FormDefinition.model_validate_json(form_model.form_definition)
assert definition.placeholder_values == resolved_values
assert definition.default_values == resolved_values
def test_create_form_persists_display_in_ui(self, db_session_with_containers: Session) -> None:
engine = db_session_with_containers.get_bind()
@ -224,7 +224,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
rendered_content="<p>Approve?</p>",
delivery_methods=[WebAppDeliveryMethod()],
display_in_ui=True,
resolved_placeholder_values={},
resolved_default_values={},
)
form_entity = repository.create_form(params)

View File

@ -95,7 +95,7 @@ def _build_form(db_session_with_containers, tenant, account):
rendered_content="Rendered",
delivery_methods=node_data.delivery_methods,
display_in_ui=False,
resolved_placeholder_values={},
resolved_default_values={},
)
return repo.create_form(params)

View File

@ -66,8 +66,8 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
return {
"form_content": "Raw content",
"rendered_content": "Rendered {{#$output.name#}}",
"inputs": [{"type": "text", "output_variable_name": "name", "placeholder": None}],
"placeholder_values": {"name": "Alice", "age": 30, "meta": {"k": "v"}},
"inputs": [{"type": "text", "output_variable_name": "name", "default": None}],
"default_values": {"name": "Alice", "age": 30, "meta": {"k": "v"}},
"user_actions": [{"id": "approve", "title": "Approve", "button_style": "default"}],
}
@ -132,13 +132,13 @@ def test_get_form_includes_site(monkeypatch: pytest.MonkeyPatch, app: Flask):
"site",
"form_content",
"inputs",
"resolved_placeholder_values",
"resolved_default_values",
"user_actions",
"expiration_time",
}
assert body["form_content"] == "Rendered {{#$output.name#}}"
assert body["inputs"] == [{"type": "text", "output_variable_name": "name", "placeholder": None}]
assert body["resolved_placeholder_values"] == {"name": "Alice", "age": "30", "meta": '{"k": "v"}'}
assert body["inputs"] == [{"type": "text", "output_variable_name": "name", "default": None}]
assert body["resolved_default_values"] == {"name": "Alice", "age": "30", "meta": '{"k": "v"}'}
assert body["user_actions"] == [{"id": "approve", "title": "Approve", "button_style": "default"}]
assert body["expiration_time"] == int(expiration_time.timestamp())
assert body["site"] == {
@ -184,7 +184,7 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
"form_content": "Raw content",
"rendered_content": "Rendered",
"inputs": [],
"placeholder_values": {},
"default_values": {},
"user_actions": [],
}
@ -245,13 +245,13 @@ def test_get_form_allows_backstage_token(monkeypatch: pytest.MonkeyPatch, app: F
"site",
"form_content",
"inputs",
"resolved_placeholder_values",
"resolved_default_values",
"user_actions",
"expiration_time",
}
assert body["form_content"] == "Rendered"
assert body["inputs"] == []
assert body["resolved_placeholder_values"] == {}
assert body["resolved_default_values"] == {}
assert body["user_actions"] == []
assert body["expiration_time"] == int(expiration_time.timestamp())
assert body["site"] == {
@ -297,7 +297,7 @@ def test_get_form_raises_forbidden_when_site_missing(monkeypatch: pytest.MonkeyP
"form_content": "Raw content",
"rendered_content": "Rendered",
"inputs": [],
"placeholder_values": {},
"default_values": {},
"user_actions": [],
}

View File

@ -125,7 +125,7 @@ def test_handle_workflow_paused_event_persists_human_input_extra_content() -> No
node_id="node-1",
node_title="Approval",
form_token="token-1",
resolved_placeholder_values={},
resolved_default_values={},
)
event = QueueWorkflowPausedEvent(reasons=[reason], outputs={}, paused_nodes=["node-1"])

View File

@ -124,7 +124,7 @@ def test_queue_workflow_paused_event_to_stream_responses():
form_id="form-1",
form_content="Rendered",
inputs=[
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", placeholder=None),
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", default=None),
],
actions=[UserAction(id="approve", title="Approve")],
display_in_ui=True,

View File

@ -17,7 +17,7 @@ from core.workflow.nodes.human_input.entities import (
EmailRecipients,
ExternalRecipient,
FormInput,
FormInputPlaceholder,
FormInputDefault,
HumanInputNodeData,
MemberRecipient,
UserAction,
@ -76,37 +76,37 @@ class TestDeliveryMethod:
class TestFormInput:
"""Test FormInput entity."""
def test_text_input_with_constant_placeholder(self):
"""Test text input with constant placeholder."""
placeholder = FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="Enter your response here...")
def test_text_input_with_constant_default(self):
"""Test text input with constant default value."""
default = FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter your response here...")
form_input = FormInput(
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", placeholder=placeholder
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
)
assert form_input.type == FormInputType.TEXT_INPUT
assert form_input.output_variable_name == "user_input"
assert form_input.placeholder.type == PlaceholderType.CONSTANT
assert form_input.placeholder.value == "Enter your response here..."
assert form_input.default.type == PlaceholderType.CONSTANT
assert form_input.default.value == "Enter your response here..."
def test_text_input_with_variable_placeholder(self):
"""Test text input with variable placeholder."""
placeholder = FormInputPlaceholder(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"])
def test_text_input_with_variable_default(self):
"""Test text input with variable default value."""
default = FormInputDefault(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"])
form_input = FormInput(
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", placeholder=placeholder
type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default
)
assert form_input.placeholder.type == PlaceholderType.VARIABLE
assert form_input.placeholder.selector == ["node_123", "output_var"]
assert form_input.default.type == PlaceholderType.VARIABLE
assert form_input.default.selector == ["node_123", "output_var"]
def test_form_input_without_placeholder(self):
"""Test form input without placeholder."""
def test_form_input_without_default(self):
"""Test form input without default value."""
form_input = FormInput(type=FormInputType.PARAGRAPH, output_variable_name="description")
assert form_input.type == FormInputType.PARAGRAPH
assert form_input.output_variable_name == "description"
assert form_input.placeholder is None
assert form_input.default is None
class TestUserAction:
@ -163,7 +163,7 @@ class TestHumanInputNodeData:
FormInput(
type=FormInputType.TEXT_INPUT,
output_variable_name="content",
placeholder=FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="Enter content..."),
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter content..."),
)
]
@ -209,7 +209,7 @@ class TestHumanInputNodeData:
assert node_data.timeout == 1
assert node_data.timeout_unit == TimeoutUnit.DAY
def test_node_data_default_values(self):
def test_node_data_defaults(self):
"""Test node data with default values."""
node_data = HumanInputNodeData(title="Test Node")
@ -302,9 +302,9 @@ class TestRecipients:
class TestHumanInputNodeVariableResolution:
"""Tests for resolving variable-based placeholders in HumanInputNode."""
"""Tests for resolving variable-based defaults in HumanInputNode."""
def test_resolves_variable_placeholders(self):
def test_resolves_variable_defaults(self):
variable_pool = VariablePool(
system_variables=SystemVariable(
user_id="user",
@ -335,12 +335,12 @@ class TestHumanInputNodeVariableResolution:
FormInput(
type=FormInputType.TEXT_INPUT,
output_variable_name="user_name",
placeholder=FormInputPlaceholder(type=PlaceholderType.VARIABLE, selector=["start", "name"]),
default=FormInputDefault(type=PlaceholderType.VARIABLE, selector=["start", "name"]),
),
FormInput(
type=FormInputType.TEXT_INPUT,
output_variable_name="user_email",
placeholder=FormInputPlaceholder(type=PlaceholderType.CONSTANT, value="foo@example.com"),
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="foo@example.com"),
),
],
user_actions=[UserAction(id="submit", title="Submit")],
@ -370,10 +370,10 @@ class TestHumanInputNodeVariableResolution:
assert isinstance(pause_event, PauseRequestedEvent)
expected_values = {"user_name": "Jane Doe"}
assert pause_event.reason.resolved_placeholder_values == expected_values
assert pause_event.reason.resolved_default_values == expected_values
params = mock_repo.create_form.call_args.args[0]
assert params.resolved_placeholder_values == expected_values
assert params.resolved_default_values == expected_values
def test_debugger_falls_back_to_recipient_token_when_webapp_disabled(self):
variable_pool = VariablePool(

View File

@ -53,7 +53,7 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name#
{
"type": "text_input",
"output_variable_name": "name",
"placeholder": {"type": "constant", "value": ""},
"default": {"type": "constant", "value": ""},
}
],
"user_actions": [

View File

@ -50,7 +50,7 @@ class TestFormService:
"tenant_id": "tenant-abc",
"app_id": "app-def",
"form_content": "# Test Form\n\nInput: {{#$output.input#}}",
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", placeholder=None)],
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
"user_actions": [UserAction(id="submit", title="Submit")],
"timeout": 1,
"timeout_unit": TimeoutUnit.HOUR,
@ -305,7 +305,7 @@ class TestFormValidation:
"app_id": "app-def",
"form_content": "Test form",
"inputs": [
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", placeholder=None)
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)
],
"user_actions": [UserAction(id="submit", title="Submit")],
"timeout": 1,

View File

@ -31,7 +31,7 @@ class TestHumanInputForm:
"tenant_id": "tenant-abc",
"app_id": "app-def",
"form_content": "# Test Form\n\nInput: {{#$output.input#}}",
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", placeholder=None)],
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
"user_actions": [UserAction(id="submit", title="Submit")],
"timeout": 2,
"timeout_unit": TimeoutUnit.HOUR,

View File

@ -379,7 +379,7 @@ class TestBuildHumanInputRequiredReason:
rendered_content="rendered",
timeout=1,
timeout_unit=TimeoutUnit.HOUR,
placeholder_values={"name": "Alice"},
default_values={"name": "Alice"},
node_title="Ask Name",
display_in_ui=True,
)

View File

@ -127,7 +127,7 @@ def test_get_by_message_ids_returns_unsubmitted_form_definition() -> None:
rendered_content="rendered",
timeout=1,
timeout_unit=TimeoutUnit.HOUR,
placeholder_values={"name": "John"},
default_values={"name": "John"},
node_title="Approval",
display_in_ui=True,
)
@ -177,4 +177,4 @@ def test_get_by_message_ids_returns_unsubmitted_form_definition() -> None:
assert form_definition.form_content == "Rendered block"
assert form_definition.display_in_ui is True
assert form_definition.form_token == "token-1"
assert form_definition.resolved_placeholder_values == {"name": "John"}
assert form_definition.resolved_default_values == {"name": "John"}

View File

@ -24,8 +24,6 @@ from services.workflow_event_snapshot_service import (
BufferState,
MessageContext,
_build_snapshot_events,
_collect_snapshot_keys,
_filter_buffered_events,
_resolve_task_id,
)
@ -221,36 +219,3 @@ def test_resolve_task_id_priority(context_task_id, buffered_task_id, expected) -
buffer_state.task_id_ready.set()
task_id = _resolve_task_id(resumption_context, buffer_state, "run-1", wait_timeout=0.0)
assert task_id == expected
def test_filter_buffered_events_deduplicates_snapshot_nodes() -> None:
workflow_run = _build_workflow_run(WorkflowExecutionStatus.RUNNING)
snapshot = _build_snapshot(WorkflowNodeExecutionStatus.SUCCEEDED)
events = _build_snapshot_events(
workflow_run=workflow_run,
node_snapshots=[snapshot],
task_id="task-1",
message_context=None,
pause_entity=None,
resumption_context=None,
)
snapshot_keys = _collect_snapshot_keys(events)
buffered_events = [
{
"event": "node_started",
"data": {"id": "exec-1"},
},
{
"event": "node_finished",
"data": {"id": "exec-2"},
},
]
filtered = list(_filter_buffered_events(buffered_events, snapshot_keys))
assert filtered == [
{
"event": "node_finished",
"data": {"id": "exec-2"},
}
]

View File

@ -95,7 +95,7 @@ def test_human_input_delivery_dispatches_to_test_service(monkeypatch: pytest.Mon
service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined]
node_stub = MagicMock()
node_stub._render_form_content_before_submission.return_value = "rendered"
node_stub._resolve_inputs.return_value = {}
node_stub._resolve_default_values.return_value = {}
service._build_human_input_node = MagicMock(return_value=node_stub) # type: ignore[attr-defined]
service._create_human_input_delivery_test_form = MagicMock( # type: ignore[attr-defined]
return_value=("form-1", {})
@ -134,7 +134,7 @@ def test_human_input_delivery_debug_mode_overrides_recipients(monkeypatch: pytes
service._build_human_input_variable_pool = MagicMock(return_value=MagicMock()) # type: ignore[attr-defined]
node_stub = MagicMock()
node_stub._render_form_content_before_submission.return_value = "rendered"
node_stub._resolve_inputs.return_value = {}
node_stub._resolve_default_values.return_value = {}
service._build_human_input_node = MagicMock(return_value=node_stub) # type: ignore[attr-defined]
service._create_human_input_delivery_test_form = MagicMock( # type: ignore[attr-defined]
return_value=("form-1", {})