diff --git a/test/web_interface/test_app_plugins_dir_resolution.py b/test/web_interface/test_app_plugins_dir_resolution.py new file mode 100644 index 00000000..96db9bba --- /dev/null +++ b/test/web_interface/test_app_plugins_dir_resolution.py @@ -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 diff --git a/web_interface/app.py b/web_interface/app.py index 72092bae..8453bc13 100644 --- a/web_interface/app.py +++ b/web_interface/app.py @@ -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(