import asyncio from datetime import datetime, timezone from types import SimpleNamespace from bot.services import subscription_lifecycle_notifications as lifecycle from bot.services.subscription_lifecycle_notifications import ( SubscriptionLifecycleNotificationService, SubscriptionNotificationStage, ) class FakeI18n: def gettext(self, lang_code, key, **kwargs): messages = { "subscription_72h_notification": "Hi {user_name}, expires on {end_date}", "email_subscription_lifecycle_subject_before_days": "{days} days left", "email_subscription_lifecycle_subject_before_hours": "{hours} hours left", "email_subscription_lifecycle_subject_expired": "Expired", "email_subscription_lifecycle_subject_expired_after": "Expired yesterday", "email_subscription_lifecycle_subject_autorenew": "Auto-renewal tomorrow", "email_subscription_lifecycle_intro": "Subscription notice", "email_subscription_lifecycle_row_end_date": "Active until", "email_subscription_lifecycle_cta": "Open dashboard", "email_subscription_lifecycle_text_renew": "Dashboard: {url}", "email_footer_auto": "Sent by {brand}", } return messages.get(key, key).format(**kwargs) class FakeBot: def __init__(self): self.messages = [] async def send_message(self, chat_id, text, reply_markup=None): self.messages.append( { "chat_id": chat_id, "text": text, "reply_markup": reply_markup, } ) class FakeEmailService: def __init__(self): self.messages = [] async def send_rendered_email(self, *, email, content): self.messages.append({"email": email, "content": content}) def _settings(**overrides): return SimpleNamespace( DEFAULT_LANGUAGE="ru", SUBSCRIPTION_EMAIL_NOTIFICATIONS_ENABLED=True, SUBSCRIPTION_MINI_APP_URL="https://app.example.test/", WEBAPP_PRIMARY_COLOR="#00fe7a", WEBAPP_TITLE="Minishop", WEBAPP_LOGO_USE_EMOJI=False, WEBAPP_LOGO_URL="", email_auth_configured=True, **overrides, ) def _subscription(): return SimpleNamespace( subscription_id=42, user_id=123, end_date=datetime(2026, 6, 1, tzinfo=timezone.utc), ) def _user(**overrides): return SimpleNamespace( user_id=123, telegram_id=555, email="user@example.test", language_code="ru", first_name="Ada", **overrides, ) def test_send_stage_records_telegram_and_email_channel_keys(monkeypatch): recorded = [] async def fake_has(session, subscription_id, notification_key): return notification_key in recorded async def fake_record(session, subscription_id, notification_key, *, sent_at=None): recorded.append(notification_key) monkeypatch.setattr(lifecycle.subscription_dal, "has_subscription_notification", fake_has) monkeypatch.setattr(lifecycle.subscription_dal, "record_subscription_notification", fake_record) bot = FakeBot() email_service = FakeEmailService() service = SubscriptionLifecycleNotificationService( _settings(), bot, FakeI18n(), email_service=email_service, ) async def run(): return await service.send_stage( object(), _subscription(), SubscriptionNotificationStage( key="before_3d", message_key="subscription_72h_notification", days_left=3, ), user=_user(), telegram_markup="markup", ) delivery = asyncio.run(run()) assert delivery.telegram_sent is True assert delivery.email_sent is True assert bot.messages == [ { "chat_id": 555, "text": "Hi Ada, expires on 2026-06-01", "reply_markup": "markup", } ] assert email_service.messages[0]["email"] == "user@example.test" assert recorded == ["before_3d:telegram", "before_3d:email"] def test_legacy_stage_key_suppresses_only_telegram(monkeypatch): recorded = ["before_3d"] async def fake_has(session, subscription_id, notification_key): return notification_key in recorded async def fake_record(session, subscription_id, notification_key, *, sent_at=None): recorded.append(notification_key) monkeypatch.setattr(lifecycle.subscription_dal, "has_subscription_notification", fake_has) monkeypatch.setattr(lifecycle.subscription_dal, "record_subscription_notification", fake_record) bot = FakeBot() email_service = FakeEmailService() service = SubscriptionLifecycleNotificationService( _settings(), bot, FakeI18n(), email_service=email_service, ) async def run(): return await service.send_stage( object(), _subscription(), SubscriptionNotificationStage( key="before_3d", message_key="subscription_72h_notification", days_left=3, ), user=_user(), telegram_markup="markup", ) delivery = asyncio.run(run()) assert delivery.telegram_sent is False assert delivery.email_sent is True assert bot.messages == [] assert email_service.messages[0]["email"] == "user@example.test" assert recorded == ["before_3d", "before_3d:email"]