from pathlib import Path import tempfile import unittest from fastapi import FastAPI from fastapi.testclient import TestClient from web.static_files import NoCacheStaticFiles ROOT = Path(__file__).resolve().parents[1] STATIC_DIR = ROOT / "web" / "static" DEV_HTML = STATIC_DIR / "dev.html" UI_CSS = STATIC_DIR / "ui.css" JS_DIR = STATIC_DIR / "js" VENDOR_DIR = STATIC_DIR / "vendor" / "markdown" def _frontend_source() -> str: """dev.html + 拆出的 ES module 源(路径 1 模块化后逻辑散落 js/*.js)合并文本。""" parts = [DEV_HTML.read_text(encoding="utf-8")] parts += [p.read_text(encoding="utf-8") for p in sorted(JS_DIR.glob("*.js"))] return "\n".join(parts) class StaticVendorTests(unittest.TestCase): def test_dev_html_uses_local_markdown_vendor_assets(self) -> None: html = DEV_HTML.read_text(encoding="utf-8") self.assertNotIn("cdn.jsdelivr.net", html) self.assertIn('src="vendor/markdown/marked.umd.js"', html) self.assertIn('src="vendor/markdown/purify.min.js"', html) self.assertIn('src="vendor/markdown/highlight.min.js"', html) self.assertIn('href="vendor/markdown/github.min.css"', html) for name in ( "marked.umd.js", "purify.min.js", "highlight.min.js", "github.min.css", ): path = VENDOR_DIR / name self.assertTrue(path.exists(), f"missing vendored asset: {path}") self.assertGreater(path.stat().st_size, 0, f"empty vendored asset: {path}") def test_dev_html_uses_local_ui_foundation(self) -> None: html = DEV_HTML.read_text(encoding="utf-8") self.assertIn('href="ui.css"', html) self.assertTrue(UI_CSS.exists(), f"missing UI foundation: {UI_CSS}") css = UI_CSS.read_text(encoding="utf-8") self.assertIn("--accent:", css) self.assertIn(":focus-visible", css) self.assertIn("@media (max-width: 640px)", css) def test_ui_polish_keeps_mobile_composer_and_reading_rail_contracts(self) -> None: html = DEV_HTML.read_text(encoding="utf-8") css = UI_CSS.read_text(encoding="utf-8") frontend = _frontend_source() self.assertIn("--list-divider:", css) self.assertIn(".msg.assistant", css) self.assertIn(".composer-tool .composer-label", css) self.assertIn('aria-label="添加附件"', html) self.assertIn('aria-label="语音输入"', html) self.assertIn('aria-label="润色提示词"', html) self.assertIn("hasVisibleAssistantContent", frontend) self.assertIn('input.placeholder = mqPhone.matches', frontend) def test_frontend_surfaces_context_and_cache_stats(self) -> None: src = _frontend_source() self.assertIn("formatContextStats", src) self.assertIn("context_original_chars", src) self.assertIn("cache_hit_tokens", src) def test_static_js_is_served_with_revalidation_header(self) -> None: with tempfile.TemporaryDirectory() as tmp: static_dir = Path(tmp) (static_dir / "app.js").write_text("export const ok = true;\n", encoding="utf-8") app = FastAPI() app.mount("/static", NoCacheStaticFiles(directory=str(static_dir)), name="static") client = TestClient(app) resp = client.get("/static/app.js") self.assertEqual(resp.status_code, 200) self.assertEqual(resp.headers.get("cache-control"), "no-cache") def test_ui_css_is_served_as_css_with_revalidation(self) -> None: app = FastAPI() app.mount("/static", NoCacheStaticFiles(directory=str(STATIC_DIR)), name="static") client = TestClient(app) resp = client.get("/static/ui.css") self.assertEqual(resp.status_code, 200) self.assertTrue(resp.headers.get("content-type", "").startswith("text/css")) self.assertEqual(resp.headers.get("cache-control"), "no-cache") self.assertIn("--accent:", resp.text) if __name__ == "__main__": unittest.main()