mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 23:05:10 +00:00
fix(plugins): address review -- no manifest fallback for live plugins, keep mode names whole, send registered spelling
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
6dbec2e0b1
commit
b440a27cdb
@@ -244,6 +244,8 @@ class PluginCatalog:
|
|||||||
if live and any(m.lower() == wanted for m in live):
|
if live and any(m.lower() == wanted for m in live):
|
||||||
return plugin_id
|
return plugin_id
|
||||||
for plugin_id, manifest in manifests.items():
|
for plugin_id, manifest in manifests.items():
|
||||||
|
if self._live_display_modes(plugin_id):
|
||||||
|
continue # the display's list is the truth for this plugin
|
||||||
modes = manifest.get('display_modes')
|
modes = manifest.get('display_modes')
|
||||||
if isinstance(modes, list) and any(
|
if isinstance(modes, list) and any(
|
||||||
isinstance(m, str) and m.lower() == wanted for m in modes):
|
isinstance(m, str) and m.lower() == wanted for m in modes):
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ def _published_modes(modes: Any) -> Optional[List[str]]:
|
|||||||
"""The registered display modes as a snapshot carries them, or None."""
|
"""The registered display modes as a snapshot carries them, or None."""
|
||||||
if not isinstance(modes, list):
|
if not isinstance(modes, list):
|
||||||
return None
|
return None
|
||||||
return [_clip(m, _ID_CHARS) for m in modes[:_MAX_MODES] if isinstance(m, str)]
|
# A name is a key the display matches exactly: drop one too long to
|
||||||
|
# carry whole rather than clip it into a different name.
|
||||||
|
return [m for m in modes if isinstance(m, str) and len(m) <= _ID_CHARS][:_MAX_MODES]
|
||||||
|
|
||||||
|
|
||||||
def build_runtime_snapshot(state_manager: Any, *, started_at: float,
|
def build_runtime_snapshot(state_manager: Any, *, started_at: float,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ manifest.json off disk and reimplemented PluginManager's own fallbacks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -155,3 +155,14 @@ class TestOneBadConfigSectionDoesNotBlankTheList:
|
|||||||
side_effect=RuntimeError("GET https://x/y?api_key=SEC123 failed"))
|
side_effect=RuntimeError("GET https://x/y?api_key=SEC123 failed"))
|
||||||
body = api_v3_client.get('/api/v3/display/modes').get_json()
|
body = api_v3_client.get('/api/v3/display/modes').get_json()
|
||||||
assert 'SEC123' not in json.dumps(body)
|
assert 'SEC123' not in json.dumps(body)
|
||||||
|
|
||||||
|
|
||||||
|
class TestOnDemandUsesTheRegisteredSpelling:
|
||||||
|
def test_a_mode_differing_in_case_is_sent_as_registered(self, client):
|
||||||
|
with patch('web_interface.blueprints.api_v3.display._deliver_on_demand',
|
||||||
|
return_value=('socket', None)) as deliver:
|
||||||
|
response = client.post('/api/v3/display/on-demand/start',
|
||||||
|
json={'plugin_id': 'football-scoreboard',
|
||||||
|
'mode': 'NFL_LIVE', 'start_service': False})
|
||||||
|
assert response.status_code == 200, response.get_json()
|
||||||
|
assert deliver.call_args.args[0]['mode'] == 'nfl_live'
|
||||||
|
|||||||
@@ -165,6 +165,12 @@ class TestSnapshotAndView:
|
|||||||
published = snapshot["plugins"]["soccer-scoreboard"]["modes"]
|
published = snapshot["plugins"]["soccer-scoreboard"]["modes"]
|
||||||
assert len(published) == rt._MAX_MODES
|
assert len(published) == rt._MAX_MODES
|
||||||
|
|
||||||
|
def test_a_mode_name_is_kept_whole_or_dropped(self):
|
||||||
|
long_mode = "x" * (rt._ID_CHARS + 1)
|
||||||
|
snapshot = build_runtime_snapshot(_loaded_states(["ok", long_mode]),
|
||||||
|
started_at=1.0, now=self.NOW)
|
||||||
|
assert snapshot["plugins"]["soccer-scoreboard"]["modes"] == ["ok"]
|
||||||
|
|
||||||
def test_non_strings_from_a_hand_made_snapshot_are_dropped(self):
|
def test_non_strings_from_a_hand_made_snapshot_are_dropped(self):
|
||||||
snapshot = {"schema": rt.SNAPSHOT_SCHEMA, "running": True,
|
snapshot = {"schema": rt.SNAPSHOT_SCHEMA, "running": True,
|
||||||
"published_at": self.NOW, "plugins": {
|
"published_at": self.NOW, "plugins": {
|
||||||
@@ -202,6 +208,13 @@ class TestCatalog:
|
|||||||
assert catalog.get_plugin_display_modes("clock-simple") == ["clock"]
|
assert catalog.get_plugin_display_modes("clock-simple") == ["clock"]
|
||||||
assert catalog.find_plugin_for_mode("clock") == "clock-simple"
|
assert catalog.find_plugin_for_mode("clock") == "clock-simple"
|
||||||
|
|
||||||
|
def test_a_mode_the_display_dropped_does_not_resolve_by_manifest(
|
||||||
|
self, plugins_dir, shared_cache):
|
||||||
|
display_cache, web_cache = shared_cache
|
||||||
|
PluginRuntimePublisher(display_cache, _loaded_states(CUSTOM)).tick()
|
||||||
|
catalog = self._catalog(plugins_dir, web_cache)
|
||||||
|
assert catalog.find_plugin_for_mode("soccer_eng.1_live") is None
|
||||||
|
|
||||||
def test_a_stopped_display_falls_back_to_manifests(self, plugins_dir, shared_cache):
|
def test_a_stopped_display_falls_back_to_manifests(self, plugins_dir, shared_cache):
|
||||||
display_cache, web_cache = shared_cache
|
display_cache, web_cache = shared_cache
|
||||||
publisher = PluginRuntimePublisher(display_cache, _loaded_states(REGISTERED))
|
publisher = PluginRuntimePublisher(display_cache, _loaded_states(REGISTERED))
|
||||||
|
|||||||
@@ -279,6 +279,15 @@ def start_on_demand_display():
|
|||||||
if not resolved_plugin:
|
if not resolved_plugin:
|
||||||
return jsonify({'status': 'error', 'message': f'Mode {resolved_mode} not found'}), 404
|
return jsonify({'status': 'error', 'message': f'Mode {resolved_mode} not found'}), 404
|
||||||
|
|
||||||
|
# The display matches mode names exactly: pass the registered spelling
|
||||||
|
# when the caller's differs only in case.
|
||||||
|
if api_v3.plugin_catalog and resolved_plugin and resolved_mode:
|
||||||
|
wanted = resolved_mode.strip().lower()
|
||||||
|
for registered in api_v3.plugin_catalog.get_plugin_display_modes(resolved_plugin):
|
||||||
|
if isinstance(registered, str) and registered.lower() == wanted:
|
||||||
|
resolved_mode = registered
|
||||||
|
break
|
||||||
|
|
||||||
# On-demand works with disabled plugins: the running display loads one
|
# On-demand works with disabled plugins: the running display loads one
|
||||||
# for the session and unloads it afterwards, leaving config.json alone
|
# for the session and unloads it afterwards, leaving config.json alone
|
||||||
# (DisplayController._load_plugin_for_on_demand). Logged for debugging.
|
# (DisplayController._load_plugin_for_on_demand). Logged for debugging.
|
||||||
|
|||||||
Reference in New Issue
Block a user