Files
hermes-webui/tests/test_static_asset_resolver.py
T

136 lines
4.7 KiB
Python

from __future__ import annotations
import importlib
import json
from pathlib import Path
from types import SimpleNamespace
from urllib.parse import quote
import api.config as api_config
import api.routes as routes
ROOT = Path(__file__).resolve().parent.parent
class _FakeHandler:
def __init__(self, request_headers=None):
self.status = None
self.sent_headers = []
self.body = bytearray()
self.headers = dict(request_headers or {})
self.wfile = self
def send_response(self, status):
self.status = status
def send_header(self, name, value):
self.sent_headers.append((name, value))
def end_headers(self):
pass
def write(self, data):
self.body.extend(data)
def header(self, name):
for key, value in self.sent_headers:
if key.lower() == name.lower():
return value
return None
def _get(path, request_headers=None):
handler = _FakeHandler(request_headers)
routes.handle_get(handler, SimpleNamespace(path=path, query=""))
return handler
def test_config_owner_returns_checkout_static_root():
static_root = ROOT / "static"
assert api_config.get_static_root() == static_root
assert api_config.get_index_html_path() == static_root / "index.html"
def test_manifest_routes_follow_selected_static_root(tmp_path, monkeypatch):
static_root = tmp_path / "static"
static_root.mkdir()
manifest_path = static_root / "manifest.json"
payload = json.dumps({"name": "temp", "display": "standalone"}).encode("utf-8")
manifest_path.write_bytes(payload)
monkeypatch.setattr(api_config, "get_static_root", lambda: static_root)
handler = _get("/manifest.json")
assert handler.status == 200
assert handler.header("Content-Type") == "application/manifest+json; charset=utf-8"
assert handler.header("Cache-Control") == "no-store"
assert bytes(handler.body) == payload
session_handler = _get("/session/manifest.webmanifest")
assert session_handler.status == 200
assert bytes(session_handler.body) == payload
def test_service_worker_and_favicon_follow_selected_static_root(tmp_path, monkeypatch):
static_root = tmp_path / "static"
static_root.mkdir()
sw_path = static_root / "sw.js"
sw_path.write_text("const version = '__WEBUI_VERSION__';\n", encoding="utf-8")
favicon_path = static_root / "favicon.ico"
favicon_path.write_bytes(b"favicon-bytes")
monkeypatch.setattr(api_config, "get_static_root", lambda: static_root)
sw_handler = _get("/sw.js")
# The route resolves api.updates dynamically on each request. Other update
# tests deliberately evict and re-import that module, so a collection-time
# alias can become stale even though the checkout and runtime module agree.
webui_version = importlib.import_module("api.updates").WEBUI_VERSION
expected = sw_path.read_text(encoding="utf-8").replace(
"__WEBUI_VERSION__", quote(webui_version, safe="")
).encode("utf-8")
assert sw_handler.status == 200
assert sw_handler.header("Service-Worker-Allowed") == "/"
assert sw_handler.header("Cache-Control") == "no-store"
assert bytes(sw_handler.body) == expected
favicon_handler = _get("/favicon.ico")
assert favicon_handler.status == 200
assert favicon_handler.header("Content-Type") == "image/x-icon"
assert bytes(favicon_handler.body) == b"favicon-bytes"
favicon_path.unlink()
missing_favicon_handler = _get("/favicon.ico")
assert missing_favicon_handler.status == 204
def test_index_shell_and_static_route_use_selected_root(tmp_path, monkeypatch):
static_root = tmp_path / "static"
static_root.mkdir()
index_path = static_root / "index.html"
index_path.write_bytes(
b"<html>__WEBUI_VERSION__ __MAX_UPLOAD_BYTES__ __CSRF_TOKEN_JSON__ temp</html>"
)
ui_path = static_root / "ui.js"
ui_path.write_bytes(b"console.log('temp static');\n")
monkeypatch.setattr(api_config, "get_static_root", lambda: static_root)
monkeypatch.setattr(api_config, "get_index_html_path", lambda: index_path)
monkeypatch.setattr(routes, "_INDEX_SHELL_CACHE", {})
monkeypatch.setattr(routes, "_STATIC_CACHE", {})
shell = routes._render_index_shell_base()
assert "temp" in shell
assert "__WEBUI_VERSION__" not in shell
assert "__MAX_UPLOAD_BYTES__" not in shell
assert "__CSRF_TOKEN_JSON__" in shell
temp_static = _get("/static/ui.js")
assert temp_static.status == 200
assert bytes(temp_static.body) == b"console.log('temp static');\n"
assert bytes(temp_static.body) != (ROOT / "static" / "ui.js").read_bytes()
traversal = _get("/static/../api/routes.py")
assert traversal.status == 404