feat(wp-nodes): deserialize_node_input — modules + snapshots + pickOrder
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""Tests for catalog injection at WP_Context node execution + the
|
||||
backwards-compat path for old workflow JSON. Spec §4.4 / §6 / 5.5.3."""
|
||||
from __future__ import annotations
|
||||
|
||||
from wp_nodes.types import deserialize_node_input
|
||||
|
||||
|
||||
def test_deserialize_returns_modules_with_empty_snapshots_for_legacy_input():
|
||||
"""Old workflow JSON had only `modules` (a list). The deserializer
|
||||
returns `(modules, snapshots, pickOrder)` where snapshots defaults to
|
||||
empty dict and pickOrder defaults to empty list — backwards compat,
|
||||
spec §4.5."""
|
||||
legacy = {"modules": [
|
||||
{"type": "fixed_values", "name": "x", "payload": {"values": []}},
|
||||
]}
|
||||
modules, snapshots, pick_order = deserialize_node_input(legacy)
|
||||
assert len(modules) == 1
|
||||
assert snapshots == {}
|
||||
assert pick_order == []
|
||||
|
||||
|
||||
def test_deserialize_extracts_snapshots_and_pickorder_from_new_input():
|
||||
new_shape = {
|
||||
"modules": [
|
||||
{"type": "wildcard", "name": "outfit",
|
||||
"payload": {"options": [{"value": "x"}]}},
|
||||
],
|
||||
"snapshots": {
|
||||
"ou111111": {
|
||||
"snapshot_version": 1, "uuid": "ou111111", "type": "wildcard",
|
||||
"name": "outfit",
|
||||
"payload": {"options": [{"value": "x"}]},
|
||||
"payload_hash": "h" * 64,
|
||||
"source": {"kind": "user"},
|
||||
},
|
||||
},
|
||||
"pickOrder": ["ou111111"],
|
||||
"snapshotVersion": 1,
|
||||
}
|
||||
modules, snapshots, pick_order = deserialize_node_input(new_shape)
|
||||
assert len(modules) == 1
|
||||
assert "ou111111" in snapshots
|
||||
assert pick_order == ["ou111111"]
|
||||
@@ -124,6 +124,44 @@ def deserialize_modules(payload: str) -> list[Module]:
|
||||
return [module_from_dict(entry) for entry in raw_modules]
|
||||
|
||||
|
||||
def deserialize_node_input(
|
||||
raw: "dict[str, Any] | str | None",
|
||||
) -> "tuple[list[dict[str, Any]], dict[str, Any], list[str]]":
|
||||
"""Parse a WP_Context node's ``modules`` widget value into the three
|
||||
parallel concerns the engine needs: an executable module list, a catalog
|
||||
snapshot dict, and the user's explicit pick order.
|
||||
|
||||
Spec §4.4. Backwards-compat: old workflows have no ``snapshots`` /
|
||||
``pickOrder`` fields — those default to empty (catalog stays {}, no
|
||||
@{} resolution, raw token leaks in output, current behavior).
|
||||
"""
|
||||
if raw is None:
|
||||
return [], {}, []
|
||||
if isinstance(raw, str):
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except Exception:
|
||||
return [], {}, []
|
||||
else:
|
||||
data = raw
|
||||
if not isinstance(data, dict):
|
||||
return [], {}, []
|
||||
|
||||
raw_modules = data.get("modules")
|
||||
modules: list[dict[str, Any]] = (
|
||||
list(raw_modules) if isinstance(raw_modules, list) else []
|
||||
)
|
||||
snapshots_raw = data.get("snapshots")
|
||||
snapshots: dict[str, Any] = (
|
||||
snapshots_raw if isinstance(snapshots_raw, dict) else {}
|
||||
)
|
||||
pick_order_raw = data.get("pickOrder")
|
||||
pick_order: list[str] = (
|
||||
pick_order_raw if isinstance(pick_order_raw, list) else []
|
||||
)
|
||||
return modules, snapshots, pick_order
|
||||
|
||||
|
||||
def build_payload(
|
||||
ctx: dict[str, Any],
|
||||
upstream_debug: dict[str, Any],
|
||||
|
||||
Reference in New Issue
Block a user