Exposes the pick_independent instance field (engine resolution landed in 33b3600): instance schema + snapshot round-trip, TS instance type + _shell INSTANCE_FIELDS_PER_KIND registry (so reset-overrides + modified-state + the TS/Py parity gate all include it) + preview cache-key, and the canvas PoolSection 'Allow repeats' toggle (shown only while multi-pick is active; off = unique). Instance-only (per-node, canvas runtime), additive + tolerant — no community catalog change.
194 lines
6.0 KiB
Python
194 lines
6.0 KiB
Python
"""Tests for snapshot.py — payload hash, freeze, legacy coercion."""
|
|
from engine.modules.snapshot import (
|
|
coerce_legacy_module,
|
|
freeze_snapshot,
|
|
payload_hash,
|
|
)
|
|
|
|
|
|
def test_payload_hash_is_stable_across_key_order():
|
|
a = {"x": 1, "y": [1, 2]}
|
|
b = {"y": [1, 2], "x": 1}
|
|
assert payload_hash(a) == payload_hash(b)
|
|
|
|
|
|
def test_payload_hash_changes_with_value():
|
|
a = {"x": 1}
|
|
b = {"x": 2}
|
|
assert payload_hash(a) != payload_hash(b)
|
|
|
|
|
|
def test_payload_hash_returns_64_char_hex_string():
|
|
h = payload_hash({"x": 1})
|
|
assert isinstance(h, str)
|
|
assert len(h) == 64
|
|
int(h, 16) # raises ValueError if not hex
|
|
|
|
|
|
def test_payload_hash_handles_non_ascii():
|
|
a = {"name": "café"}
|
|
h = payload_hash(a)
|
|
assert len(h) == 64
|
|
|
|
|
|
def test_freeze_snapshot_includes_required_fields():
|
|
library_row = {
|
|
"id": "wc_colors_a3f2",
|
|
"type": "wildcard",
|
|
"name": "colors",
|
|
"category_id": "style",
|
|
"version": 7,
|
|
"payload": {"options": []},
|
|
}
|
|
snap = freeze_snapshot(library_row)
|
|
assert snap["library_id"] == "wc_colors_a3f2"
|
|
assert snap["library_version_at_snapshot"] == 7
|
|
assert snap["library_snapshot_at"]
|
|
assert snap["library_snapshot_at"].endswith("Z")
|
|
assert "." in snap["library_snapshot_at"] # ms-precision
|
|
assert snap["type"] == "wildcard"
|
|
assert snap["name"] == "colors"
|
|
assert snap["category_id"] == "style"
|
|
assert snap["payload"] == {"options": []}
|
|
assert snap["instance"] == {
|
|
"variable_binding": None,
|
|
"enabled_options": None,
|
|
"category_filter": None,
|
|
"option_weights": None,
|
|
# SP2a multi-select + null toggle — part of the snapshot baseline.
|
|
"exclude_null": None,
|
|
"pick_min": None,
|
|
"pick_max": None,
|
|
"pick_separator": None,
|
|
"pick_independent": None,
|
|
"mode": None,
|
|
"pinned_option_id": None,
|
|
"locked_seed": None,
|
|
"internal": None,
|
|
"disabled_rule_ids": None,
|
|
"disabled_exception_keys": None,
|
|
"disabled_matrix_cells": None,
|
|
}
|
|
|
|
|
|
def test_freeze_snapshot_each_call_yields_fresh_instance_dict():
|
|
library_row = {
|
|
"id": "x", "type": "wildcard", "name": "n",
|
|
"category_id": None, "version": 1, "payload": {},
|
|
}
|
|
a = freeze_snapshot(library_row)
|
|
b = freeze_snapshot(library_row)
|
|
a["instance"]["variable_binding"] = "$mutated"
|
|
assert b["instance"]["variable_binding"] is None
|
|
|
|
|
|
def test_freeze_snapshot_handles_missing_category_id():
|
|
library_row = {
|
|
"id": "x", "type": "wildcard", "name": "n",
|
|
"version": 1, "payload": {},
|
|
}
|
|
snap = freeze_snapshot(library_row)
|
|
assert snap["category_id"] is None
|
|
|
|
|
|
def test_coerce_legacy_module_fills_missing_fields_for_fixed_values():
|
|
legacy = {
|
|
"id": "fv_x",
|
|
"type": "fixed_values",
|
|
"entries": [
|
|
{"variable_name": "lens", "value": "85mm"},
|
|
{"variable_name": "angle", "value": "wide"},
|
|
],
|
|
}
|
|
coerced = coerce_legacy_module(legacy)
|
|
assert coerced["library_id"] is None
|
|
assert coerced["library_snapshot_at"] is None
|
|
assert coerced["library_version_at_snapshot"] is None
|
|
assert coerced["type"] == "fixed_values"
|
|
assert coerced["payload"]["values"][0] == {
|
|
"id": "val_0000", "name": "lens", "value": "85mm",
|
|
}
|
|
assert coerced["payload"]["values"][1] == {
|
|
"id": "val_0001", "name": "angle", "value": "wide",
|
|
}
|
|
assert coerced["instance"]["variable_binding"] is None
|
|
|
|
|
|
def test_coerce_legacy_module_passes_through_already_coerced():
|
|
already = {
|
|
"library_id": "fv_x",
|
|
"library_snapshot_at": "2026-04-26T00:00:00Z",
|
|
"library_version_at_snapshot": 1,
|
|
"type": "fixed_values",
|
|
"name": "lens",
|
|
"category_id": None,
|
|
"payload": {"values": []},
|
|
"instance": {
|
|
"variable_binding": "$lens",
|
|
"enabled_options": None,
|
|
"category_filter": None,
|
|
},
|
|
}
|
|
coerced = coerce_legacy_module(already)
|
|
assert coerced == already
|
|
|
|
|
|
def test_coerce_legacy_module_uses_meta_name_when_present():
|
|
legacy = {
|
|
"id": "fv_x",
|
|
"type": "fixed_values",
|
|
"meta": {"name": "from-meta"},
|
|
"entries": [],
|
|
}
|
|
coerced = coerce_legacy_module(legacy)
|
|
assert coerced["name"] == "from-meta"
|
|
|
|
|
|
def test_coerce_legacy_module_prefers_top_level_name_over_meta():
|
|
legacy = {
|
|
"id": "x",
|
|
"type": "fixed_values",
|
|
"name": "top-level",
|
|
"meta": {"name": "from-meta"},
|
|
"entries": [],
|
|
}
|
|
coerced = coerce_legacy_module(legacy)
|
|
assert coerced["name"] == "top-level"
|
|
|
|
|
|
def test_coerce_legacy_module_non_fixed_values_uses_payload():
|
|
raw = {
|
|
"id": "wc_x",
|
|
"type": "wildcard",
|
|
"name": "x",
|
|
"payload": {"options": [{"id": "o1", "value": "red", "weight": 1}]},
|
|
}
|
|
coerced = coerce_legacy_module(raw)
|
|
assert coerced["type"] == "wildcard"
|
|
assert coerced["payload"] == {"options": [{"id": "o1", "value": "red", "weight": 1}]}
|
|
|
|
|
|
def test_coerce_legacy_module_preserves_user_instance_overrides():
|
|
"""SPA-picked modules carry an `instance` dict but no `library_id`.
|
|
|
|
Previously the short-circuit guard required both keys, so user
|
|
overrides (option enable/disable, weight overrides) got stomped by
|
|
`_fresh_instance()`. This test pins the merge behavior.
|
|
"""
|
|
raw = {
|
|
"id": "ab12cd34",
|
|
"type": "wildcard",
|
|
"name": "outfit",
|
|
"payload": {"options": [{"id": "o1", "value": "red", "weight": 1}]},
|
|
"instance": {
|
|
"enabled_options": ["o1"],
|
|
"option_weights": {"o1": 5.0},
|
|
},
|
|
}
|
|
coerced = coerce_legacy_module(raw)
|
|
assert coerced["instance"]["enabled_options"] == ["o1"]
|
|
assert coerced["instance"]["option_weights"] == {"o1": 5.0}
|
|
# Defaults still present for keys the user didn't override.
|
|
assert coerced["instance"]["variable_binding"] is None
|
|
assert coerced["instance"]["category_filter"] is None
|