From ee5df2a3217964e8f9c2b48120518ac26f21bc0b Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Sun, 12 Jul 2026 20:17:18 -0400 Subject: [PATCH] fix(testing): add MockCacheManager.get_cached_data_with_strategy/save_cache ledmatrix-leaderboard's data_fetcher.py calls these two real-CacheManager methods (src/cache_manager.py:313,817), but MockCacheManager had neither -- update() always hit an AttributeError, caught by a broad except and logged, so the harness rendered an empty-but-green leaderboard on every test run without ever exercising real standings data. Both mocks delegate to the existing get()/set() -- a mock doesn't need the real strategy's per-data-type max_age/market-hours timing, plugins under test just need the methods to exist and round-trip whatever was cached. --- src/plugin_system/testing/mocks.py | 19 +++++++++++++++ test/test_testing_mocks.py | 39 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/test_testing_mocks.py diff --git a/src/plugin_system/testing/mocks.py b/src/plugin_system/testing/mocks.py index 2bbc9bcc..cbe36b31 100644 --- a/src/plugin_system/testing/mocks.py +++ b/src/plugin_system/testing/mocks.py @@ -71,6 +71,7 @@ class MockCacheManager: self.get_calls = [] self.set_calls = [] self.delete_calls = [] + self.get_cached_data_with_strategy_calls = [] # Real temp dir for plugins that write/read files under cache_dir. # Registered for cleanup so each mock instance doesn't leak a tmp dir. self.cache_dir = tempfile.mkdtemp(prefix="ledmatrix-mock-cache-") @@ -108,6 +109,24 @@ class MockCacheManager: self.delete_calls.append(key) if key in self._cache: del self._cache[key] + + def get_cached_data_with_strategy(self, key: str, data_type: str = 'default') -> Optional[Any]: + """Mock of CacheManager.get_cached_data_with_strategy (src/cache_manager.py). + + The real method picks a max_age/memory_ttl strategy per data_type + (and extends it during market-closed hours for market data) before + delegating to get_cached_data(). None of that timing nuance matters + for a mock -- plugins under test just need the method to exist and + return whatever was cached, so this delegates straight to get(). + """ + self.get_cached_data_with_strategy_calls.append({'key': key, 'data_type': data_type}) + return self.get(key) + + def save_cache(self, key: str, data: Any) -> None: + """Mock of CacheManager.save_cache (src/cache_manager.py) -- the + write-side counterpart to get_cached_data_with_strategy, used by the + same real-CacheManager-oriented plugins. Delegates to set().""" + self.set(key, data) if key in self._cache_timestamps: del self._cache_timestamps[key] diff --git a/test/test_testing_mocks.py b/test/test_testing_mocks.py new file mode 100644 index 00000000..4505eeec --- /dev/null +++ b/test/test_testing_mocks.py @@ -0,0 +1,39 @@ +""" +Unit tests for src/plugin_system/testing/mocks.py. + +MockCacheManager/MockPluginManager stand in for the real production +managers under the plugin safety harness -- a missing method here isn't a +harness bug in the abstract, it's a plugin silently failing to render +under test (confirmed on ledmatrix-leaderboard, which calls +get_cached_data_with_strategy() and previously hit an AttributeError that +its own broad except swallowed, producing an empty-but-green render). +""" + +from src.plugin_system.testing.mocks import MockCacheManager + + +class TestMockCacheManagerStrategyMethod: + def test_get_cached_data_with_strategy_returns_cached_value(self): + cm = MockCacheManager() + cm.set("standings_nfl", {"teams": ["KC", "BUF"]}) + result = cm.get_cached_data_with_strategy("standings_nfl", "sports_live") + assert result == {"teams": ["KC", "BUF"]} + + def test_get_cached_data_with_strategy_returns_none_when_missing(self): + cm = MockCacheManager() + assert cm.get_cached_data_with_strategy("missing_key") is None + + def test_get_cached_data_with_strategy_defaults_data_type(self): + cm = MockCacheManager() + cm.set("k", "v") + assert cm.get_cached_data_with_strategy("k") == "v" + + def test_calls_are_tracked(self): + cm = MockCacheManager() + cm.get_cached_data_with_strategy("k", "sports_live") + assert cm.get_cached_data_with_strategy_calls == [{"key": "k", "data_type": "sports_live"}] + + def test_save_cache_is_readable_via_strategy_lookup(self): + cm = MockCacheManager() + cm.save_cache("standings_nfl", {"teams": ["KC", "BUF"]}) + assert cm.get_cached_data_with_strategy("standings_nfl") == {"teams": ["KC", "BUF"]}