mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-04-28 03:30:10 +00:00
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
"""Regression tests for SKY-8799.
|
|
|
|
PyYAML's default ``SafeLoader`` resolves ISO 8601 strings into Python
|
|
``datetime`` objects. When users embed such strings inside the
|
|
``default_value`` of a JSON workflow parameter, those values become
|
|
``datetime`` instances on the way in and then crash ``json.dumps`` on
|
|
the way back out, producing::
|
|
|
|
TypeError: Object of type datetime is not JSON serializable
|
|
|
|
``skyvern.utils.yaml_loader.safe_load_no_dates`` removes the timestamp
|
|
implicit resolver so the values stay as plain strings.
|
|
"""
|
|
|
|
import json
|
|
|
|
import yaml
|
|
|
|
from skyvern.forge.sdk.routes.workflow_copilot import _process_workflow_yaml
|
|
from skyvern.utils.yaml_loader import safe_load_no_dates
|
|
|
|
ISO_BLOB = """
|
|
parameters:
|
|
- parameter_type: workflow
|
|
key: payload
|
|
workflow_parameter_type: json
|
|
default_value:
|
|
id: "12345"
|
|
metadata:
|
|
# Unquoted ISO 8601 strings are what trip the default SafeLoader.
|
|
created_at: 2023-10-27T10:00:00Z
|
|
updated_at: 2023-10-28T14:30:00Z
|
|
tags: ["primary", "test-data"]
|
|
"""
|
|
|
|
|
|
def test_default_safe_load_does_parse_datetimes() -> None:
|
|
# Sanity check: documents the behavior we are working around.
|
|
parsed = yaml.safe_load(ISO_BLOB)
|
|
default_value = parsed["parameters"][0]["default_value"]
|
|
# The default loader turns the unquoted-looking timestamps into datetimes,
|
|
# which is exactly what breaks downstream JSON serialization.
|
|
assert not isinstance(default_value["metadata"]["created_at"], str)
|
|
|
|
|
|
def test_safe_load_no_dates_keeps_iso_strings_as_strings() -> None:
|
|
parsed = safe_load_no_dates(ISO_BLOB)
|
|
metadata = parsed["parameters"][0]["default_value"]["metadata"]
|
|
|
|
assert metadata["created_at"] == "2023-10-27T10:00:00Z"
|
|
assert metadata["updated_at"] == "2023-10-28T14:30:00Z"
|
|
assert isinstance(metadata["created_at"], str)
|
|
assert isinstance(metadata["updated_at"], str)
|
|
|
|
|
|
def test_safe_load_no_dates_round_trips_through_json() -> None:
|
|
parsed = safe_load_no_dates(ISO_BLOB)
|
|
# The whole point: the parsed structure must be JSON-serializable
|
|
# without a custom encoder.
|
|
serialized = json.dumps(parsed)
|
|
assert "2023-10-27T10:00:00Z" in serialized
|
|
|
|
|
|
def test_safe_load_no_dates_preserves_other_implicit_types() -> None:
|
|
parsed = safe_load_no_dates(
|
|
"""
|
|
an_int: 42
|
|
a_float: 3.14
|
|
a_bool: true
|
|
a_null: null
|
|
a_list: [1, 2, 3]
|
|
"""
|
|
)
|
|
assert parsed["an_int"] == 42
|
|
assert parsed["a_float"] == 3.14
|
|
assert parsed["a_bool"] is True
|
|
assert parsed["a_null"] is None
|
|
assert parsed["a_list"] == [1, 2, 3]
|
|
|
|
|
|
def test_process_workflow_yaml_keeps_json_parameter_iso_strings() -> None:
|
|
workflow = _process_workflow_yaml(
|
|
workflow_id="wf-123",
|
|
workflow_permanent_id="wfp-123",
|
|
organization_id="org-123",
|
|
workflow_yaml="""
|
|
title: Test
|
|
workflow_definition:
|
|
parameters:
|
|
- parameter_type: workflow
|
|
key: payload
|
|
workflow_parameter_type: json
|
|
default_value:
|
|
id: "12345"
|
|
metadata:
|
|
created_at: 2023-10-27T10:00:00Z
|
|
updated_at: 2023-10-28T14:30:00Z
|
|
blocks:
|
|
- block_type: navigation
|
|
label: step1
|
|
url: https://example.com
|
|
title: Step 1
|
|
navigation_goal: Open the page
|
|
""",
|
|
)
|
|
|
|
parameter = workflow.get_parameter("payload")
|
|
assert parameter is not None
|
|
assert parameter.default_value is not None
|
|
|
|
metadata = parameter.default_value["metadata"]
|
|
assert metadata["created_at"] == "2023-10-27T10:00:00Z"
|
|
assert metadata["updated_at"] == "2023-10-28T14:30:00Z"
|
|
assert isinstance(metadata["created_at"], str)
|
|
assert isinstance(metadata["updated_at"], str)
|