mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-07 03:38:06 +00:00
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
111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
"""
|
|
Tests for BasePlugin.get_display_duration — ~100 lines of type coercion that
|
|
every plugin's rotation slot depends on, previously untested.
|
|
|
|
The contract: a positive number wins wherever it comes from; everything else
|
|
falls through instance attr → config → the 15.0 default, logging on the way.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from src.plugin_system.base_plugin import BasePlugin
|
|
|
|
|
|
class _MinimalPlugin(BasePlugin):
|
|
def update(self):
|
|
pass
|
|
|
|
def display(self, force_clear=False):
|
|
pass
|
|
|
|
|
|
def make_plugin(config=None, instance_duration="__unset__"):
|
|
plugin = _MinimalPlugin(
|
|
plugin_id="duration-test",
|
|
config=config or {},
|
|
display_manager=MagicMock(),
|
|
cache_manager=MagicMock(),
|
|
plugin_manager=MagicMock(),
|
|
)
|
|
if instance_duration != "__unset__":
|
|
plugin.display_duration = instance_duration
|
|
return plugin
|
|
|
|
|
|
class TestInstanceVariable:
|
|
def test_positive_int_wins(self):
|
|
assert make_plugin(instance_duration=30).get_display_duration() == 30.0
|
|
|
|
def test_positive_float_wins(self):
|
|
assert make_plugin(instance_duration=12.5).get_display_duration() == 12.5
|
|
|
|
def test_returns_float_type(self):
|
|
result = make_plugin(instance_duration=30).get_display_duration()
|
|
assert isinstance(result, float)
|
|
|
|
def test_numeric_string_wins(self):
|
|
assert make_plugin(instance_duration="25").get_display_duration() == 25.0
|
|
|
|
def test_zero_falls_through_to_config(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration=0)
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_negative_falls_through_to_config(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration=-5)
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_none_falls_through_to_config(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration=None)
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_garbage_string_falls_through(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration="abc")
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_non_positive_string_falls_through(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration="0")
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_unexpected_type_falls_through(self):
|
|
plugin = make_plugin(config={"display_duration": 20},
|
|
instance_duration=[30])
|
|
assert plugin.get_display_duration() == 20.0
|
|
|
|
def test_bool_true_is_one_second(self):
|
|
# Characterized quirk: bool is an int subclass, so display_duration =
|
|
# True passes the isinstance((int, float)) branch and returns 1.0.
|
|
assert make_plugin(instance_duration=True).get_display_duration() == 1.0
|
|
|
|
|
|
class TestConfigFallback:
|
|
def test_config_number(self):
|
|
assert make_plugin({"display_duration": 20}).get_display_duration() == 20.0
|
|
|
|
def test_config_numeric_string(self):
|
|
assert make_plugin({"display_duration": "12.5"}).get_display_duration() == 12.5
|
|
|
|
def test_missing_config_uses_default(self):
|
|
assert make_plugin({}).get_display_duration() == 15.0
|
|
|
|
def test_config_zero_uses_default(self):
|
|
assert make_plugin({"display_duration": 0}).get_display_duration() == 15.0
|
|
|
|
def test_config_negative_uses_default(self):
|
|
assert make_plugin({"display_duration": -10}).get_display_duration() == 15.0
|
|
|
|
def test_config_garbage_string_uses_default(self):
|
|
assert make_plugin({"display_duration": "soon"}).get_display_duration() == 15.0
|
|
|
|
def test_config_unexpected_type_uses_default(self):
|
|
assert make_plugin({"display_duration": {"s": 5}}).get_display_duration() == 15.0
|
|
|
|
def test_config_none_uses_default(self):
|
|
assert make_plugin({"display_duration": None}).get_display_duration() == 15.0
|