Files
omni-parse/tests/test_static_assets.py
Vlad Gothbreach 8d4a4c14b4
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled
abuse: теги accs + продажа подписок, строже порог; panels: реальные free-имена и 130/175 с бесплатными
2026-10-27 03:28:44 +03:00

124 lines
5.0 KiB
Python

"""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("<html>panel</html>", 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"<html>panel</html>"
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)