main
py 153 lines 5.16 KB
Raw
1 import re
2 from html.parser import HTMLParser
3 from pathlib import Path
4
5
6 PROJECT_ROOT = Path(__file__).resolve().parents[1]
7 WEBUI_ROOT = PROJECT_ROOT / "webui"
8 REMOTE_URL = re.compile(r"^(?:https?:)?//", re.IGNORECASE)
9 REMOTE_CSS_ASSET = re.compile(
10 r"(?:@import\s+(?:url\()?|url\()\s*['\"]?(?:https?:)?//",
11 re.IGNORECASE,
12 )
13
14
15 class PassiveAssetParser(HTMLParser):
16 passive_link_relations = {
17 "icon",
18 "manifest",
19 "modulepreload",
20 "prefetch",
21 "preload",
22 "stylesheet",
23 }
24
25 def __init__(self) -> None:
26 super().__init__()
27 self.remote_assets: list[str] = []
28
29 def handle_starttag(
30 self, tag: str, attrs: list[tuple[str, str | None]]
31 ) -> None:
32 values = dict(attrs)
33 candidates: list[str] = []
34
35 if tag in {"audio", "embed", "iframe", "img", "script", "source", "video"}:
36 candidates.extend(filter(None, (values.get("src"), values.get("poster"))))
37 elif tag == "link":
38 relations = set((values.get("rel") or "").lower().split())
39 if relations & self.passive_link_relations:
40 candidates.extend(filter(None, (values.get("href"),)))
41
42 self.remote_assets.extend(value for value in candidates if REMOTE_URL.match(value))
43
44
45 def read(path: Path) -> str:
46 return path.read_text(encoding="utf-8")
47
48
49 def test_core_stylesheets_do_not_load_remote_assets() -> None:
50 offenders = []
51 for path in WEBUI_ROOT.rglob("*.css"):
52 if "vendor" in path.parts:
53 continue
54 if REMOTE_CSS_ASSET.search(read(path)):
55 offenders.append(str(path.relative_to(PROJECT_ROOT)))
56
57 assert offenders == []
58
59
60 def test_webui_markup_does_not_passively_load_remote_assets() -> None:
61 offenders: dict[str, list[str]] = {}
62 for path in WEBUI_ROOT.rglob("*.html"):
63 parser = PassiveAssetParser()
64 parser.feed(read(path))
65 if parser.remote_assets:
66 offenders[str(path.relative_to(PROJECT_ROOT))] = parser.remote_assets
67
68 assert offenders == {}
69
70
71 def test_main_and_login_pages_load_the_shared_local_font_stylesheet() -> None:
72 index_html = read(WEBUI_ROOT / "index.html")
73 login_html = read(WEBUI_ROOT / "login.html")
74
75 assert 'href="/vendor/fonts/fonts.css"' in index_html
76 assert 'href="/vendor/fonts/fonts.css"' in login_html
77 assert "fonts.googleapis.com" not in read(WEBUI_ROOT / "index.css")
78 assert "fonts.googleapis.com" not in read(WEBUI_ROOT / "login.css")
79
80
81 def test_login_uses_full_svg_logo() -> None:
82 login_html = read(WEBUI_ROOT / "login.html")
83
84 assert 'src="/public/dark.svg"' in login_html
85 assert (WEBUI_ROOT / "public" / "dark.svg").is_file()
86 assert 'src="/public/splash.jpg"' not in login_html
87
88
89 def test_vendored_variable_font_bundle_is_complete() -> None:
90 fonts_root = WEBUI_ROOT / "vendor" / "fonts"
91 font_css = read(fonts_root / "fonts.css")
92 expected_fonts = {
93 "rubik-variable.ttf": (
94 'font-family: "Rubik"',
95 "font-style: normal",
96 "font-weight: 300 900",
97 ),
98 "rubik-italic-variable.ttf": (
99 'font-family: "Rubik"',
100 "font-style: italic",
101 "font-weight: 300 900",
102 ),
103 "roboto-mono-variable.ttf": (
104 'font-family: "Roboto Mono"',
105 "font-style: normal",
106 "font-weight: 100 700",
107 ),
108 "roboto-mono-italic-variable.ttf": (
109 'font-family: "Roboto Mono"',
110 "font-style: italic",
111 "font-weight: 100 700",
112 ),
113 }
114
115 for filename, declarations in expected_fonts.items():
116 font_path = fonts_root / filename
117 font_face = next(
118 block
119 for block in re.findall(r"@font-face\s*\{([^}]+)\}", font_css)
120 if f'url("./{filename}")' in block
121 )
122 assert font_path.read_bytes()[:4] == b"\x00\x01\x00\x00"
123 assert all(declaration in font_face for declaration in declarations)
124
125 assert REMOTE_CSS_ASSET.search(font_css) is None
126 assert (fonts_root / "rubik-OFL.txt").is_file()
127 assert (fonts_root / "roboto-mono-OFL.txt").is_file()
128
129
130 def test_material_icon_font_is_preloaded_and_layout_stable() -> None:
131 icon_root = WEBUI_ROOT / "vendor" / "google"
132 icon_css = read(icon_root / "google-icons.css")
133 index_html = read(WEBUI_ROOT / "index.html")
134 splash_html = read(WEBUI_ROOT / "splash.html")
135
136 assert (icon_root / "google-icons.woff2").read_bytes()[:4] == b"wOF2"
137 assert "url(./google-icons.woff2) format('woff2')" in icon_css
138 assert "font-display: block" in icon_css
139 assert "width: 1em !important" in icon_css
140 assert "min-width: 1em !important" in icon_css
141 assert "max-width: 1em !important" in icon_css
142 assert "height: 1em !important" in icon_css
143 assert "overflow: hidden !important" in icon_css
144 assert "html:not(.material-icons-ready)" in icon_css
145 assert "x-icon," in icon_css
146 assert ".material-symbols-outlined," in icon_css
147 assert ".material-icons-outlined" in icon_css
148 preload = (
149 '<link rel="preload" href="/vendor/google/google-icons.woff2" '
150 'as="font" type="font/woff2" crossorigin>'
151 )
152 assert preload in index_html
153 assert preload not in splash_html