fix(web): define project_root when plugins_directory is absolute (#616)

project_root was only assigned in the relative-path branch, so an absolute
plugin_system.plugins_directory made web_interface/app.py raise NameError
at import (first use: the SchemaManager construction). Define it before the
if/else; plugins_dir resolution is unchanged.

Adds a regression test that imports the real module in a fresh interpreter
with an absolute and a relative plugins_directory.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-23 12:54:29 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 9d024f24ef
commit f813ea2117
2 changed files with 70 additions and 2 deletions
@@ -0,0 +1,65 @@
"""
web_interface/app.py must start with an absolute ``plugins_directory``.
``project_root`` used to be assigned only in the relative-path branch, so an
absolute path in config.json raised NameError at module import (the
SchemaManager construction was the first use). Each case imports the real
module in a fresh interpreter, since app.py does its setup at import time and
the test process may already hold a copy built from the real config.
"""
import json
import subprocess
import sys
import textwrap
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
_IMPORT_APP = textwrap.dedent(
"""
import json, sys
from pathlib import Path
sys.path.insert(0, {root!r})
from src.config_manager import ConfigManager
ConfigManager.load_config = lambda self: {config!r}
import web_interface.app as web_app
print(json.dumps({{
"plugins_dir": str(web_app.plugins_dir),
"project_root": str(web_app.project_root),
"schema_project_root": str(web_app.schema_manager.project_root),
}}))
"""
)
def _import_app_with(plugins_directory):
config = {"plugin_system": {"plugins_directory": plugins_directory}}
script = _IMPORT_APP.format(root=str(PROJECT_ROOT), config=config)
result = subprocess.run(
[sys.executable, "-c", script],
cwd=str(PROJECT_ROOT),
capture_output=True,
text=True,
timeout=120,
)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout.strip().splitlines()[-1])
def test_absolute_plugins_directory_imports(tmp_path):
plugins = tmp_path / "my-plugins"
plugins.mkdir()
info = _import_app_with(str(plugins))
assert Path(info["plugins_dir"]) == plugins
assert Path(info["project_root"]) == PROJECT_ROOT
assert Path(info["schema_project_root"]) == PROJECT_ROOT
def test_relative_plugins_directory_resolves_under_project_root():
info = _import_app_with("plugin-repos")
assert Path(info["plugins_dir"]) == PROJECT_ROOT / "plugin-repos"
assert Path(info["project_root"]) == PROJECT_ROOT
+5 -2
View File
@@ -89,12 +89,15 @@ config = config_manager.load_config()
plugin_system_config = config.get('plugin_system', {})
plugins_dir_name = plugin_system_config.get('plugins_directory', 'plugin-repos')
# Project root (LEDMatrix directory). Needed below for data/ and assets/ paths
# whether or not the plugins directory is absolute.
project_root = Path(__file__).parent.parent
# Resolve plugin directory - handle both absolute and relative paths
if os.path.isabs(plugins_dir_name):
plugins_dir = Path(plugins_dir_name)
else:
# If relative, resolve relative to the project root (LEDMatrix directory)
project_root = Path(__file__).parent.parent
# If relative, resolve relative to the project root
plugins_dir = project_root / plugins_dir_name
plugin_manager = PluginManager(