mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-07 11:48:06 +00:00
test: cover the untested fragile logic (compatibility gate, secrets, config merges, durations, skin cards)
New unit tests for pure or filesystem-only logic that previously had zero direct coverage: - test_compatibility.py: the semver install gate (parse_semver suffix handling, every range operator, TRUSTWORTHY_FLOOR behavior for cores reporting untrustworthy versions, 'more restrictive wins', and the malformed-manifest shapes that used to raise). - test/web_interface/test_secret_helpers.py: the canonical x-secret helpers — find/separate/mask/remove, array-item secrets, no input mutation, and a separate->recombine round-trip. - test/web_interface/test_api_v3_helpers.py: the module-level helpers behind the plugin config save endpoint (_is_plugin_update_available, _coerce_to_bool including the int==1 quirk, deep_merge including its shared-subtree shallowness, _parse_form_value, dotted-key-aware _get_schema_property/_set_nested_value). - test_base_plugin_duration.py: get_display_duration's full coercion ladder (instance attr -> config -> 15.0), including the bool-is-int quirk where display_duration=True means one second. - test_config_manager_secrets.py: the secrets round-trip — deep-merge on load, strip on save, group pruning, the load fast path — and two characterized sharp edges marked SUSPECTED BUG: an unreadable secrets file at save time writes secrets into config.json in plaintext, and a same-mtime-same-size content swap is served stale. - test_schema_manager_merge.py: merge_with_defaults branch behavior (None replacement vs falsey preservation, dict-vs-scalar mismatches, arrays replaced wholesale, defaults never mutated). - test_skin_system.py (extended): render_skin_card shares _render_game's 3-strike counter but never resets it on success — the asymmetry is pinned in both directions, along with card fallthrough and the disable interaction between the two paths. Suspected bugs are characterized, not fixed — each carries a comment so a future behavior change is deliberate rather than accidental. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NohXi78cwsAKtN1sCfxjUh
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Tests for SchemaManager.merge_with_defaults — the merge every plugin config
|
||||
passes through at load time (schema defaults + user config, with None
|
||||
replacement). A regression here silently changes every plugin's effective
|
||||
config, so the exact branch behavior is pinned, including the
|
||||
characterized type-mismatch cases.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from src.plugin_system.schema_manager import SchemaManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sm(tmp_path):
|
||||
return SchemaManager(plugins_dir=str(tmp_path))
|
||||
|
||||
|
||||
class TestBasicMerge:
|
||||
def test_missing_keys_filled_from_defaults(self, sm):
|
||||
merged = sm.merge_with_defaults(
|
||||
{"city": "Austin"}, {"city": "NYC", "units": "metric"})
|
||||
assert merged == {"city": "Austin", "units": "metric"}
|
||||
|
||||
def test_present_keys_preserved(self, sm):
|
||||
merged = sm.merge_with_defaults({"enabled": False}, {"enabled": True})
|
||||
assert merged["enabled"] is False
|
||||
|
||||
def test_nested_three_level_merge(self, sm):
|
||||
config = {"a": {"b": {"c": 1}}}
|
||||
defaults = {"a": {"b": {"c": 0, "d": 2}, "e": 3}}
|
||||
merged = sm.merge_with_defaults(config, defaults)
|
||||
assert merged == {"a": {"b": {"c": 1, "d": 2}, "e": 3}}
|
||||
|
||||
def test_inputs_not_mutated(self, sm):
|
||||
config = {"a": {"b": 1}}
|
||||
defaults = {"a": {"b": 0, "c": 2}, "d": 3}
|
||||
sm.merge_with_defaults(config, defaults)
|
||||
assert config == {"a": {"b": 1}}
|
||||
assert defaults == {"a": {"b": 0, "c": 2}, "d": 3}
|
||||
|
||||
def test_merged_values_are_copies_not_aliases(self, sm):
|
||||
config = {"teams": ["DAL"]}
|
||||
merged = sm.merge_with_defaults(config, {"teams": []})
|
||||
merged["teams"].append("HOU")
|
||||
assert config["teams"] == ["DAL"] # user's list untouched
|
||||
|
||||
|
||||
class TestNoneReplacement:
|
||||
def test_none_replaced_by_default(self, sm):
|
||||
merged = sm.merge_with_defaults({"units": None}, {"units": "metric"})
|
||||
assert merged["units"] == "metric"
|
||||
|
||||
def test_falsey_non_none_values_kept(self, sm):
|
||||
merged = sm.merge_with_defaults(
|
||||
{"enabled": False, "count": 0, "label": ""},
|
||||
{"enabled": True, "count": 5, "label": "x"},
|
||||
)
|
||||
assert merged == {"enabled": False, "count": 0, "label": ""}
|
||||
|
||||
def test_nested_none_replaced(self, sm):
|
||||
merged = sm.merge_with_defaults(
|
||||
{"style": {"color": None}}, {"style": {"color": "red"}})
|
||||
assert merged["style"]["color"] == "red"
|
||||
|
||||
def test_none_with_no_default_stays_none(self, sm):
|
||||
merged = sm.merge_with_defaults({"extra": None}, {})
|
||||
assert merged["extra"] is None
|
||||
|
||||
def test_none_replaced_by_dict_default_is_a_copy(self, sm):
|
||||
defaults = {"style": {"color": "red"}}
|
||||
merged = sm.merge_with_defaults({"style": None}, defaults)
|
||||
assert merged["style"] == {"color": "red"}
|
||||
merged["style"]["color"] = "blue"
|
||||
assert defaults["style"]["color"] == "red"
|
||||
|
||||
|
||||
class TestTypeMismatches:
|
||||
def test_user_scalar_over_dict_default_wins(self, sm):
|
||||
# Characterized: a scalar user value replaces a dict default outright.
|
||||
merged = sm.merge_with_defaults(
|
||||
{"style": "compact"}, {"style": {"color": "red"}})
|
||||
assert merged["style"] == "compact"
|
||||
|
||||
def test_user_dict_over_scalar_default_wins(self, sm):
|
||||
merged = sm.merge_with_defaults(
|
||||
{"style": {"color": "red"}}, {"style": "compact"})
|
||||
assert merged["style"] == {"color": "red"}
|
||||
|
||||
def test_arrays_replaced_wholesale_not_merged(self, sm):
|
||||
# Pinned contract: arrays never element-merge — the user's array is
|
||||
# the whole answer, even when shorter than the default.
|
||||
merged = sm.merge_with_defaults(
|
||||
{"teams": ["DAL"]}, {"teams": ["NYG", "PHI", "WAS"]})
|
||||
assert merged["teams"] == ["DAL"]
|
||||
|
||||
def test_empty_user_array_beats_default(self, sm):
|
||||
merged = sm.merge_with_defaults({"teams": []}, {"teams": ["NYG"]})
|
||||
assert merged["teams"] == []
|
||||
|
||||
def test_extra_user_keys_survive(self, sm):
|
||||
# Keys with no schema default pass through untouched.
|
||||
merged = sm.merge_with_defaults({"custom_flag": 7}, {"known": 1})
|
||||
assert merged == {"known": 1, "custom_flag": 7}
|
||||
Reference in New Issue
Block a user