302 lines
10 KiB
Python
302 lines
10 KiB
Python
import json
|
||
import re
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from bot.app.web.admin_settings_manifest import coerce_value, get_field_by_key, manifest_payload
|
||
from bot.middlewares.i18n import resolve_locale_key
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||
|
||
SUPPORT_RELATED_SETTINGS = (
|
||
"LOG_SUPPORT_THREAD_ID",
|
||
"SUPPORT_TICKETS_ENABLED",
|
||
"SUPPORT_ADMIN_EMAIL_NOTIFICATIONS_ENABLED",
|
||
"SUPPORT_ADMIN_NOTIFICATION_COOLDOWN_SECONDS",
|
||
"SUPPORT_ADMIN_EMAIL_COOLDOWN_SECONDS",
|
||
"SUPPORT_TICKET_MAX_BODY_LENGTH",
|
||
"SUPPORT_TICKET_MAX_SUBJECT_LENGTH",
|
||
"SUPPORT_TICKET_RATE_LIMIT_PER_HOUR",
|
||
)
|
||
|
||
SUBSCRIPTION_PURCHASE_DESCRIPTION_SETTINGS = (
|
||
"SUBSCRIPTION_PURCHASE_DESCRIPTION_ENABLED",
|
||
"SUBSCRIPTION_PURCHASE_DESCRIPTION_RU",
|
||
"SUBSCRIPTION_PURCHASE_DESCRIPTION_EN",
|
||
)
|
||
|
||
SUBSCRIPTION_GUIDE_SETTINGS = (
|
||
"SUBSCRIPTION_GUIDES_ENABLED",
|
||
"SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED",
|
||
"SUBSCRIPTION_PAGE_CONFIG_PANEL_ENABLED",
|
||
"SUBSCRIPTION_PAGE_CONFIG_JSON_OVERRIDE_ENABLED",
|
||
"SUBSCRIPTION_PAGE_CONFIG_PATH",
|
||
"SUBSCRIPTION_PAGE_CONFIG_JSON",
|
||
)
|
||
|
||
BACKUP_SETTINGS = (
|
||
"BACKUP_ENABLED",
|
||
"BACKUP_CHAT_ID",
|
||
"BACKUP_THREAD_ID",
|
||
"BACKUP_INTERVAL_SECONDS",
|
||
"BACKUP_LOCAL_RETENTION",
|
||
"BACKUP_COMPOSE_ENABLED",
|
||
)
|
||
|
||
ADMIN_TARIFF_SETTINGS_PAGE_KEYS = {
|
||
"admin_tariffs_trial_title",
|
||
"admin_tariffs_trial_subtitle",
|
||
"admin_tariffs_trial_enabled",
|
||
"admin_tariffs_trial_days",
|
||
"admin_tariffs_trial_traffic",
|
||
"admin_tariffs_trial_strategy",
|
||
"admin_tariffs_trial_squads",
|
||
"admin_tariffs_trial_squads_hint",
|
||
"admin_tariffs_trial_add_squad",
|
||
"admin_tariffs_trial_group_switch",
|
||
"admin_tariffs_trial_group_switch_hint",
|
||
"admin_tariffs_trial_group_general",
|
||
"admin_tariffs_trial_group_general_hint",
|
||
"admin_tariffs_trial_group_reset",
|
||
"admin_tariffs_trial_group_reset_hint",
|
||
"admin_tariffs_trial_group_squads",
|
||
"admin_tariffs_trial_group_squads_hint",
|
||
"admin_tariffs_legacy_title",
|
||
"admin_tariffs_legacy_subtitle",
|
||
"admin_tariffs_legacy_period",
|
||
"admin_tariffs_legacy_enabled",
|
||
"admin_tariffs_legacy_ref_inviter",
|
||
"admin_tariffs_legacy_ref_referee",
|
||
"admin_tariffs_legacy_traffic_packages",
|
||
"admin_tariffs_legacy_stars_traffic_packages",
|
||
"admin_tariffs_legacy_traffic_hint",
|
||
"admin_payment_rub",
|
||
"admin_payment_stars",
|
||
}
|
||
|
||
ADMIN_VISIBLE_RU_KEYS = {
|
||
"admin_appearance_theme_accent",
|
||
"admin_clear",
|
||
"admin_save",
|
||
"admin_saving",
|
||
"admin_search",
|
||
"admin_settings_badge_override",
|
||
"admin_settings_badge_secret",
|
||
"admin_settings_badge_dirty",
|
||
"admin_settings_icon_current",
|
||
"admin_settings_icon_default_value",
|
||
"admin_settings_icon_empty",
|
||
"admin_settings_icon_picker_title",
|
||
"admin_settings_icon_use_default",
|
||
"admin_tariff_label_premium_squads",
|
||
"admin_tariff_premium",
|
||
"admin_tariff_squads",
|
||
"admin_tariff_tab_premium",
|
||
}
|
||
|
||
ADMIN_AT_SIMPLE_FALLBACK_RE = re.compile(
|
||
r"""\bat\(\s*["'`](?P<key>[^"'`$]+)["'`]\s*,\s*\{[^)]*?\}\s*,\s*["'`](?P<fallback>[^"'`$]*)["'`]""",
|
||
re.DOTALL,
|
||
)
|
||
|
||
|
||
def _manifest_by_key() -> dict[str, dict]:
|
||
return {item["key"]: item for item in manifest_payload()}
|
||
|
||
|
||
def _manifest_items() -> list[dict]:
|
||
return manifest_payload()
|
||
|
||
|
||
def _locale(language: str) -> dict[str, str]:
|
||
return json.loads((REPO_ROOT / "locales" / f"{language}.json").read_text(encoding="utf-8"))
|
||
|
||
|
||
def _has_locale_key(messages: dict[str, str], key: str) -> bool:
|
||
return resolve_locale_key(key) in messages
|
||
|
||
|
||
def test_webapp_title_is_first_general_admin_setting():
|
||
items = _manifest_items()
|
||
manifest = {item["key"]: item for item in items}
|
||
field = manifest["WEBAPP_TITLE"]
|
||
|
||
assert field["section"] == "general"
|
||
assert field["section_order"] == 1
|
||
assert [item["key"] for item in items if item["section"] == "general"][0] == "WEBAPP_TITLE"
|
||
|
||
|
||
def test_support_settings_manifest_uses_admin_i18n_keys():
|
||
manifest = _manifest_by_key()
|
||
|
||
assert manifest["SUPPORT_TICKETS_ENABLED"]["section"] == "support"
|
||
assert manifest["SUPPORT_TICKETS_ENABLED"]["section_order"] == 8
|
||
|
||
for setting_key in SUPPORT_RELATED_SETTINGS:
|
||
field = manifest[setting_key]
|
||
prefix = f"admin_settings_field_{setting_key.lower()}"
|
||
|
||
assert field["i18n_label_key"] == f"{prefix}_label"
|
||
assert field["i18n_description_key"] == f"{prefix}_description"
|
||
|
||
|
||
def test_support_settings_i18n_keys_exist_in_admin_locales():
|
||
manifest = _manifest_by_key()
|
||
|
||
for language in ("ru", "en"):
|
||
messages = _locale(language)
|
||
|
||
assert "admin_settings_section_support" in messages
|
||
for setting_key in SUPPORT_RELATED_SETTINGS:
|
||
field = manifest[setting_key]
|
||
assert field["i18n_label_key"] in messages
|
||
assert field["i18n_description_key"] in messages
|
||
|
||
|
||
def test_subscription_purchase_description_settings_i18n_keys_exist():
|
||
manifest = _manifest_by_key()
|
||
|
||
for language in ("ru", "en"):
|
||
messages = _locale(language)
|
||
for setting_key in SUBSCRIPTION_PURCHASE_DESCRIPTION_SETTINGS:
|
||
field = manifest[setting_key]
|
||
assert field["section"] == "payments"
|
||
assert field["subsection"] == "checkout"
|
||
assert field["i18n_label_key"] in messages
|
||
assert field["i18n_description_key"] in messages
|
||
|
||
|
||
def test_subscription_guide_settings_i18n_keys_exist():
|
||
manifest = _manifest_by_key()
|
||
|
||
assert manifest["SUBSCRIPTION_GUIDES_ENABLED"]["section"] == "subscription_guides"
|
||
assert manifest["SUBSCRIPTION_GUIDES_ENABLED"]["section_order"] == 10
|
||
assert manifest["SUBSCRIPTION_PAGE_CONFIG_JSON"]["type"] == "json"
|
||
|
||
for language in ("ru", "en"):
|
||
messages = _locale(language)
|
||
|
||
assert "admin_settings_section_subscription_guides" in messages
|
||
for setting_key in SUBSCRIPTION_GUIDE_SETTINGS:
|
||
field = manifest[setting_key]
|
||
assert field["i18n_label_key"] in messages
|
||
assert field["i18n_description_key"] in messages
|
||
|
||
|
||
def test_backup_settings_i18n_keys_exist():
|
||
manifest = _manifest_by_key()
|
||
|
||
assert manifest["BACKUP_ENABLED"]["section"] == "backups"
|
||
assert manifest["BACKUP_ENABLED"]["section_order"] == 9
|
||
assert manifest["BACKUP_INTERVAL_SECONDS"]["min"] == 60
|
||
assert manifest["BACKUP_INTERVAL_SECONDS"]["optional"] is False
|
||
assert manifest["BACKUP_LOCAL_RETENTION"]["min"] == 1
|
||
assert manifest["BACKUP_LOCAL_RETENTION"]["optional"] is False
|
||
|
||
for language in ("ru", "en"):
|
||
messages = _locale(language)
|
||
|
||
assert "admin_settings_section_backups" in messages
|
||
for setting_key in BACKUP_SETTINGS:
|
||
field = manifest[setting_key]
|
||
assert field["i18n_label_key"] in messages
|
||
assert field["i18n_description_key"] in messages
|
||
|
||
|
||
def test_backup_required_numeric_settings_reject_empty_values():
|
||
with pytest.raises(ValueError):
|
||
coerce_value(get_field_by_key("BACKUP_INTERVAL_SECONDS"), "")
|
||
|
||
|
||
def test_trial_required_settings_reject_empty_values():
|
||
for key in (
|
||
"TRIAL_ENABLED",
|
||
"TRIAL_DURATION_DAYS",
|
||
"TRIAL_TRAFFIC_LIMIT_GB",
|
||
"TRIAL_TRAFFIC_STRATEGY",
|
||
):
|
||
with pytest.raises(ValueError):
|
||
coerce_value(get_field_by_key(key), "")
|
||
|
||
|
||
def test_payment_provider_settings_include_webhook_metadata():
|
||
manifest = _manifest_by_key()
|
||
|
||
assert manifest["FREEKASSA_ENABLED"]["webhook_path"] == "/webhook/freekassa"
|
||
assert manifest["FREEKASSA_ENABLED"]["provider_id"] == "freekassa"
|
||
assert manifest["PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_RU"]["webhook_path"] == "/webhook/platega"
|
||
assert manifest["YOOKASSA_SHOP_ID"]["webhook_requires_base_url"] is True
|
||
assert "webhook_path" not in manifest["PAYMENT_STARS_WEBAPP_LABEL_RU"]
|
||
|
||
|
||
def test_payment_provider_admin_only_toggles_are_mutually_exclusive():
|
||
manifest = _manifest_by_key()
|
||
|
||
assert manifest["WATA_ADMIN_ONLY_ENABLED"]["mutually_exclusive_key"] == "WATA_ENABLED"
|
||
assert manifest["WATA_ENABLED"]["mutually_exclusive_key"] == "WATA_ADMIN_ONLY_ENABLED"
|
||
assert (
|
||
manifest["PLATEGA_CRYPTO_ADMIN_ONLY_ENABLED"]["mutually_exclusive_key"]
|
||
== "PLATEGA_CRYPTO_ENABLED"
|
||
)
|
||
assert manifest["STARS_ADMIN_ONLY_ENABLED"]["mutually_exclusive_key"] == "STARS_ENABLED"
|
||
|
||
|
||
def test_legacy_tariff_settings_are_separated_from_payment_settings():
|
||
manifest = _manifest_by_key()
|
||
payment_method_fields = [
|
||
item for item in _manifest_items() if item["key"] == "PAYMENT_METHODS_ORDER"
|
||
]
|
||
|
||
assert len(payment_method_fields) == 1
|
||
assert payment_method_fields[0]["section"] == "payments"
|
||
assert manifest["MONTH_1_ENABLED"]["section"] == "pricing"
|
||
assert manifest["MONTH_1_ENABLED"]["section_order"] == 11
|
||
assert manifest["TRIAL_ENABLED"]["section"] == "pricing"
|
||
assert manifest["TRIAL_ENABLED"]["subsection"] == "trial"
|
||
assert manifest["TRIAL_SQUAD_UUIDS"]["section"] == "pricing"
|
||
assert manifest["TRIAL_SQUAD_UUIDS"]["subsection"] == "trial"
|
||
|
||
|
||
def test_platega_settings_share_one_admin_subsection():
|
||
manifest = _manifest_by_key()
|
||
platega_keys = [
|
||
key for key in manifest if key.startswith("PLATEGA_") or key.startswith("PAYMENT_PLATEGA_")
|
||
]
|
||
|
||
assert platega_keys
|
||
assert {manifest[key]["subsection"] for key in platega_keys} == {"Platega"}
|
||
|
||
|
||
def test_tariff_settings_page_i18n_keys_exist():
|
||
for language in ("ru", "en"):
|
||
messages = _locale(language)
|
||
missing = {
|
||
key for key in ADMIN_TARIFF_SETTINGS_PAGE_KEYS if not _has_locale_key(messages, key)
|
||
}
|
||
assert missing == set()
|
||
|
||
|
||
def test_visible_admin_russian_labels_do_not_fall_back_to_english():
|
||
messages = _locale("ru")
|
||
|
||
for key in ADMIN_VISIBLE_RU_KEYS:
|
||
assert key in messages
|
||
|
||
missing_latin_fallbacks = []
|
||
for file_path in (REPO_ROOT / "frontend" / "src" / "admin").rglob("*"):
|
||
if file_path.suffix not in {".svelte", ".js"}:
|
||
continue
|
||
text = file_path.read_text(encoding="utf-8")
|
||
for match in ADMIN_AT_SIMPLE_FALLBACK_RE.finditer(text):
|
||
locale_key = f"admin_{match.group('key')}"
|
||
fallback = match.group("fallback")
|
||
if _has_locale_key(messages, locale_key):
|
||
continue
|
||
has_latin = any("A" <= char <= "Z" or "a" <= char <= "z" for char in fallback)
|
||
has_cyrillic = any("А" <= char <= "я" or char in "Ёё" for char in fallback)
|
||
if has_latin and not has_cyrillic:
|
||
missing_latin_fallbacks.append((locale_key, fallback, str(file_path)))
|
||
|
||
assert missing_latin_fallbacks == []
|