diff --git a/backend/app.py b/backend/app.py index f64c6757..03da4e57 100644 --- a/backend/app.py +++ b/backend/app.py @@ -217,6 +217,29 @@ def get_office_name_from_identity(): return None +def get_office_display_name(): + """Return the runtime office display name, falling back to the default brand.""" + return get_office_name_from_identity() or "Star 的像素办公室" + + +def build_absolute_url(path: str) -> str: + """Build an absolute URL for the current request host.""" + base = (request.url_root or "").rstrip("/") + normalized_path = "/" + path.lstrip("/") + return f"{base}{normalized_path}" + + +def render_frontend_html(filename: str, replacements: dict[str, str] | None = None): + """Load a frontend HTML file and replace lightweight placeholders.""" + with open(os.path.join(FRONTEND_DIR, filename), "r", encoding="utf-8") as f: + html = f.read() + for key, value in (replacements or {}).items(): + html = html.replace(key, value) + resp = make_response(html) + resp.headers["Content-Type"] = "text/html; charset=utf-8" + return resp + + def save_state(state: dict): """Save state to file""" with open(STATE_FILE, "w", encoding="utf-8") as f: @@ -287,21 +310,26 @@ def electron_standalone_page(): @app.route("/join", methods=["GET"]) def join_page(): """Serve the agent join page""" - with open(os.path.join(FRONTEND_DIR, "join.html"), "r", encoding="utf-8") as f: - html = f.read() - resp = make_response(html) - resp.headers["Content-Type"] = "text/html; charset=utf-8" - return resp + return render_frontend_html( + "join.html", + { + "{{OFFICE_NAME}}": get_office_display_name(), + "{{INVITE_URL}}": build_absolute_url("/invite"), + }, + ) @app.route("/invite", methods=["GET"]) def invite_page(): """Serve human-facing invite instruction page""" - with open(os.path.join(FRONTEND_DIR, "invite.html"), "r", encoding="utf-8") as f: - html = f.read() - resp = make_response(html) - resp.headers["Content-Type"] = "text/html; charset=utf-8" - return resp + office_name = get_office_display_name() + return render_frontend_html( + "invite.html", + { + "{{OFFICE_NAME}}": office_name, + "{{JOIN_URL}}": build_absolute_url("/join"), + }, + ) DEFAULT_AGENTS = [ diff --git a/backend/tests/test_dynamic_pages.py b/backend/tests/test_dynamic_pages.py new file mode 100644 index 00000000..1d3767ea --- /dev/null +++ b/backend/tests/test_dynamic_pages.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path +from unittest.mock import patch + +ROOT_DIR = Path(__file__).resolve().parents[2] +BACKEND_DIR = ROOT_DIR / "backend" +STATE_FILE = ROOT_DIR / "state.json" +STATE_FILE_EXISTED = STATE_FILE.exists() + +if str(BACKEND_DIR) not in sys.path: + sys.path.insert(0, str(BACKEND_DIR)) + +import app as backend_app # noqa: E402 + + +class DynamicPagesTestCase(unittest.TestCase): + def setUp(self) -> None: + self.client = backend_app.app.test_client() + + def test_join_page_uses_runtime_invite_url_and_office_name(self) -> None: + with patch.object(backend_app, "get_office_name_from_identity", return_value="测试办公室"): + response = self.client.get("/join") + + self.assertEqual(response.status_code, 200) + html = response.get_data(as_text=True) + self.assertIn("测试办公室", html) + self.assertIn("http://localhost/invite", html) + self.assertNotIn("office.example.com", html) + + def test_invite_page_uses_runtime_join_url_and_office_name(self) -> None: + with patch.object(backend_app, "get_office_name_from_identity", return_value="测试办公室"): + response = self.client.get("/invite") + + self.assertEqual(response.status_code, 200) + html = response.get_data(as_text=True) + self.assertIn("测试办公室", html) + self.assertIn("http://localhost/join", html) + self.assertNotIn("office.example.com", html) + + +def tearDownModule() -> None: + if not STATE_FILE_EXISTED and STATE_FILE.exists(): + STATE_FILE.unlink() + + +if __name__ == "__main__": + unittest.main() diff --git a/frontend/invite.html b/frontend/invite.html index 812f9611..44cf60a0 100644 --- a/frontend/invite.html +++ b/frontend/invite.html @@ -3,7 +3,7 @@
-