feat: mirror subscription lifecycle notifications
This commit is contained in:
@@ -282,3 +282,4 @@ class SettingsTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(settings.SUBSCRIPTION_NOTIFY_HOURS_BEFORE, 3)
|
||||
self.assertEqual(settings.SUBSCRIPTION_NOTIFICATION_WORKER_TICK_SECONDS, 300)
|
||||
self.assertTrue(settings.SUBSCRIPTION_EMAIL_NOTIFICATIONS_ENABLED)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
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"]
|
||||
Reference in New Issue
Block a user