feat: tune deeplink fallback page
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
@@ -10,6 +12,8 @@ from bot.app.web import admin_api, subscription_webapp
|
||||
from bot.app.web.admin_api_impl import auth as admin_auth_routes
|
||||
from bot.app.web.webapp_auth import create_webapp_session_token
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class _Request(dict):
|
||||
def __init__(self, *, path="/", app=None, headers=None, cookies=None):
|
||||
@@ -31,6 +35,26 @@ class _AsyncSessionFactory:
|
||||
return False
|
||||
|
||||
|
||||
class _I18n:
|
||||
locales_data = {
|
||||
"en": {
|
||||
"wa_app_launch_title": "Localized launch",
|
||||
"wa_app_launch_opening_hint": "Localized opening hint",
|
||||
"wa_app_launch_hint": "Localized manual hint",
|
||||
"wa_app_launch_button": "Localized open",
|
||||
"wa_app_launch_retry_button": "Localized retry",
|
||||
"wa_app_launch_done_title": "Localized done",
|
||||
"wa_app_launch_done_hint": "Localized done hint",
|
||||
"wa_app_launch_close_button": "Localized close",
|
||||
"wa_app_launch_unavailable_title": "Localized unavailable",
|
||||
"wa_app_launch_unavailable_hint": "Localized unavailable hint",
|
||||
}
|
||||
}
|
||||
|
||||
def gettext(self, lang_code, key, **kwargs):
|
||||
return self.locales_data.get(lang_code, {}).get(key, key)
|
||||
|
||||
|
||||
def _route_map(app: web.Application) -> dict[tuple[str, str], str]:
|
||||
return {
|
||||
(route.method, route.resource.canonical): route.handler.__name__
|
||||
@@ -202,6 +226,7 @@ class WebAppRouteContractTests(unittest.TestCase):
|
||||
"settings": SimpleNamespace(
|
||||
WEBAPP_ENABLED=True,
|
||||
WEBAPP_TITLE="/minishop",
|
||||
DEFAULT_LANGUAGE="en",
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -212,11 +237,53 @@ class WebAppRouteContractTests(unittest.TestCase):
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertEqual(response.headers["Cache-Control"], "no-store")
|
||||
self.assertIn('nonce="nonce-value"', response.text)
|
||||
self.assertNotIn("__MESSAGES_JSON__", response.text)
|
||||
self.assertIn("window.location.hash", response.text)
|
||||
self.assertIn("URLSearchParams", response.text)
|
||||
self.assertIn("The app link is unavailable.", response.text)
|
||||
self.assertIn("Settings added", response.text)
|
||||
self.assertIn("window.close()", response.text)
|
||||
self.assertIn(r"/^(?:javascript|data|vbscript|https?):/i", response.text)
|
||||
|
||||
def test_app_deeplink_gateway_uses_i18n_template(self):
|
||||
request = _Request(
|
||||
app={
|
||||
"settings": SimpleNamespace(
|
||||
WEBAPP_ENABLED=True,
|
||||
WEBAPP_TITLE="/minishop",
|
||||
DEFAULT_LANGUAGE="en",
|
||||
),
|
||||
"i18n": _I18n(),
|
||||
}
|
||||
)
|
||||
request["csp_nonce"] = "nonce-value"
|
||||
|
||||
response = asyncio.run(subscription_webapp.app_deeplink_route(request))
|
||||
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertIn("Localized launch", response.text)
|
||||
self.assertIn("Localized done hint", response.text)
|
||||
self.assertIn("<title>/minishop - Localized launch</title>", response.text)
|
||||
self.assertTrue(
|
||||
(REPO_ROOT / "backend/bot/app/web/templates/open_app_gateway.html").is_file()
|
||||
)
|
||||
|
||||
def test_app_launch_i18n_keys_are_available_to_webapp_bootstrap(self):
|
||||
required_keys = {
|
||||
"wa_app_launch_title",
|
||||
"wa_app_launch_opening_hint",
|
||||
"wa_app_launch_hint",
|
||||
"wa_app_launch_button",
|
||||
"wa_app_launch_retry_button",
|
||||
"wa_app_launch_done_title",
|
||||
"wa_app_launch_done_hint",
|
||||
"wa_app_launch_close_button",
|
||||
"wa_app_launch_unavailable_title",
|
||||
"wa_app_launch_unavailable_hint",
|
||||
}
|
||||
for locale in ("en", "ru"):
|
||||
messages = json.loads((REPO_ROOT / f"locales/{locale}.json").read_text("utf-8"))
|
||||
self.assertLessEqual(required_keys, set(messages))
|
||||
|
||||
|
||||
class AdminApiAuthContractTests(unittest.IsolatedAsyncioTestCase):
|
||||
def _settings(self):
|
||||
|
||||
@@ -35,14 +35,17 @@ def test_logout_handler_is_noop_inside_telegram_mini_app():
|
||||
assert guard_pos < mark_logout_pos
|
||||
|
||||
|
||||
def test_open_app_route_skips_bootstrap_and_auth_flow():
|
||||
def test_open_app_route_uses_fallback_screen_without_auth_flow():
|
||||
main_source = _read("frontend/src/main.js")
|
||||
app_source = _read("frontend/src/App.svelte")
|
||||
screen_source = _read("frontend/src/webapp/screens/AppLaunchScreen.svelte")
|
||||
|
||||
assert "skipBootstrap = isExternalAppLaunchPath(window.location.pathname)" in main_source
|
||||
assert "loadBootstrap().finally" in main_source
|
||||
assert "AppLaunchScreen" in app_source
|
||||
assert 'mode = isAppLaunchRoute ? "appLaunch"' in app_source
|
||||
assert "window.close()" in screen_source
|
||||
|
||||
launch_guard_pos = app_source.index("if (isAppLaunchRoute) {")
|
||||
launch_guard_pos = app_source.index("if (isAppLaunchRoute) return;")
|
||||
boot_pos = app_source.index("boot();", launch_guard_pos)
|
||||
|
||||
assert launch_guard_pos < boot_pos
|
||||
|
||||
Reference in New Issue
Block a user