"""Контракт: пользовательские уведомления без технических деталей.""" from __future__ import annotations import re import unittest from tests.helpers.zootech_test_helpers import STATIC class UserMessagesContractTests(unittest.TestCase): def test_user_messages_module_exports(self) -> None: js = (STATIC / "js" / "wesp-user-messages.js").read_text(encoding="utf-8") self.assertIn("WespUserMessages", js) self.assertIn("userFacingMessage", js) self.assertIn("isTechnicalMessage", js) def test_notify_delegates_sanitization(self) -> None: notify_js = (STATIC / "js" / "wesp-zootech-notify.js").read_text(encoding="utf-8") self.assertIn("WespUserMessages", notify_js) self.assertIn("userFacingMessage(message, opts && opts.fallback)", notify_js) def test_no_raw_err_message_in_zootech_pages(self) -> None: offenders: list[str] = [] patterns = [ re.compile(r"notyf\.error\([^)]*err\.message[^,)]*\)"), re.compile(r"notyf\.error\(`[^`]*\$\{error\.message\}"), re.compile(r"textContent\s*=\s*[^;]*err\.message"), re.compile(r"textContent\s*=\s*[^;]*e\.message"), re.compile(r"HTTP error! status:"), ] for path in ( STATIC / "consumption.html", STATIC / "reports.html", STATIC / "recipes.html", STATIC / "components.html", STATIC / "feed_dispensers.html", STATIC / "js" / "feed-accounting-modal.js", STATIC / "js" / "pages" / "daily-plan-panel.js", STATIC / "js" / "pages" / "recipes-editor-controller.js", ): text = path.read_text(encoding="utf-8") for i, line in enumerate(text.splitlines(), 1): if "console." in line or "userFacingMessage" in line or "setupUserError" in line: continue for pat in patterns: if pat.search(line): offenders.append(f"{path.name}:{i}: {line.strip()}") self.assertEqual(offenders, [], "\n".join(offenders)) if __name__ == "__main__": unittest.main()