`$outfit.K.AXIS` compacted away picks whose option has no tag on the axis, so every later index slid down and `$outfit.0.SHOES` could print pick 1's shoe beside pick 0. Both the resolver (`get_axis`) and the derivation condition reader now keep one slot per pick. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fQ7Lkc8AxwXqsRZrFBxE9
110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
"""A derivation condition can test a rolled axis.
|
|
|
|
This is the reported ask, written out: "IF location = outdoors, append one of
|
|
the tags from SHOES". Conditions read `condition.var` as a raw string rather
|
|
than a token, so this path parses the accessor itself.
|
|
"""
|
|
import random
|
|
|
|
from engine.modules.derivation_handler import DerivationHandler
|
|
|
|
|
|
def _ctx(axes=None):
|
|
return {
|
|
"__wp_rng__": random.Random(0),
|
|
"__wp_node_seed__": 1,
|
|
"__wp_warnings__": [],
|
|
"__wp_axes__": {"outfit": {"SHOES": "sandals"}} if axes is None else axes,
|
|
"outfit": "white t-shirt",
|
|
"location": "outdoors",
|
|
}
|
|
|
|
|
|
def _rule(var, op="equals", value="sandals"):
|
|
return {
|
|
"rules": [{
|
|
"id": "r1",
|
|
"branches": [{
|
|
"condition": {"var": var, "op": op, "value": value},
|
|
"action": {
|
|
"target_var": "footwear",
|
|
"mode": "replace",
|
|
"value": "strappy sandals",
|
|
},
|
|
}],
|
|
}]
|
|
}
|
|
|
|
|
|
def test_condition_reads_an_axis():
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.SHOES"), {}, _ctx(),
|
|
)["footwear"] == "strappy sandals"
|
|
|
|
|
|
def test_condition_on_a_missing_axis_is_falsy_not_an_error():
|
|
# Derivations are control flow, not validation: a condition that cannot be
|
|
# answered is simply false.
|
|
assert DerivationHandler.resolve(_rule("outfit.SHOES"), {}, _ctx(axes={})) == {}
|
|
|
|
|
|
def test_exists_op_sees_an_axis():
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.SHOES", op="exists", value=""), {}, _ctx(),
|
|
)["footwear"] == "strappy sandals"
|
|
|
|
|
|
def test_exists_op_is_false_for_an_undeclared_axis():
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.BELTS", op="exists", value=""), {}, _ctx(),
|
|
) == {}
|
|
|
|
|
|
def test_multi_pick_axis_joins_for_a_bare_read():
|
|
ctx = _ctx(axes={"outfit": [{"SHOES": "sandals"}, {"SHOES": "sneakers"}]})
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.SHOES", value="sandals, sneakers"), {}, ctx,
|
|
)["footwear"] == "strappy sandals"
|
|
|
|
|
|
def test_multi_pick_axis_indexes_in_either_order():
|
|
ctx = _ctx(axes={"outfit": [{"SHOES": "sandals"}, {"SHOES": "sneakers"}]})
|
|
for var in ("outfit.1.SHOES", "outfit.SHOES.1"):
|
|
assert DerivationHandler.resolve(
|
|
_rule(var, value="sneakers"), {}, _ctx(axes=ctx["__wp_axes__"]),
|
|
)["footwear"] == "strappy sandals"
|
|
|
|
|
|
def test_multi_pick_axis_index_keeps_its_slot_when_a_pick_has_no_axis_tag():
|
|
axes = {"outfit": [{}, {"SHOES": "sneakers"}]}
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.1.SHOES", value="sneakers"), {}, _ctx(axes=axes),
|
|
)["footwear"] == "strappy sandals"
|
|
assert DerivationHandler.resolve(
|
|
_rule("outfit.0.SHOES", value="sneakers"), {}, _ctx(axes=axes),
|
|
) == {}
|
|
|
|
|
|
def test_plain_var_condition_is_untouched():
|
|
assert DerivationHandler.resolve(
|
|
_rule("location", value="outdoors"), {}, _ctx(),
|
|
)["footwear"] == "strappy sandals"
|
|
|
|
|
|
def test_action_can_emit_the_axis_value():
|
|
"""The literal reported ask: when outdoors, append one of the SHOES tags."""
|
|
payload = {
|
|
"rules": [{
|
|
"id": "r1",
|
|
"branches": [{
|
|
"condition": {"var": "location", "op": "equals", "value": "outdoors"},
|
|
"action": {
|
|
"target_var": "footwear",
|
|
"mode": "replace",
|
|
"value": "$outfit.SHOES",
|
|
},
|
|
}],
|
|
}]
|
|
}
|
|
assert DerivationHandler.resolve(payload, {}, _ctx())["footwear"] == "sandals"
|