Files
DumiFlex-ComfyUI-Wildcard-P…/tests/engine/cascade/test_subcat_schema_fix.py
T
DumiFlex 13de622c6b feat(engine): multi-tag options + boolean sub-category filters (v2)
Option sub_category -> sub_categories[] (default []). Both pool filters (instance category_filter, nested @{} ref) become boolean expressions (and/or/not/parens; comma=or) matched against an option's tag set via the shared engine/syntax/subcat_filter matcher, applied BEFORE constraints. Exclude-null is a separate flag: instance exclude_null + the trailing !null ref marker, replacing the old 'null' keyword.

4-segment ref grammar @{uuid#name:expr!null} single-sourced across tokenize.py/scan.py/pipeline.py/_constraints.py (! excluded from name/expr captures); tokenizer REF meta now carries filter_expr + exclude_null instead of a comma-split list. scan.py findall unpack sites updated for the 4th group.

validate_payload: per-option sub_categories[] must be in the registry; optional tag_groups (UI-only axes, members in-registry, <=1 group); registry names validated via subcat_filter.validate_subcat_name (rejects whitespace, boolean/ref chars, reserved words incl. null). wp_api/_validators delegates to the same function.

Constraints stay alive via a primary-tag stopgap: _apply_constraint_to_options + _record_pick use sub_categories[0] as the matrix key; true multi-tag matrix matching is SP3. Cascade fix_subcat_delete/rename + orchestrator scrub the per-option list; instance_schemas category_filter -> string. Existing engine tests migrated to the v2 shape; added tests for the ref regex, payload validation, and multi-tag pool filtering.
2026-06-07 00:43:55 +03:00

126 lines
4.2 KiB
Python

"""Real-shape fixture tests for subcategory rename + delete fixers.
These tests construct wildcards via `ModuleRepository.create` so the
data matches what the server actually stores under the SP1 multi-tag
model: each option carries a `sub_categories` membership list plus a
top-level `payload.sub_categories` declared list. A subcat rename
rewrites the matching entry inside each option's list; a delete strips
it from the list (the option itself is preserved).
Regression: 2026-05-24 live QA on cascade-edit-indicators v1.
"""
import sqlite3
import pytest
from engine.cascade.orchestrator import apply_cascade
from engine.db.migrations import migrate
from engine.db.repositories import ModuleRepository
@pytest.fixture
def conn():
c = sqlite3.connect(":memory:")
c.row_factory = sqlite3.Row
migrate(c)
yield c
c.close()
def _create_wildcard(conn, name, sub_categories, options):
repo = ModuleRepository(conn)
return repo.create(
type="wildcard",
name=name,
description="",
category_id=None,
tags=[],
payload={"sub_categories": sub_categories, "options": options},
)
def test_subcat_rename_updates_top_level_list(conn):
w = _create_wildcard(
conn,
"mood",
sub_categories=["positive", "negative"],
options=[
{"value": "serene", "weight": 1, "sub_categories": ["positive"], "probability": 1.0},
{"value": "tense", "weight": 1, "sub_categories": ["negative"], "probability": 1.0},
],
)
res = apply_cascade(conn, {
"kind": "subcategory", "id": w["id"], "action": "rename",
"cascade_refs": True, "new_name": "good",
"extra": {"subcat_name": "positive"},
})
assert res["ok"], res
updated = ModuleRepository(conn).get(w["id"])
assert "good" in updated["payload"]["sub_categories"]
assert "positive" not in updated["payload"]["sub_categories"]
def test_subcat_rename_updates_option_assignments(conn):
w = _create_wildcard(
conn,
"mood",
sub_categories=["positive", "negative"],
options=[
{"value": "serene", "weight": 1, "sub_categories": ["positive"], "probability": 1.0},
{"value": "tense", "weight": 1, "sub_categories": ["negative"], "probability": 1.0},
],
)
apply_cascade(conn, {
"kind": "subcategory", "id": w["id"], "action": "rename",
"cascade_refs": True, "new_name": "good",
"extra": {"subcat_name": "positive"},
})
updated = ModuleRepository(conn).get(w["id"])
assignments = [o["sub_categories"] for o in updated["payload"]["options"]]
assert assignments == [["good"], ["negative"]]
def test_subcat_delete_removes_from_top_level_and_options(conn):
w = _create_wildcard(
conn,
"mood",
sub_categories=["positive", "negative"],
options=[
{"value": "serene", "weight": 1, "sub_categories": ["positive"], "probability": 1.0},
{"value": "tense", "weight": 1, "sub_categories": ["negative"], "probability": 1.0},
],
)
res = apply_cascade(conn, {
"kind": "subcategory", "id": w["id"], "action": "delete",
"cascade_refs": True, "extra": {"subcat_name": "positive"},
})
assert res["ok"]
updated = ModuleRepository(conn).get(w["id"])
assert updated["payload"]["sub_categories"] == ["negative"]
assignments = [o["sub_categories"] for o in updated["payload"]["options"]]
assert assignments == [[], ["negative"]]
def test_subcat_rename_optout_updates_source_only(conn):
w = _create_wildcard(
conn,
"mood",
sub_categories=["positive", "negative"],
options=[
{"value": "serene", "weight": 1, "sub_categories": ["positive"], "probability": 1.0},
],
)
res = apply_cascade(conn, {
"kind": "subcategory", "id": w["id"], "action": "rename",
"cascade_refs": False, "new_name": "good",
"extra": {"subcat_name": "positive"},
})
assert res["ok"]
updated = ModuleRepository(conn).get(w["id"])
assert updated["payload"]["sub_categories"] == ["good", "negative"]
assert updated["payload"]["options"][0]["sub_categories"] == ["good"]