import unittest from types import SimpleNamespace from typing import List from unittest.mock import patch from bot.services import user_email_notifications as module class _FakeI18n: def gettext(self, language, key, **kwargs): values = { "email_payment_failed_subject": "Payment failed", "email_user_notification_intro": "Account notification.", "email_user_notification_cta": "Open dashboard", "email_user_notification_text_dashboard": "Dashboard: {url}", "email_footer_auto": "Sent automatically by {brand}.", } return values.get(key, key).format(**kwargs) class _FakeEmailService: instances: List["_FakeEmailService"] = [] def __init__(self, settings, i18n=None): self.settings = settings self.i18n = i18n self.sent: List[dict] = [] _FakeEmailService.instances.append(self) async def send_rendered_email(self, *, email, content): self.sent.append({"email": email, "content": content}) def _settings(**overrides): values = { "email_auth_configured": True, "DEFAULT_LANGUAGE": "en", "WEBAPP_PRIMARY_COLOR": "#00fe7a", "WEBAPP_TITLE": "Mini Shop", "WEBAPP_LOGO_URL": "", "WEBAPP_LOGO_USE_EMOJI": False, } values.update(overrides) return SimpleNamespace(**values) class SendUserNotificationEmailTests(unittest.IsolatedAsyncioTestCase): def setUp(self) -> None: _FakeEmailService.instances = [] async def test_sends_rendered_notification_to_linked_email(self): user = SimpleNamespace(email="user@example.com", language_code="en") with patch.object(module, "EmailAuthService", _FakeEmailService): sent = await module.send_user_notification_email( settings=_settings(), i18n=_FakeI18n(), user=user, subject_key="email_payment_failed_subject", message_text="Important\n", dashboard_url="https://app.example.com/account", ) self.assertTrue(sent) self.assertEqual(len(_FakeEmailService.instances), 1) payload = _FakeEmailService.instances[0].sent[0] self.assertEqual(payload["email"], "user@example.com") content = payload["content"] self.assertEqual(content.subject, "Payment failed") self.assertIn("Important", content.text) self.assertIn("Dashboard: https://app.example.com/account", content.text) self.assertIn("Important", content.html) self.assertIn("<script>alert(1)</script>", content.html) async def test_skips_when_smtp_is_not_configured(self): user = SimpleNamespace(email="user@example.com", language_code="en") with patch.object(module, "EmailAuthService", _FakeEmailService): sent = await module.send_user_notification_email( settings=_settings(email_auth_configured=False), i18n=_FakeI18n(), user=user, subject_key="email_payment_failed_subject", message_text="Payment failed", ) self.assertFalse(sent) self.assertEqual(_FakeEmailService.instances, []) async def test_skips_when_user_has_no_email(self): user = SimpleNamespace(email=" ", language_code="en") with patch.object(module, "EmailAuthService", _FakeEmailService): sent = await module.send_user_notification_email( settings=_settings(), i18n=_FakeI18n(), user=user, subject_key="email_payment_failed_subject", message_text="Payment failed", ) self.assertFalse(sent) self.assertEqual(_FakeEmailService.instances, []) if __name__ == "__main__": # pragma: no cover unittest.main()