Files
hermes-webui/tests/test_sprint33.py
T
Rod Boevandrodboev 2260a393f0 fix(#6612): isolate update_channel writes from generic Preferences autosave (#6666)
Removes update_channel from the generic preferences autosave payload and gives the channel selector a dedicated sequenced write path with FIFO settings-POST serialization and revert-on-failure, so a stale tab cannot silently flip the release channel.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-08-08 20:36:47 +00:00

179 lines
6.7 KiB
Python

"""
Sprint 33 Tests: Shared app dialogs replace native confirm/prompt usage.
These tests verify the static assets expose the reusable confirm/input modal
and that browser-native confirm/prompt calls are no longer used in the Web UI.
"""
import pathlib
import re
REPO = pathlib.Path(__file__).parent.parent
def read(path):
return (REPO / path).read_text(encoding="utf-8")
def test_index_has_shared_app_dialog_markup():
html = read("static/index.html")
assert 'id="appDialogOverlay"' in html
assert 'id="appDialog"' in html
assert 'id="appDialogTitle"' in html
assert 'id="appDialogDesc"' in html
assert 'id="appDialogInput"' in html
assert 'id="appDialogCancel"' in html
assert 'id="appDialogConfirm"' in html
def test_app_dialog_css_rules_exist():
css = read("static/style.css")
for selector in (
".app-dialog-overlay",
".app-dialog",
".app-dialog-input",
".app-dialog-actions",
".app-dialog-btn.confirm",
".app-dialog-btn.confirm.danger",
):
assert selector in css, f"missing CSS selector: {selector}"
def test_ui_js_exposes_shared_dialog_helpers():
src = read("static/ui.js")
assert "function showConfirmDialog(opts={})" in src
assert "function showPromptDialog(opts={})" in src
assert "document.addEventListener('keydown'" in src
def test_prompt_dialog_honors_custom_label_and_danger_state():
src = read("static/ui.js")
assert "confirmBtn.textContent=opts.confirmLabel||t('create')" in src
assert "confirmBtn.classList.toggle('danger',!!opts.danger)" in src
assert "opts.danger?'alertdialog':'dialog'" in src
def test_disable_auth_prompt_uses_destructive_label_not_create():
src = read("static/panels.js")
match = re.search(r"async function disableAuth\(\)\{(.*?)\n\}", src, re.DOTALL)
assert match, "disableAuth() not found"
body = match.group(1)
assert "const confirmText='DISABLE AUTH'" in body
assert "currentPwField=$('settingsCurrentPassword')" in body
assert "showToast(t('current_password_required'))" in body
assert body.index("showToast(t('current_password_required'))") < body.index("showPromptDialog")
assert "showPromptDialog" in body
assert "confirmLabel:t('disable_auth')" in body
assert "danger:true" in body
assert "t('create')" not in body
def test_auth_disabled_warning_uses_status_payload_without_extra_settings_fetch():
src = read("static/panels.js")
match = re.search(r"function _updateAuthDisabledWarning\(authStatus\)\{(.*?)\n\}", src, re.DOTALL)
assert match, "_updateAuthDisabledWarning(authStatus) not found"
body = match.group(1)
assert "authStatus&&authStatus.auth_disabled_acknowledged" in body
assert "api('/api/settings')" not in body
def test_acknowledgement_save_failure_uses_i18n_toast():
src = read("static/panels.js")
match = re.search(r"async function _setAuthDisabledAck\(checked\)\{(.*?)\n\}", src, re.DOTALL)
assert match, "_setAuthDisabledAck(checked) not found"
body = match.group(1)
assert "showToast(t('auth_ack_save_failed')+e.message)" in body
assert "Failed to update acknowledgement" not in body
def test_save_settings_password_change_preflights_current_password_before_api():
src = read("static/panels.js")
match = re.search(r"async function saveSettings\([^)]*\)\{(.*?)\n\}", src, re.DOTALL)
assert match, "saveSettings() not found"
body = match.group(1)
assert "currentPwField=$('settingsCurrentPassword')" in body
assert "showToast(t('current_password_required'))" in body
assert body.index("showToast(t('current_password_required'))") < body.index("_enqueueSettingsPost(")
def test_disable_auth_typed_confirm_locales_show_literal_phrase():
src = read("static/i18n.js")
values = re.findall(r"disable_auth_typed_confirm:\s*'([^']*)'", src)
assert values, "disable_auth_typed_confirm keys missing"
assert len(values) == len(_i18n_locale_blocks(src)), (
"Every locale must define disable_auth_typed_confirm exactly once"
)
missing_literal = [value for value in values if "DISABLE AUTH" not in value]
assert not missing_literal, (
"Disable-auth prompt must display the exact phrase accepted by "
f"disableAuth(), but these translations do not: {missing_literal}"
)
AUTH_SAFETY_LOCALE_KEYS = (
"current_password_label",
"current_password_placeholder",
"current_password_required",
"current_password_incorrect",
"disable_auth_typed_confirm",
"auth_status_password",
"auth_status_passkey_only",
"auth_status_unauthenticated",
"auth_warning_badge",
"auth_disabled_warning_message",
"auth_acknowledged_label",
"auth_ack_save_failed",
)
def _i18n_locale_blocks(src):
heads = list(re.finditer(r"^ (?:(?:'([^']+)')|([A-Za-z][A-Za-z0-9_]*)):\s*\{", src, re.M))
blocks = {}
for i, head in enumerate(heads):
locale = head.group(1) or head.group(2)
end = heads[i + 1].start() if i + 1 < len(heads) else src.find("\n};", head.end())
assert end != -1, f"could not find end of locale block {locale}"
blocks[locale] = src[head.end():end]
return blocks
def test_auth_safety_keys_exist_once_per_locale():
src = read("static/i18n.js")
blocks = _i18n_locale_blocks(src)
assert "pt" in blocks
assert "zh-Hant" in blocks
for locale, block in blocks.items():
missing = [key for key in AUTH_SAFETY_LOCALE_KEYS if f"{key}:" not in block]
duplicated = [
key for key in AUTH_SAFETY_LOCALE_KEYS
if len(re.findall(rf"\b{re.escape(key)}\s*:", block)) != 1
]
assert not missing, f"{locale} missing auth-safety locale keys: {missing}"
assert not duplicated, f"{locale} has duplicated auth-safety locale keys: {duplicated}"
def test_auth_safety_pt_and_zh_hant_strings_stay_in_correct_locale_blocks():
src = read("static/i18n.js")
blocks = _i18n_locale_blocks(src)
zh_hant = blocks["zh-Hant"]
pt = blocks["pt"]
assert "目前密碼" in zh_hant
assert "輸入 DISABLE AUTH" in zh_hant
assert "Senha atual" not in zh_hant
assert "Digite DISABLE AUTH" not in zh_hant
assert "Senha atual" in pt
assert "Digite DISABLE AUTH" in pt
def test_no_native_confirm_calls_remain_in_static_js():
for path in (REPO / "static").glob("*.js"):
src = path.read_text(encoding="utf-8")
assert not re.search(r"\bconfirm\s*\(", src), f"native confirm() remains in {path.name}"
def test_no_native_prompt_calls_remain_in_static_js():
for path in (REPO / "static").glob("*.js"):
src = path.read_text(encoding="utf-8")
assert not re.search(r"\bprompt\s*\(", src), f"native prompt() remains in {path.name}"