mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-09 19:57:16 +02:00
feat(frontend): rename inj roles to sys-front-*, add sys-svc-cdn, cache-busting lookup
Introduce sys-svc-cdn (cdn_paths/cdn_urls/cdn_dirs) and ensure CDN directories + latest symlink. Rename sys-srv-web-inj-* → sys-front-inj-*; update includes/templates; serve shared/per-app CSS & JS via CDN. Add lookup_plugins/local_mtime_qs.py for mtime-based cache busting; split CSS into default.css/bootstrap.css + optional per-app style.css. CSP: use style-src-elem; drop unsafe-inline for styles. Services: fix SYS_SERVICE_ALL_ENABLED bool and controlled flush. BREAKING CHANGE: role names changed; replace includes and references accordingly. Conversation: https://chatgpt.com/share/68b55494-9ec4-800f-b559-44707029141d
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
# tests/unit/roles/srv-web-inj-compose/filter_plugins/test_inj_enabled.py
|
||||
import importlib.util
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
@@ -8,7 +7,7 @@ import unittest
|
||||
THIS_FILE = Path(__file__)
|
||||
|
||||
def find_repo_root(start: Path) -> Path:
|
||||
target_rel = Path("roles") / "sys-srv-web-inj-compose" / "filter_plugins" / "inj_enabled.py"
|
||||
target_rel = Path("roles") / "sys-front-inj-all" / "filter_plugins" / "inj_enabled.py"
|
||||
cur = start
|
||||
for _ in range(12):
|
||||
if (cur / target_rel).is_file():
|
||||
@@ -17,7 +16,7 @@ def find_repo_root(start: Path) -> Path:
|
||||
return start.parents[6]
|
||||
|
||||
REPO_ROOT = find_repo_root(THIS_FILE)
|
||||
PLUGIN_PATH = REPO_ROOT / "roles" / "sys-srv-web-inj-compose" / "filter_plugins" / "inj_enabled.py"
|
||||
PLUGIN_PATH = REPO_ROOT / "roles" / "sys-front-inj-all" / "filter_plugins" / "inj_enabled.py"
|
||||
|
||||
# Ensure 'module_utils' is importable under its canonical package name
|
||||
if str(REPO_ROOT) not in sys.path:
|
@@ -1,6 +1,6 @@
|
||||
# tests/unit/roles/sys-srv-web-inj-compose/filter_plugins/test_inj_snippets.py
|
||||
# tests/unit/roles/sys-front-inj-all/filter_plugins/test_inj_snippets.py
|
||||
"""
|
||||
Unit tests for roles/sys-srv-web-inj-compose/filter_plugins/inj_snippets.py
|
||||
Unit tests for roles/sys-front-inj-all/filter_plugins/inj_snippets.py
|
||||
|
||||
- Uses tempfile.TemporaryDirectory for an isolated roles/ tree.
|
||||
- Loads inj_snippets.py by absolute path (no sys.path issues).
|
||||
@@ -22,7 +22,7 @@ class TestInjSnippets(unittest.TestCase):
|
||||
cls.test_dir = os.path.dirname(__file__)
|
||||
root = cls.test_dir
|
||||
inj_rel = os.path.join(
|
||||
"roles", "sys-srv-web-inj-compose", "filter_plugins", "inj_snippets.py"
|
||||
"roles", "sys-front-inj-all", "filter_plugins", "inj_snippets.py"
|
||||
)
|
||||
|
||||
while True:
|
||||
@@ -67,7 +67,7 @@ class TestInjSnippets(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def _mkrole(cls, feature, head=False, body=False):
|
||||
role_dir = os.path.join(cls.roles_dir, f"sys-srv-web-inj-{feature}")
|
||||
role_dir = os.path.join(cls.roles_dir, f"sys-front-inj-{feature}")
|
||||
tmpl_dir = os.path.join(role_dir, "templates")
|
||||
os.makedirs(tmpl_dir, exist_ok=True)
|
||||
if head:
|
0
tests/unit/roles/sys-svc-cdn/__init__.py
Normal file
0
tests/unit/roles/sys-svc-cdn/__init__.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
import importlib.util
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
def _find_repo_root(start_dir: str, probe_parts: list[str]) -> str:
|
||||
"""
|
||||
Walk upwards from start_dir until a path joined with probe_parts exists.
|
||||
Returns the directory considered the repo root.
|
||||
"""
|
||||
cur = os.path.abspath(start_dir)
|
||||
for _ in range(15): # plenty of headroom
|
||||
candidate = os.path.join(cur, *probe_parts)
|
||||
if os.path.exists(candidate):
|
||||
return cur
|
||||
parent = os.path.dirname(cur)
|
||||
if parent == cur:
|
||||
break
|
||||
cur = parent
|
||||
raise RuntimeError(
|
||||
f"Could not locate {'/'.join(probe_parts)} starting from {start_dir}"
|
||||
)
|
||||
|
||||
PROBE = ["roles", "sys-svc-cdn", "filter_plugins", "cdn_paths.py"]
|
||||
ROOT = _find_repo_root(HERE, PROBE)
|
||||
|
||||
def _load_module(mod_name: str, rel_path_from_root: str):
|
||||
"""Load a python module from an absolute file path (hyphen-safe)."""
|
||||
path = os.path.join(ROOT, rel_path_from_root)
|
||||
if not os.path.isfile(path):
|
||||
raise FileNotFoundError(path)
|
||||
spec = importlib.util.spec_from_file_location(mod_name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader, f"Cannot load spec for {path}"
|
||||
spec.loader.exec_module(module) # type: ignore[attr-defined]
|
||||
return module
|
||||
|
||||
cdn_paths_mod = _load_module(
|
||||
"cdn_paths_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_paths.py")
|
||||
)
|
||||
cdn_urls_mod = _load_module(
|
||||
"cdn_urls_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_urls.py")
|
||||
)
|
||||
cdn_dirs_mod = _load_module(
|
||||
"cdn_dirs_mod", os.path.join("roles", "sys-svc-cdn", "filter_plugins", "cdn_dirs.py")
|
||||
)
|
||||
|
||||
class TestCdnPathsUrlsDirs(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.root = self.tmp.name
|
||||
self.app = "web-app-desktop"
|
||||
self.ver = "20250101"
|
||||
|
||||
self.cdn_paths = cdn_paths_mod.cdn_paths
|
||||
self.cdn_urls = cdn_urls_mod.cdn_urls
|
||||
self.cdn_dirs = cdn_dirs_mod.cdn_dirs
|
||||
|
||||
self.tree = self.cdn_paths(self.root, self.app, self.ver)
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
# ---- cdn_paths ----
|
||||
def test_paths_shape_and_values(self):
|
||||
t = self.tree
|
||||
self.assertTrue(os.path.isabs(t["root"]))
|
||||
self.assertEqual(t["role"]["id"], self.app)
|
||||
self.assertEqual(t["role"]["version"], self.ver)
|
||||
self.assertTrue(t["shared"]["css"].endswith(os.path.join("_shared", "css")))
|
||||
self.assertTrue(
|
||||
t["role"]["release"]["css"].endswith(os.path.join(self.app, self.ver, "css"))
|
||||
)
|
||||
|
||||
# ---- cdn_urls ----
|
||||
def test_urls_mapping_and_root_trailing_slash(self):
|
||||
base = "https://cdn.example.com"
|
||||
urls = self.cdn_urls(self.tree, base)
|
||||
|
||||
# Non-path strings remain untouched
|
||||
self.assertEqual(urls["role"]["id"], self.app)
|
||||
self.assertEqual(urls["role"]["version"], self.ver)
|
||||
|
||||
# Paths are mapped to URLs
|
||||
self.assertTrue(urls["shared"]["js"].startswith(base + "/"))
|
||||
self.assertTrue(urls["vendor"].startswith(base + "/vendor"))
|
||||
|
||||
# Root always ends with '/'
|
||||
self.assertEqual(urls["root"], base.rstrip("/") + "/")
|
||||
|
||||
def test_urls_invalid_input_raises(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.cdn_urls({}, "https://cdn.example.com")
|
||||
with self.assertRaises(ValueError):
|
||||
self.cdn_urls("nope", "https://cdn.example.com") # type: ignore[arg-type]
|
||||
|
||||
# ---- cdn_dirs ----
|
||||
def test_dirs_collects_all_abs_dirs_sorted_unique(self):
|
||||
dirs = self.cdn_dirs(self.tree)
|
||||
self.assertIn(os.path.join(self.root, "_shared", "css"), dirs)
|
||||
self.assertIn(os.path.join(self.root, "roles", self.app, self.ver, "img"), dirs)
|
||||
self.assertEqual(dirs, sorted(dirs))
|
||||
self.assertEqual(len(dirs), len(set(dirs)))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Reference in New Issue
Block a user