"""Local UI dependencies are served without exposing project data.""" import http.client import pathlib import re import threading from http.server import ThreadingHTTPServer import pytest from omni import serve @pytest.fixture def static_server(tmp_path, monkeypatch): web = tmp_path / "web" vendor = web / "vendor" / "tabler" vendor.mkdir(parents=True) (vendor / "tabler.min.css").write_text("body{color:#123}", encoding="utf-8") (vendor / "tabler.min.js").write_text("window.tabler={};", encoding="utf-8") (web / "index.html").write_text("panel", encoding="utf-8") (web / "private.js").write_text("private", encoding="utf-8") (vendor / "secret.json").write_text('{"secret":"test-only"}', encoding="utf-8") (tmp_path / "outside.js").write_text("outside", encoding="utf-8") monkeypatch.setattr(serve, "WEB", web) server = ThreadingHTTPServer(("127.0.0.1", 0), serve.Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() def get(path): connection = http.client.HTTPConnection("127.0.0.1", server.server_port, timeout=5) try: connection.request("GET", path) response = connection.getresponse() return response.status, dict(response.getheaders()), response.read() finally: connection.close() try: yield get finally: server.shutdown() server.server_close() thread.join(timeout=5) @pytest.mark.parametrize("name,ctype,expected", [ ("tabler.min.css", "text/css; charset=utf-8", b"body{color:#123}"), ("tabler.min.js", "text/javascript; charset=utf-8", b"window.tabler={};"), ]) def test_local_template_assets(static_server, name, ctype, expected): status, headers, body = static_server("/assets/vendor/tabler/" + name + "?v=1.7.1") assert status == 200 and body == expected assert headers["Content-Type"] == ctype assert headers["X-Content-Type-Options"] == "nosniff" assert int(headers["Content-Length"]) == len(expected) @pytest.mark.parametrize("path", [ "/assets/vendor/tabler/missing.js", "/assets/vendor/tabler/", "/assets/vendor/tabler/secret.json", "/assets/private.js", "/assets/index.html", "/assets/vendor/../../private.js", "/assets/vendor/%2e%2e/%2e%2e/outside.js", "/assets/%2foutside.js", "/assets/vendor/..%5c..%5cprivate.js", "/assets/vendor/%00.js", ]) def test_assets_reject_private_files_and_traversal(static_server, path): status, _, body = static_server(path) assert status == 404 assert body == b"not found" def test_index_still_served(static_server): status, headers, body = static_server("/") assert status == 200 and body == b"panel" assert headers["Content-Type"].startswith("text/html") WEB_ROOT = pathlib.Path(__file__).resolve().parents[1] / "web" def test_panel_html_is_self_contained_black(): html = (WEB_ROOT / "index.html").read_text(encoding="utf-8") # Black theme is declared three ways so first paint cannot flash white. assert 'data-bs-theme="dark"' in html assert "color-scheme: dark" in html assert "--omni-background: #09090b" in html # No remote dependencies: every src/href must stay inside /assets/ or be a data URL. for attr in re.findall(r'(?:src|href)="([^"]+)"', html): assert not attr.startswith(("http://", "https://", "//")), attr assert "/assets/vendor/tabler/tabler.min.css" in html assert "/assets/vendor/tabler/tabler.min.js" in html def test_panel_self_hosts_inter_for_latin_and_cyrillic(): html = (WEB_ROOT / "index.html").read_text(encoding="utf-8") assert html.count('font-family: "Inter Variable"') == 4 assert 'format("woff2-variations")' not in html # rejected by Firefox/Safari assert html.count('format("woff2")') == 4 assert html.count("font-display: swap") == 4 assert "--tblr-font-sans-serif: \"Inter Variable\"" in html files = WEB_ROOT / "vendor" / "inter" / "files" for subset in ("latin", "latin-ext", "cyrillic", "cyrillic-ext"): face = files / f"inter-{subset}-wght-normal.woff2" assert face.is_file() and face.stat().st_size > 2000, face assert f"/assets/vendor/inter/files/{face.name}" in html licence = (WEB_ROOT / "vendor" / "inter" / "LICENSE").read_text(encoding="utf-8") assert "SIL OPEN FONT LICENSE" in licence.upper() def test_panel_typography_does_not_clip_cyrillic(): html = (WEB_ROOT / "index.html").read_text(encoding="utf-8") badge = re.search(r"\.badge \{[^}]*\}", html) assert badge and "line-height: 1.4" in badge.group(0) assert "letter-spacing: -.01em" not in html # negative tracking on Cyrillic titles for selector in (".brand-caption", ".feed-title", ".feed-preview"): rule = re.search(r"(?m)^" + re.escape(selector) + r" \{[^}]*\}", html) assert rule, selector line_height = re.search(r"line-height: ([\d.]+)", rule.group(0)) assert line_height and float(line_height.group(1)) >= 1.4, rule.group(0)