Files
C.E.L_Slide_test2/tests/test_structure_override_resolver.py
kyeongmin 4da22adb43
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 20s
feat(#90): IMP-56 u1-u19 catch-up before final close (post-u20 push fix)
u1: text_overrides axis in user_overrides_io
u2: structure_overrides axis in user_overrides_io
u3: vite allowlist for new endpoints
u4: text_override_resolver
u5: Step 12 text_overrides apply in phase_z2_pipeline
u6: structure_override_resolver
u7: text_path_stamper
u8: SlideCanvas text-edit capture
u9: SlideCanvas structure-edit overlay
u10: userOverridesApi service extension
u11: designAgent types extension
u12: slidePlanUtils restore
u13: user_overrides endpoint tests
u14: user_overrides restore tests
u15: pipeline fallback tests
u16: edit-mode state + gating tests
u17: slide_base print mode CSS
u18: /api/connect endpoint (vite)
u19: /api/export endpoint (vite)

Recovery scope: 29 files (12 modified + 17 new). u20 already pushed in
9439575; this commit lands u1-u19 that were authored but not committed
before #90 was externally closed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 06:12:13 +09:00

393 lines
15 KiB
Python

"""IMP-56 (#90) u6 — tests for ``src.structure_override_resolver``.
Covers the resolver contract called out in the Stage 2 plan :
1. ``validate_structure_overrides`` returns ``{}`` for non-mapping input.
2. ``validate_structure_overrides`` preserves well-formed entries.
3. ``validate_structure_overrides`` drops malformed per-entry rows without
rejecting the whole batch (per-entry tolerance — mirrors u4
text_override_resolver contract).
4. ``validate_structure_overrides`` REJECTS frame swap (any inner key
other than slot_order / hidden_slots is silently dropped — SCOPE LOCK).
5. ``validate_structure_overrides`` drops non-list slot_order / hidden_slots
values.
6. ``validate_structure_overrides`` drops non-string or empty slot_keys
inside slot_order / hidden_slots.
7. ``validate_structure_overrides`` de-duplicates slot_key entries within
each list.
8. ``validate_structure_overrides`` returns fresh nested dicts AND lists
(caller can mutate without aliasing the source).
9. ``validate_structure_overrides`` drops per-zone payloads that contain
neither a non-empty slot_order nor a non-empty hidden_slots after
sanitization.
10. ``apply_structure_override`` removes hidden_slots in-place and returns
``True``.
11. ``apply_structure_override`` reorders the slot-payload mapping per
slot_order (partial reorder; unmentioned slots keep tail order).
12. ``apply_structure_override`` combines hide + reorder atomically.
13. ``apply_structure_override`` silently skips stale slot_keys (frame
swap / layout regression) without raising.
14. ``apply_structure_override`` returns ``False`` (no mutation) on a
no-op override (empty, or all stale).
15. ``apply_structure_override`` preserves the caller's reference identity
on ``zone`` (in-place mutation via clear + update).
16. ``apply_structure_override`` NEVER inspects or mutates per-slot
payload values — only top-level key membership / ordering.
17. ``apply_structure_override`` is defensive against non-list
slot_order / hidden_slots leaking through (treats as empty, no raise).
All tests are pure-Python — no filesystem, no Selenium, no fixtures.
"""
from __future__ import annotations
from src.structure_override_resolver import (
InvalidStructureOverride,
apply_structure_override,
validate_structure_overrides,
)
# -- module surface ---------------------------------------------------------
def test_invalid_structure_override_is_value_error_subclass():
# Reserved future strict-mode exception — kept as a public surface so
# u7 / strict callers can branch on source-malformation vs stale-DOM.
assert issubclass(InvalidStructureOverride, ValueError)
# -- validate_structure_overrides ------------------------------------------
def test_validate_structure_overrides_non_mapping_returns_empty():
for bad_input in [None, [], "string", 42, 1.5]:
assert validate_structure_overrides(bad_input) == {}
def test_validate_structure_overrides_passes_well_formed():
payload = {
"zone-top": {
"slot_order": ["slot_title", "slot_body"],
"hidden_slots": ["slot_caption"],
},
"zone-bottom": {"slot_order": ["slot_a", "slot_b"]},
"zone-only-hide": {"hidden_slots": ["slot_x"]},
}
out = validate_structure_overrides(payload)
assert out == {
"zone-top": {
"slot_order": ["slot_title", "slot_body"],
"hidden_slots": ["slot_caption"],
},
"zone-bottom": {"slot_order": ["slot_a", "slot_b"]},
"zone-only-hide": {"hidden_slots": ["slot_x"]},
}
assert out is not payload # fresh dict
def test_validate_structure_overrides_per_entry_tolerance():
payload = {
"zone-top": {
"slot_order": ["slot_title", "slot_body"],
"hidden_slots": ["slot_caption"],
},
"": {"slot_order": ["x"]}, # empty zone_id dropped
42: {"slot_order": ["y"]}, # non-string zone_id dropped
"zone-non-mapping": "not a dict", # non-mapping payload dropped
"zone-bottom": {"hidden_slots": ["slot_aux"]},
}
out = validate_structure_overrides(payload)
assert out == {
"zone-top": {
"slot_order": ["slot_title", "slot_body"],
"hidden_slots": ["slot_caption"],
},
"zone-bottom": {"hidden_slots": ["slot_aux"]},
}
def test_validate_structure_overrides_rejects_frame_swap_inner_keys():
# SCOPE LOCK — frame swap attempts MUST be silently dropped. The only
# mechanism for swapping a frame is the existing ``frames`` axis; this
# resolver intentionally has no escape hatch so the Phase Z
# no-AI-HTML-structure invariant stays intact.
payload = {
"zone-top": {
"slot_order": ["slot_title"],
# The following 4 keys are all frame-swap / DOM-rebuild
# attempts and MUST be dropped by validate.
"frame_id": "compare_v2",
"template_id": "topic_left_right",
"unit_id": "03-1+03-2",
"slot_payload": {"injected_slot": ["unsafe"]},
},
}
out = validate_structure_overrides(payload)
assert out == {"zone-top": {"slot_order": ["slot_title"]}}
def test_validate_structure_overrides_rejects_frame_swap_zone_with_no_lock_keys():
# If a per-zone payload contains ONLY frame-swap attempts (no
# slot_order / hidden_slots), the whole zone gets dropped after
# sanitization (no signal remains).
payload = {
"zone-attempt-swap": {
"frame_id": "compare_v2",
"template_id": "topic_left_right",
},
}
out = validate_structure_overrides(payload)
assert out == {}
def test_validate_structure_overrides_drops_non_list_slot_arrays():
payload = {
"zone-top": {
"slot_order": "not a list",
"hidden_slots": {"also": "not a list"},
},
"zone-bottom": {
"slot_order": 42,
"hidden_slots": None,
},
"zone-good": {"slot_order": ["slot_title"]},
}
out = validate_structure_overrides(payload)
assert out == {"zone-good": {"slot_order": ["slot_title"]}}
def test_validate_structure_overrides_drops_bad_slot_key_entries():
payload = {
"zone-top": {
"slot_order": [
"good_slot",
"", # empty string dropped
42, # non-string dropped
None, # non-string dropped
{"nested": "obj"}, # non-string dropped
"another_good",
],
"hidden_slots": ["", "valid_hide", 99, "valid_hide_2"],
},
}
out = validate_structure_overrides(payload)
assert out == {
"zone-top": {
"slot_order": ["good_slot", "another_good"],
"hidden_slots": ["valid_hide", "valid_hide_2"],
},
}
def test_validate_structure_overrides_dedupes_slot_key_entries():
payload = {
"zone-top": {
"slot_order": ["a", "b", "a", "c", "b"],
"hidden_slots": ["x", "x", "y", "x"],
},
}
out = validate_structure_overrides(payload)
assert out == {
"zone-top": {
"slot_order": ["a", "b", "c"],
"hidden_slots": ["x", "y"],
},
}
def test_validate_structure_overrides_drops_empty_payload_after_sanitization():
# Per-zone payloads that have empty slot_order AND empty hidden_slots
# after sanitization carry no signal → drop the zone entirely.
payload = {
"zone-empty-lists": {"slot_order": [], "hidden_slots": []},
"zone-only-bad-entries": {"slot_order": ["", None, 99]},
"zone-good": {"slot_order": ["slot_title"]},
}
out = validate_structure_overrides(payload)
assert out == {"zone-good": {"slot_order": ["slot_title"]}}
def test_validate_structure_overrides_returns_fresh_nested_dicts_and_lists():
# Mutating the returned dict's per-zone payload (or any list inside)
# must not leak back into the source.
payload = {
"zone-top": {
"slot_order": ["a", "b"],
"hidden_slots": ["x"],
},
}
out = validate_structure_overrides(payload)
out["zone-top"]["slot_order"].append("mutated")
out["zone-top"]["hidden_slots"].append("mutated_hide")
out["zone-top"]["new_key"] = "leaked?"
assert payload["zone-top"]["slot_order"] == ["a", "b"]
assert payload["zone-top"]["hidden_slots"] == ["x"]
assert "new_key" not in payload["zone-top"]
# -- apply_structure_override ----------------------------------------------
def test_apply_structure_override_hide_only_mutates_in_place():
zone: dict = {
"slot_title": ["title line"],
"slot_body": ["body line"],
"slot_caption": ["caption"],
}
assert apply_structure_override(zone, {"hidden_slots": ["slot_caption"]}) is True
assert list(zone.keys()) == ["slot_title", "slot_body"]
assert zone == {
"slot_title": ["title line"],
"slot_body": ["body line"],
}
def test_apply_structure_override_reorder_only_partial():
# Partial reorder — listed keys move to front in order; unmentioned
# keys keep their original relative order at the tail.
zone: dict = {
"slot_title": ["t"],
"slot_body": ["b"],
"slot_caption": ["c"],
"slot_aux": ["a"],
}
assert apply_structure_override(zone, {"slot_order": ["slot_aux", "slot_title"]}) is True
assert list(zone.keys()) == ["slot_aux", "slot_title", "slot_body", "slot_caption"]
def test_apply_structure_override_combines_hide_and_reorder():
zone: dict = {
"slot_title": ["t"],
"slot_body": ["b"],
"slot_caption": ["c"],
"slot_aux": ["a"],
}
override = {
"slot_order": ["slot_aux", "slot_body"],
"hidden_slots": ["slot_caption"],
}
assert apply_structure_override(zone, override) is True
# slot_caption hidden; slot_aux + slot_body moved to front; remaining
# (slot_title) appended at tail in original order.
assert list(zone.keys()) == ["slot_aux", "slot_body", "slot_title"]
def test_apply_structure_override_silently_skips_stale_slot_keys():
# Frame swap / layout regression — the prior render's override
# references slot_keys that the new render's frame no longer emits.
# The resolver must silently skip those without raising.
zone: dict = {"slot_title": ["t"], "slot_body": ["b"]}
override = {
"slot_order": ["slot_phantom_1", "slot_body", "slot_phantom_2"],
"hidden_slots": ["slot_does_not_exist"],
}
assert apply_structure_override(zone, override) is True
# slot_body moves to front; slot_title appended at tail; phantoms
# silently ignored; hidden_slots no-op.
assert list(zone.keys()) == ["slot_body", "slot_title"]
assert zone == {"slot_body": ["b"], "slot_title": ["t"]}
def test_apply_structure_override_no_op_returns_false():
# Empty override → no mutation.
zone: dict = {"slot_title": ["t"], "slot_body": ["b"]}
snapshot = dict(zone)
assert apply_structure_override(zone, {}) is False
assert zone == snapshot
assert list(zone.keys()) == ["slot_title", "slot_body"]
def test_apply_structure_override_all_stale_returns_false():
# All slot_keys in the override are absent from zone → no mutation.
zone: dict = {"slot_title": ["t"], "slot_body": ["b"]}
snapshot = dict(zone)
override = {
"slot_order": ["phantom_a", "phantom_b"],
"hidden_slots": ["phantom_c"],
}
assert apply_structure_override(zone, override) is False
assert zone == snapshot
assert list(zone.keys()) == ["slot_title", "slot_body"]
def test_apply_structure_override_already_in_desired_order_returns_false():
# slot_order matches the existing key order exactly → no mutation.
zone: dict = {"slot_title": ["t"], "slot_body": ["b"]}
override = {"slot_order": ["slot_title", "slot_body"]}
assert apply_structure_override(zone, override) is False
assert list(zone.keys()) == ["slot_title", "slot_body"]
def test_apply_structure_override_preserves_zone_reference_identity():
# In-place mutation via clear + update — caller's reference must
# remain valid after reorder.
zone: dict = {"slot_a": ["a"], "slot_b": ["b"], "slot_c": ["c"]}
zone_ref = zone # capture reference
apply_structure_override(zone, {"slot_order": ["slot_c", "slot_a"]})
assert zone_ref is zone
assert list(zone.keys()) == ["slot_c", "slot_a", "slot_b"]
def test_apply_structure_override_never_inspects_per_slot_values():
# The resolver MUST NOT inspect / mutate per-slot list[str] contents.
# Use weird non-list values to confirm passthrough.
zone: dict = {
"slot_a": ["a1", "a2", "a3"],
"slot_b": {"nested": "object"}, # non-list payload — passthrough
"slot_c": None, # None payload — passthrough
"slot_d": 42, # int payload — passthrough
}
snapshot = {k: zone[k] for k in zone}
apply_structure_override(zone, {"slot_order": ["slot_d", "slot_a"]})
assert list(zone.keys()) == ["slot_d", "slot_a", "slot_b", "slot_c"]
# values are identity-preserved
for key in zone:
assert zone[key] is snapshot[key]
def test_apply_structure_override_defensive_on_non_list_arrays():
# If a non-validated override leaks through, non-list slot_order /
# hidden_slots should be treated as empty rather than raising.
zone: dict = {"slot_title": ["t"], "slot_body": ["b"]}
snapshot = dict(zone)
override = {
"slot_order": "not a list",
"hidden_slots": {"also": "not a list"},
}
assert apply_structure_override(zone, override) is False
assert zone == snapshot
assert list(zone.keys()) == ["slot_title", "slot_body"]
def test_apply_structure_override_hide_wins_over_reorder():
# Edge case: a slot_key appears in BOTH slot_order and hidden_slots.
# hidden_slots is applied first, so the slot is gone by the time
# reorder runs — the reorder entry silently no-ops.
zone: dict = {"slot_a": ["a"], "slot_b": ["b"], "slot_c": ["c"]}
override = {
"slot_order": ["slot_b", "slot_a", "slot_c"],
"hidden_slots": ["slot_b"],
}
assert apply_structure_override(zone, override) is True
# slot_b removed first; slot_a + slot_c reordered to front (slot_b
# silently skipped because it no longer exists).
assert list(zone.keys()) == ["slot_a", "slot_c"]
def test_apply_structure_override_returns_true_on_pure_reorder_only():
# Pure reorder (no hide) — should still return True when key order
# actually changes.
zone: dict = {"slot_a": ["a"], "slot_b": ["b"]}
override = {"slot_order": ["slot_b", "slot_a"]}
assert apply_structure_override(zone, override) is True
assert list(zone.keys()) == ["slot_b", "slot_a"]
def test_apply_structure_override_returns_true_on_pure_hide_only():
# Pure hide (no reorder) — should still return True when a key was
# actually removed.
zone: dict = {"slot_a": ["a"], "slot_b": ["b"]}
override = {"hidden_slots": ["slot_a"]}
assert apply_structure_override(zone, override) is True
assert list(zone.keys()) == ["slot_b"]