A toggle with a Value writes it to its variable while on and nothing while off; its Negative goes with the variable. $name.bool reads the switch (true/false) across nodes, in text and in conditions, through __wp_toggle_bools__. Payloads reading $name.bool stamp schema 12; bool is reserved as a tag group.
253 lines
9.4 KiB
Python
253 lines
9.4 KiB
Python
from engine.migrations import (
|
|
CURRENT_SCHEMA_VERSION,
|
|
MAX_KNOWN_SCHEMA_VERSION,
|
|
migrate_payload,
|
|
)
|
|
|
|
EMPTY_7 = {
|
|
"bundles": [], "wildcards": [], "fixed_values": [],
|
|
"combines": [], "derivations": [], "constraints": [], "categories": [],
|
|
}
|
|
|
|
|
|
def test_returns_payload_as_is_at_current_version():
|
|
payload = {"schema_version": CURRENT_SCHEMA_VERSION, **EMPTY_7}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"] == payload
|
|
assert result["migrated_entity_count"] == 0
|
|
|
|
|
|
def test_rejects_future_version():
|
|
# The reject threshold is MAX_KNOWN (6), not CURRENT (2): only versions
|
|
# beyond what the runtime natively supports are rejected.
|
|
payload = {"schema_version": MAX_KNOWN_SCHEMA_VERSION + 1, **EMPTY_7}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is False
|
|
assert "future" in result["reason"].lower()
|
|
|
|
|
|
def test_rejects_missing_version():
|
|
result = migrate_payload({**EMPTY_7})
|
|
assert result["ok"] is False
|
|
assert "schema_version" in result["reason"]
|
|
|
|
|
|
def test_walks_chain_to_current():
|
|
v0_payload = {
|
|
"schema_version": 0,
|
|
**EMPTY_7,
|
|
"wildcards": [{"uuid": "u", "name": "old"}],
|
|
}
|
|
result = migrate_payload(v0_payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == CURRENT_SCHEMA_VERSION
|
|
assert result["migrated"]["wildcards"][0]["migrated_from"] == 0
|
|
# 2 chain steps (v0->v1, v1->v2) each pass the 1 wildcard => count 2.
|
|
assert result["migrated_entity_count"] == 2
|
|
|
|
|
|
def test_walks_chain_on_empty_payload():
|
|
v0_empty = {"schema_version": 0, **EMPTY_7}
|
|
result = migrate_payload(v0_empty)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == CURRENT_SCHEMA_VERSION
|
|
assert result["migrated_entity_count"] == 0
|
|
|
|
|
|
def test_tags_fixed_values_combines_derivations_categories():
|
|
v0_payload = {
|
|
"schema_version": 0,
|
|
"bundles": [],
|
|
"wildcards": [],
|
|
"fixed_values": [{"uuid": "fv1", "name": "fv"}],
|
|
"combines": [{"uuid": "cb1", "name": "cb"}],
|
|
"derivations": [{"uuid": "dr1", "name": "dr"}],
|
|
"constraints": [],
|
|
"categories": [{"uuid": "cat1", "name": "cat"}],
|
|
}
|
|
result = migrate_payload(v0_payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["fixed_values"][0]["migrated_from"] == 0
|
|
assert result["migrated"]["combines"][0]["migrated_from"] == 0
|
|
assert result["migrated"]["derivations"][0]["migrated_from"] == 0
|
|
assert result["migrated"]["categories"][0]["migrated_from"] == 0
|
|
|
|
|
|
def test_sums_all_7_arrays_for_migrated_entity_count():
|
|
v0_payload = {
|
|
"schema_version": 0,
|
|
"bundles": [{"uuid": "b1"}],
|
|
"wildcards": [{"uuid": "w1"}],
|
|
"fixed_values": [{"uuid": "fv1"}],
|
|
"combines": [{"uuid": "cb1"}],
|
|
"derivations": [{"uuid": "dr1"}],
|
|
"constraints": [{"uuid": "c1"}],
|
|
"categories": [{"uuid": "cat1"}],
|
|
}
|
|
result = migrate_payload(v0_payload)
|
|
assert result["ok"] is True
|
|
# 7 entities x 2 chain steps (v0->v1, v1->v2) = 14.
|
|
assert result["migrated_entity_count"] == 14
|
|
|
|
|
|
def test_defaults_missing_arrays_to_empty():
|
|
# A partial payload (missing new buckets) should still succeed —
|
|
# migrate_payload fills them in via .get("key", []).
|
|
partial = {"schema_version": CURRENT_SCHEMA_VERSION, "wildcards": [{"uuid": "w1"}]}
|
|
result = migrate_payload(partial)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["fixed_values"] == []
|
|
assert result["migrated"]["combines"] == []
|
|
assert result["migrated"]["derivations"] == []
|
|
assert result["migrated"]["categories"] == []
|
|
|
|
|
|
# === Install-path fork: accept natively-supported future versions ==========
|
|
# Mirror of the TS `migrateImportEnvelope` fork. A payload at
|
|
# CURRENT < v <= MAX_KNOWN (v3 = text-grammar only; v4 = additive
|
|
# `target_select`; v5 = additive `accepts` tag axes; v6 = the constraint
|
|
# `only` rule; v7 = derivation AND / OR condition groups; v8 = send-to-negative
|
|
# `negative` text; v9 = the wildcard `fallback` option flag; v10 = NOT tests;
|
|
# v11 = `$name` terms in filters; v12 = `$name.bool`) is shape-compatible with
|
|
# v2 and natively handled at runtime, so the engine's commit-side re-validate must accept it AS-IS:
|
|
# not rejected, not migrated (the while-loop bound stays CURRENT so there is
|
|
# nothing to do), schema_version preserved. Only v > MAX_KNOWN rejects.
|
|
|
|
|
|
def test_max_known_schema_version_is_12():
|
|
assert MAX_KNOWN_SCHEMA_VERSION == 12
|
|
|
|
|
|
def test_v3_payload_passes_through_unchanged():
|
|
payload = {
|
|
"schema_version": 3,
|
|
**EMPTY_7,
|
|
"wildcards": [
|
|
{"id": "wcaaa", "payload": {"options": [
|
|
{"id": "o", "value": "{1-2$$a|b}", "weight": 1}]}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
# No migration ran — schema_version preserved, count zero.
|
|
assert result["migrated"]["schema_version"] == 3
|
|
assert result["migrated_entity_count"] == 0
|
|
|
|
|
|
def test_v4_target_select_payload_passes_through_unchanged():
|
|
payload = {
|
|
"schema_version": 4,
|
|
**EMPTY_7,
|
|
"constraints": [
|
|
{"id": "cn001", "type": "constraint", "name": "c", "payload": {
|
|
"source_wildcard_id": "wcaaa", "target_wildcard_id": "wcbbb",
|
|
"matrix": {}, "exceptions": [],
|
|
"target_select": {"mode": "next", "count": 2}}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == 4
|
|
assert result["migrated_entity_count"] == 0
|
|
# The additive target_select field survives verbatim.
|
|
cn = result["migrated"]["constraints"][0]
|
|
assert cn["payload"]["target_select"] == {"mode": "next", "count": 2}
|
|
|
|
|
|
def test_v5_accepts_axis_payload_passes_through_unchanged():
|
|
payload = {
|
|
"schema_version": 5,
|
|
**EMPTY_7,
|
|
"wildcards": [
|
|
{"id": "wcaxs", "type": "wildcard", "name": "outfit", "payload": {
|
|
"tag_groups": {"SHOES": ["boots"]},
|
|
"tag_group_kinds": {"SHOES": "accepts"},
|
|
"options": [{"id": "o", "value": "hiker", "weight": 1,
|
|
"sub_categories": ["boots"]}]}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == 5
|
|
assert result["migrated_entity_count"] == 0
|
|
wc = result["migrated"]["wildcards"][0]
|
|
assert wc["payload"]["tag_group_kinds"] == {"SHOES": "accepts"}
|
|
|
|
|
|
def test_v6_only_rule_payload_passes_through_unchanged():
|
|
only = {"source_value": "maid", "target_value": "apron", "mode": "only", "factor": 1.0}
|
|
payload = {
|
|
"schema_version": 6,
|
|
**EMPTY_7,
|
|
"constraints": [
|
|
{"id": "cnonly", "type": "constraint", "name": "link", "payload": {
|
|
"source_wildcard_id": "a", "target_wildcard_id": "b",
|
|
"matrix": {}, "exceptions": [only]}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == 6
|
|
assert result["migrated_entity_count"] == 0
|
|
assert result["migrated"]["constraints"][0]["payload"]["exceptions"] == [only]
|
|
|
|
|
|
def test_v7_condition_group_payload_passes_through_unchanged():
|
|
condition = {"match": "any", "conditions": [
|
|
{"var": "mood", "op": "equals", "value": "calm"},
|
|
{"var": "time", "op": "is_empty", "value": ""},
|
|
]}
|
|
payload = {
|
|
"schema_version": 7,
|
|
**EMPTY_7,
|
|
"derivations": [
|
|
{"id": "dvgroup", "type": "derivation", "name": "d", "payload": {"rules": [
|
|
{"id": "r1", "branches": [{
|
|
"condition": condition,
|
|
"action": {"target_var": "light", "mode": "replace", "value": "soft"},
|
|
}]},
|
|
]}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == 7
|
|
assert result["migrated_entity_count"] == 0
|
|
branch = result["migrated"]["derivations"][0]["payload"]["rules"][0]["branches"][0]
|
|
assert branch["condition"] == condition
|
|
|
|
|
|
def test_v8_negative_payload_passes_through_unchanged():
|
|
payload = {
|
|
"schema_version": 8,
|
|
**EMPTY_7,
|
|
"wildcards": [
|
|
{"id": "wcneg", "payload": {"options": [
|
|
{"id": "o", "value": "red hair", "weight": 1, "negative": "blonde"}]}}
|
|
],
|
|
}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == 8
|
|
assert result["migrated_entity_count"] == 0
|
|
opt = result["migrated"]["wildcards"][0]["payload"]["options"][0]
|
|
assert opt["negative"] == "blonde"
|
|
|
|
|
|
def test_v2_still_migrates_from_older_version():
|
|
v0_payload = {"schema_version": 0, **EMPTY_7, "wildcards": [{"uuid": "u", "name": "old"}]}
|
|
result = migrate_payload(v0_payload)
|
|
assert result["ok"] is True
|
|
assert result["migrated"]["schema_version"] == CURRENT_SCHEMA_VERSION
|
|
assert result["migrated"]["wildcards"][0]["migrated_from"] == 0
|
|
|
|
|
|
def test_v5_payload_still_rejects_with_future_error():
|
|
payload = {"schema_version": MAX_KNOWN_SCHEMA_VERSION + 1, **EMPTY_7}
|
|
result = migrate_payload(payload)
|
|
assert result["ok"] is False
|
|
assert "future" in result["reason"].lower()
|
|
# The reject reason names the max-known ceiling, not the chain head.
|
|
assert str(MAX_KNOWN_SCHEMA_VERSION) in result["reason"]
|