mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-02 17:28:05 +00:00
Merge branch 'claude/sports-unification-safety-net' into claude/sports-unification-phase1
This commit is contained in:
@@ -106,6 +106,13 @@ def resolve_font_path(font_name: str) -> Optional[str]:
|
||||
return None
|
||||
if os.path.isabs(font_name):
|
||||
return font_name if os.path.isfile(font_name) else None
|
||||
# A relative name must be a bare filename. font_name comes from plugin
|
||||
# config, which the web UI writes; a value like "../../config/config.json"
|
||||
# would otherwise escape assets/fonts/ once joined and let a config probe
|
||||
# arbitrary paths for existence. os.path.basename collapses any such value
|
||||
# to its last component, so a name that isn't already bare is rejected.
|
||||
if os.path.basename(font_name) != font_name:
|
||||
return None
|
||||
candidates = (
|
||||
os.path.join(os.getcwd(), _FONTS_SUBDIR, font_name),
|
||||
os.path.join(_CORE_ROOT, _FONTS_SUBDIR, font_name),
|
||||
|
||||
@@ -377,6 +377,22 @@ class TestResolverPlumbing:
|
||||
font = load_font("5x7.bdf", 7)
|
||||
assert isinstance(font, freetype.Face)
|
||||
|
||||
@pytest.mark.parametrize("hostile", [
|
||||
"../../config/config.json",
|
||||
"../secrets.txt",
|
||||
"sub/dir/font.ttf",
|
||||
"..",
|
||||
])
|
||||
def test_relative_font_name_with_path_components_is_rejected(self, hostile):
|
||||
# font_name comes from plugin config (web-UI writable); a relative name
|
||||
# carrying path separators would escape assets/fonts/ after os.path.join
|
||||
# and let a config probe arbitrary paths. Only bare filenames resolve.
|
||||
assert resolve_font_path(hostile) is None
|
||||
|
||||
def test_bare_filename_still_resolves(self):
|
||||
# The guard must not reject legitimate bare names.
|
||||
assert resolve_font_path("PressStart2P-Regular.ttf") is not None
|
||||
|
||||
def test_schema_manager_expands_on_load(self, tmp_path):
|
||||
# The web-UI form path: SchemaManager.load_schema serves the
|
||||
# expanded schema so the style blocks actually appear in the UI.
|
||||
|
||||
@@ -434,6 +434,17 @@ def build_manager(monkeypatch, tmp_path):
|
||||
"src.base_classes.sports.core.get_background_service",
|
||||
lambda *args, **kwargs: MagicMock())
|
||||
|
||||
# Rig requests.Session BEFORE any manager is built. Construction creates
|
||||
# both SportsCore.session and the ESPNDataSource.session; replacing only
|
||||
# manager.session after the fact (below) leaves data_source.session real,
|
||||
# so an accidental fetch during or right after construction could reach
|
||||
# the network. Patching the class makes every session created here raise.
|
||||
def _offline_get(*args, **kwargs):
|
||||
raise requests.exceptions.ConnectionError(
|
||||
"characterization tests are offline")
|
||||
|
||||
monkeypatch.setattr(requests.Session, "get", _offline_get)
|
||||
|
||||
def build(cls, schedule, **mode_cfg):
|
||||
config = {
|
||||
"timezone": "UTC",
|
||||
|
||||
Reference in New Issue
Block a user