fix: simplify subscription payment messages
This commit is contained in:
1 parent
1ff4012e2f
commit
c32702c402
6 files changed
+100
-114
No files matched your search
@@ -153,7 +153,7 @@ MODE_COPY = {
|
||||
"Генерация",
|
||||
"Напиши, о чём нужен пост, или отправь голосовое.\nДля правки ответь на мой вариант.",
|
||||
),
|
||||
"subscription": ("Подписка", "Проверь статус ниже.\nВыбери действие в меню."),
|
||||
"subscription": ("Подписка", ""),
|
||||
"help": ("Помощь", "Выбери раздел в сообщении выше."),
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ async def enter_mode(message: Message, state: FSMContext, mode: str) -> None:
|
||||
await state.set_state(MODE_STATES[mode])
|
||||
subscription = None
|
||||
if mode == "subscription" and message.from_user is not None:
|
||||
await asyncio.to_thread(ensure_subscription, message.from_user.id)
|
||||
subscription = await asyncio.to_thread(subscription_row, message.from_user.id)
|
||||
title, description = MODE_COPY[mode]
|
||||
if mode == "help":
|
||||
@@ -186,7 +187,7 @@ async def enter_mode(message: Message, state: FSMContext, mode: str) -> None:
|
||||
reply_markup=help_inline_keyboard(),
|
||||
)
|
||||
await message.answer(
|
||||
f"{title}.\n{description}",
|
||||
title if mode == "subscription" else f"{title}.\n{description}",
|
||||
reply_markup=mode_keyboard(
|
||||
mode,
|
||||
subscription_purchased=bool(subscription and subscription["last_paid_at"]),
|
||||
@@ -415,21 +416,24 @@ async def confirm_development_payment(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
return
|
||||
try:
|
||||
user_id = int((callback.data or "").split(":", 1)[1])
|
||||
parts = (callback.data or "").split(":", 2)
|
||||
user_id = int(parts[1])
|
||||
days = int(parts[2])
|
||||
except (IndexError, ValueError):
|
||||
await callback.answer("Некорректное подтверждение.", show_alert=True)
|
||||
return
|
||||
if callback.from_user is None or callback.from_user.id != user_id:
|
||||
await callback.answer("Эта кнопка предназначена другому пользователю.", show_alert=True)
|
||||
return
|
||||
if days not in user_store.SUBSCRIPTION_PLANS:
|
||||
await callback.answer("Неизвестный срок подписки.", show_alert=True)
|
||||
return
|
||||
await asyncio.to_thread(ensure_subscription, user_id)
|
||||
days = await asyncio.to_thread(user_store.activate_subscription, user_id, months=1)
|
||||
await callback.answer()
|
||||
days_left = await asyncio.to_thread(user_store.activate_subscription_days, user_id, days)
|
||||
await callback.answer("Dev-оплата подтверждена.")
|
||||
if isinstance(callback.message, Message):
|
||||
await callback.message.edit_reply_markup(reply_markup=None)
|
||||
await callback.message.answer(
|
||||
f"🧪 Dev-платёж подтверждён. Подписка включена на {days} дн. Реальная оплата не проводилась."
|
||||
)
|
||||
await callback.message.answer(f"Подписка активна\nЗакончится через {days_left} дн.")
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
@@ -457,10 +461,7 @@ async def _show_subscription_info(
|
||||
message: Message, sub: sqlite3.Row | None, mode: str = "subscription"
|
||||
) -> None:
|
||||
if not sub:
|
||||
text = (
|
||||
f"Подписка не активирована. Оформи её за {SUBSCRIPTION_PRICE_STARS} звёзд "
|
||||
"за 30 дней, чтобы пользоваться генерацией."
|
||||
)
|
||||
text = "Подписка закончилась"
|
||||
else:
|
||||
days_left = user_store._days_until(sub)
|
||||
text = (
|
||||
@@ -469,9 +470,7 @@ async def _show_subscription_info(
|
||||
)
|
||||
|
||||
if mode == "subscription":
|
||||
markup = mode_keyboard(
|
||||
"subscription", subscription_purchased=bool(sub and sub["last_paid_at"])
|
||||
) if sub else mode_keyboard("subscription")
|
||||
markup = mode_keyboard("subscription", subscription_purchased=bool(sub and sub["last_paid_at"]))
|
||||
else:
|
||||
markup = mode_keyboard(mode)
|
||||
await message.answer(text, reply_markup=markup)
|
||||
@@ -499,43 +498,26 @@ async def subscribe_command(message: Message, state: FSMContext) -> None:
|
||||
async def _send_subscription_invoice(message: Message, user_id: int, days: int = 30) -> None:
|
||||
await asyncio.to_thread(ensure_subscription, user_id)
|
||||
try:
|
||||
if days == 30:
|
||||
await send_subscription_invoice(message.bot, message.chat.id, message.message_thread_id)
|
||||
else:
|
||||
await send_subscription_invoice(
|
||||
message.bot, message.chat.id, message.message_thread_id, days=days
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("Invoice send failed (%s): %s", type(exc).__name__, str(exc)[:180])
|
||||
if is_development_environment():
|
||||
await report_error(message, "отправка счёта на оплату", exc)
|
||||
else:
|
||||
await message.answer(
|
||||
"Не получилось открыть счёт на оплату. Попробуй ещё раз через минуту "
|
||||
"или напиши поддержке."
|
||||
)
|
||||
return
|
||||
await message.answer(
|
||||
f"🧾 <b>Счёт на подписку</b>\n"
|
||||
f"Срок: {days} дней\n"
|
||||
f"ID пользователя: <code>{user_id}</code>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
if is_development_environment():
|
||||
await message.answer(
|
||||
"🧪 Dev-режим: для продолжения разработки нажми кнопку ниже. "
|
||||
"Она включит доступ без реального платежа.",
|
||||
f"🧪 Dev-режим: кнопка ниже имитирует оплату и выдаёт срок: {days} дн.",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
inline_keyboard=[[InlineKeyboardButton(
|
||||
text="🧪 Оплатил (dev)",
|
||||
callback_data=f"dev_paid:{user_id}",
|
||||
text=f"🧪 Оплатил (dev, {days} дн.)",
|
||||
callback_data=f"dev_paid:{user_id}:{days}",
|
||||
)]]
|
||||
),
|
||||
)
|
||||
else:
|
||||
await message.answer(
|
||||
"Подписка активируется сразу после оплаты — отдельного подтверждения не нужно."
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("subscription:plan:"))
|
||||
@@ -552,6 +534,10 @@ async def subscription_plan(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await callback.answer("Такой тариф недоступен.", show_alert=True)
|
||||
return
|
||||
await callback.answer()
|
||||
try:
|
||||
await callback.message.delete()
|
||||
except Exception as exc:
|
||||
logger.warning("Subscription plan message delete failed (%s)", type(exc).__name__)
|
||||
await _send_subscription_invoice(callback.message, callback.from_user.id, days)
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ from aiogram.types import (
|
||||
)
|
||||
|
||||
from help_message import HELP_SECTIONS, HELP_SECTION_ORDER
|
||||
from user_store import SUBSCRIPTION_PRICE_STARS
|
||||
from user_store import SUBSCRIPTION_PLANS
|
||||
|
||||
MODE_BUTTONS: dict[str, str] = {
|
||||
"🎓 Обучение": "/training",
|
||||
@@ -118,4 +118,4 @@ def resolve_button_text(text: str | None) -> str | None:
|
||||
|
||||
|
||||
def price_hint() -> str:
|
||||
return f"Продление подписки — {SUBSCRIPTION_PRICE_STARS} звёзд."
|
||||
return f"Продление подписки — {SUBSCRIPTION_PLANS[30]} ⭐ за 30 дней."
|
||||
+11
-3
@@ -33,12 +33,20 @@ INVOICE_PREFIX = "sub:"
|
||||
|
||||
|
||||
def plan_for_payload(payload: str) -> tuple[int, int] | None:
|
||||
if payload.startswith("sub30:"):
|
||||
try:
|
||||
days = int(payload.split(":", 2)[1])
|
||||
except (IndexError, ValueError):
|
||||
timestamp = payload.split(":", 1)[1]
|
||||
datetime.datetime.strptime(timestamp, "%Y%m%d%H%M%S")
|
||||
except (ValueError, IndexError):
|
||||
return None
|
||||
if days == 30 and payload.startswith("sub30:"):
|
||||
return (30, SUBSCRIPTION_PLANS[30])
|
||||
try:
|
||||
prefix, days_part, timestamp = payload.split(":", 2)
|
||||
if prefix != "sub" or not timestamp.isdigit():
|
||||
return None
|
||||
days = int(days_part)
|
||||
except (ValueError, IndexError):
|
||||
return None
|
||||
price = SUBSCRIPTION_PLANS.get(days)
|
||||
return (days, price) if price is not None else None
|
||||
|
||||
|
||||
+27
-32
@@ -4,7 +4,7 @@ from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aiogram.types import Chat, Message
|
||||
from aiogram.types import Chat, InlineKeyboardMarkup, Message
|
||||
|
||||
import bot
|
||||
import keyboards
|
||||
@@ -115,7 +115,7 @@ class KeyboardLayoutTests(unittest.TestCase):
|
||||
def test_mode_entry_copy_uses_short_paragraphs(self):
|
||||
self.assertIn("\n", bot.MODE_COPY["training"][1])
|
||||
self.assertIn("\n", bot.MODE_COPY["generation"][1])
|
||||
self.assertIn("\n", bot.MODE_COPY["subscription"][1])
|
||||
self.assertIn("Подписка", bot.MODE_COPY["subscription"][0])
|
||||
|
||||
def test_training_reset_button_routes_to_confirmation_action(self):
|
||||
self.assertEqual(
|
||||
@@ -148,6 +148,7 @@ class _StubMessage:
|
||||
|
||||
async def answer(self, text, **kwargs):
|
||||
self.answers.append((text, kwargs))
|
||||
return SimpleNamespace(message_id=1)
|
||||
|
||||
|
||||
class StyleCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
@@ -233,36 +234,37 @@ class ModeRoutingTests(unittest.IsolatedAsyncioTestCase):
|
||||
state = _StubState("lobby")
|
||||
with (
|
||||
patch.object(bot, "notify_development_deploy", new=AsyncMock()),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock(return_value=None)),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock(return_value={"telegram_user_id":42, "trial_started_at":"2026-10-19T00:00:00", "last_paid_at":None, "is_active":1})),
|
||||
):
|
||||
await bot.handle_text(message, state)
|
||||
|
||||
self.assertEqual(await state.get_state(), bot.BotMode.subscription.state)
|
||||
self.assertEqual(len(message.answers), 2)
|
||||
initial_keyboard = message.answers[0][1]["reply_markup"]
|
||||
self.assertEqual(initial_keyboard.keyboard[0][0].text, keyboards.BUY_SUBSCRIPTION_BUTTON)
|
||||
self.assertIn("Подписка не активирована", message.answers[1][0])
|
||||
buy_keyboard = message.answers[1][1]["reply_markup"]
|
||||
self.assertFalse(hasattr(buy_keyboard, "inline_keyboard"))
|
||||
self.assertEqual(buy_keyboard.keyboard[0][0].text, keyboards.BUY_SUBSCRIPTION_BUTTON)
|
||||
self.assertEqual(
|
||||
[button.text for row in initial_keyboard.keyboard for button in row],
|
||||
[keyboards.BUY_SUBSCRIPTION_BUTTON, keyboards.MAIN_MENU_BUTTON],
|
||||
)
|
||||
self.assertIn("Подписка активна", message.answers[1][0])
|
||||
self.assertIn("Закончится через", message.answers[1][0])
|
||||
|
||||
async def test_subscription_status_uses_renew_button_after_previous_payment(self):
|
||||
message = _StubMessage()
|
||||
sub = {"last_paid_at": "2026-01-01T00:00:00", "expires_at": "2026-02-01T00:00:00", "is_active": 1}
|
||||
with patch.object(bot.user_store, "_days_until", return_value=0):
|
||||
with patch.object(bot.user_store, "_days_until", return_value=2):
|
||||
await bot._show_subscription_info(cast(Any, message), cast(Any, sub))
|
||||
markup = message.answers[0][1]["reply_markup"]
|
||||
self.assertFalse(hasattr(markup, "inline_keyboard"))
|
||||
self.assertEqual(markup.keyboard[0][0].text, keyboards.RENEW_SUBSCRIPTION_BUTTON)
|
||||
self.assertIn("Подписка активна", message.answers[0][0])
|
||||
self.assertIn("Закончится через", message.answers[0][0])
|
||||
|
||||
async def test_subscription_action_button_sends_invoice_in_subscription_mode(self):
|
||||
async def test_subscription_action_button_shows_inline_plan_choices(self):
|
||||
message = _StubMessage(keyboards.BUY_SUBSCRIPTION_BUTTON)
|
||||
with (
|
||||
patch.object(bot, "notify_development_deploy", new=AsyncMock()),
|
||||
patch.object(bot, "_send_subscription_invoice", new=AsyncMock()) as send_invoice,
|
||||
):
|
||||
with patch.object(bot, "notify_development_deploy", new=AsyncMock()):
|
||||
await bot.handle_text(message, _StubState("subscription"))
|
||||
send_invoice.assert_awaited_once_with(message, 42)
|
||||
self.assertEqual(len(message.answers), 1)
|
||||
self.assertEqual(message.answers[0][0], "Выберите срок подписки")
|
||||
labels = [button.text for row in message.answers[0][1]["reply_markup"].inline_keyboard for button in row]
|
||||
self.assertEqual(labels, ["1 день — 15 ⭐", "10 дней — 150 ⭐", "30 дней — 450 ⭐"])
|
||||
|
||||
async def test_subscription_action_button_is_rejected_outside_subscription_mode(self):
|
||||
message = _StubMessage(keyboards.RENEW_SUBSCRIPTION_BUTTON)
|
||||
@@ -274,24 +276,17 @@ class ModeRoutingTests(unittest.IsolatedAsyncioTestCase):
|
||||
send_invoice.assert_not_awaited()
|
||||
self.assertIn("доступна в разделе «Подписка»", message.answers[0][0])
|
||||
|
||||
async def test_renew_callback_sends_invoice_without_intermediate_message(self):
|
||||
async def test_renew_callback_shows_plan_choices(self):
|
||||
message_bot = AsyncMock()
|
||||
message = Message(
|
||||
message_id=91,
|
||||
date=datetime.now(),
|
||||
chat=Chat(id=555, type="private"),
|
||||
).as_(message_bot)
|
||||
callback = SimpleNamespace(
|
||||
message=message,
|
||||
from_user=SimpleNamespace(id=42),
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
with patch.object(bot, "_send_subscription_invoice", new=AsyncMock()) as send_invoice:
|
||||
message = Message(message_id=17, date=datetime.now(), chat=Chat(id=555, type="private")).as_(message_bot)
|
||||
callback = SimpleNamespace(message=message, from_user=SimpleNamespace(id=42), answer=AsyncMock())
|
||||
with patch.object(bot, "current_mode", new=AsyncMock(return_value="subscription")):
|
||||
await bot.subscription_renew(callback, _StubState("subscription"))
|
||||
|
||||
callback.answer.assert_awaited_once_with()
|
||||
send_invoice.assert_awaited_once_with(message, 42)
|
||||
message_bot.assert_not_awaited()
|
||||
self.assertEqual(len(message_bot.mock_calls), 2)
|
||||
sent = message_bot.mock_calls[-1].args[0]
|
||||
self.assertEqual(sent.text, "Выберите срок подписки")
|
||||
self.assertEqual([b.text for row in sent.reply_markup.inline_keyboard for b in row], ["1 день — 15 ⭐", "10 дней — 150 ⭐", "30 дней — 450 ⭐"])
|
||||
|
||||
async def test_idea_command_generates_only_in_generation_mode(self):
|
||||
message = _StubMessage("/idea")
|
||||
|
||||
+33
-35
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -45,16 +46,17 @@ class PaymentStoreTests(unittest.TestCase):
|
||||
|
||||
class InvoiceTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_invoice_uses_stars_and_correct_price(self):
|
||||
for days, price in user_store.SUBSCRIPTION_PLANS.items():
|
||||
with self.subTest(days=days):
|
||||
fake_bot = AsyncMock()
|
||||
await payments.send_subscription_invoice(fake_bot, 555, days=30)
|
||||
await payments.send_subscription_invoice(fake_bot, 555, days=days)
|
||||
kwargs = fake_bot.send_invoice.await_args.kwargs
|
||||
self.assertEqual(kwargs["title"], f"Подписка на {days} дней")
|
||||
self.assertEqual(kwargs["description"], "Доступ к боту по генерации текста постов")
|
||||
self.assertEqual(kwargs["prices"][0].amount, price)
|
||||
self.assertEqual(payments.plan_for_payload(kwargs["payload"]), (days, price))
|
||||
self.assertEqual(kwargs["currency"], "XTR")
|
||||
self.assertEqual(kwargs["provider_token"], "")
|
||||
self.assertEqual(kwargs["chat_id"], 555)
|
||||
self.assertTrue(kwargs["payload"].startswith("sub:30:"))
|
||||
self.assertEqual(kwargs["title"], "Подписка на 30 дней")
|
||||
self.assertEqual(kwargs["description"], "Доступ к боту по генерации текста постов")
|
||||
self.assertEqual(kwargs["prices"][0], LabeledPrice(label="30 дней", amount=450))
|
||||
|
||||
async def test_invoice_includes_thread_when_given(self):
|
||||
fake_bot = AsyncMock()
|
||||
@@ -68,7 +70,7 @@ class InvoiceTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
|
||||
class PreCheckoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
def _query(self, currency="XTR", amount=450, payload="sub:30:1"):
|
||||
def _query(self, currency="XTR", amount=450, payload="sub:30:20261001120000"):
|
||||
query = AsyncMock()
|
||||
query.currency = currency
|
||||
query.total_amount = amount
|
||||
@@ -76,7 +78,7 @@ class PreCheckoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
return query
|
||||
|
||||
async def test_accepts_valid_subscription_invoice(self):
|
||||
query = self._query(payload="sub:30:1")
|
||||
query = self._query(payload="sub:30:20261001120000")
|
||||
await payments.handle_pre_checkout(query)
|
||||
query.answer.assert_awaited_once_with(ok=True)
|
||||
|
||||
@@ -112,6 +114,8 @@ class _StubMessage:
|
||||
self.successful_payment = payment
|
||||
self.from_user = SimpleNamespace(id=user_id)
|
||||
self.chat = SimpleNamespace(id=chat_id)
|
||||
self.message_thread_id = None
|
||||
self.bot = AsyncMock()
|
||||
self.answers: list[str] = []
|
||||
self.answer_kwargs: list[dict] = []
|
||||
|
||||
@@ -121,7 +125,7 @@ class _StubMessage:
|
||||
return self
|
||||
|
||||
|
||||
def _paid_message(charge_id="charge1", amount=450, currency="XTR", payload="sub:30:1"):
|
||||
def _paid_message(charge_id="charge1", amount=450, currency="XTR", payload="sub:30:20261001120000"):
|
||||
payment = SuccessfulPayment(
|
||||
currency=currency,
|
||||
total_amount=amount,
|
||||
@@ -200,53 +204,47 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
return message
|
||||
|
||||
async def test_subscribe_sends_invoice_and_never_activates_directly(self):
|
||||
from pathlib import Path
|
||||
|
||||
source = Path(bot.__file__).read_text(encoding="utf-8")
|
||||
body = source.split("async def subscribe_command")[1].split("\nasync def ")[0]
|
||||
self.assertNotIn("activate_subscription", body)
|
||||
|
||||
message = self._message()
|
||||
with (
|
||||
patch.object(bot, "DEPLOY_ENV", "production"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()),
|
||||
patch.object(bot, "send_subscription_invoice", new=AsyncMock()) as invoice,
|
||||
):
|
||||
await bot.subscribe_command(message, _StubState())
|
||||
invoice.assert_awaited_once_with(message.bot, 555, None)
|
||||
self.assertIn("Счёт на подписку", message.answers[0])
|
||||
self.assertIn("ID пользователя: <code>42</code>", message.answers[0])
|
||||
self.assertEqual(len(message.answers), 2)
|
||||
self.assertNotIn("dev", " ".join(message.answers).lower())
|
||||
self.assertNotIn("reply_markup", message.answer_kwargs[-1])
|
||||
await bot._send_subscription_invoice(message, 42, days=30)
|
||||
invoice.assert_awaited_once_with(message.bot, 555, None, days=30)
|
||||
self.assertEqual(len(message.answers), 0)
|
||||
|
||||
async def test_dev_subscribe_shows_manual_confirmation_button(self):
|
||||
async def test_dev_subscribe_shows_selected_days_and_explains_test_button(self):
|
||||
message = self._message()
|
||||
with (
|
||||
patch.object(bot, "DEPLOY_ENV", "development"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()),
|
||||
patch.object(bot, "send_subscription_invoice", new=AsyncMock()),
|
||||
patch.object(bot, "send_subscription_invoice", new=AsyncMock()) as invoice,
|
||||
):
|
||||
await bot.subscribe_command(message, _StubState())
|
||||
self.assertIn("без реального платежа", message.answers[-1])
|
||||
self.assertEqual(len(message.answers), 2)
|
||||
self.assertIn("Счёт", message.answers[0])
|
||||
await bot._send_subscription_invoice(message, 42, days=10)
|
||||
invoice.assert_awaited_once_with(message.bot, 555, None, days=10)
|
||||
self.assertEqual(len(message.answers), 1)
|
||||
self.assertIn("имитирует оплату", message.answers[0])
|
||||
markup = message.answer_kwargs[-1]["reply_markup"]
|
||||
self.assertEqual(markup.inline_keyboard[0][0].text, "🧪 Оплатил (dev)")
|
||||
self.assertIn("ID пользователя: <code>42</code>", message.answers[0])
|
||||
self.assertEqual(markup.inline_keyboard[0][0].text, "🧪 Оплатил (dev, 10 дн.)")
|
||||
self.assertEqual(markup.inline_keyboard[0][0].callback_data, "dev_paid:42:10")
|
||||
|
||||
async def test_dev_confirmation_activates_only_for_invoice_user(self):
|
||||
async def test_dev_confirmation_activates_selected_term_and_replies_with_status(self):
|
||||
callback = AsyncMock()
|
||||
callback.data = "dev_paid:42"
|
||||
callback.data = "dev_paid:42:10"
|
||||
callback.from_user = SimpleNamespace(id=42)
|
||||
callback.message = None
|
||||
db = _db()
|
||||
user_store.ensure_subscription(42, db)
|
||||
with (
|
||||
patch.object(bot, "DEPLOY_ENV", "development"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock(return_value=30)) as to_thread,
|
||||
patch.dict(os.environ, {"BOT_DB_PATH": db}),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock(return_value=32)) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
self.assertEqual(to_thread.await_args.args[0], user_store.activate_subscription)
|
||||
callback.answer.assert_awaited_once_with()
|
||||
os.unlink(db)
|
||||
self.assertEqual(to_thread.await_args.args, (user_store.activate_subscription_days, 42, 10))
|
||||
callback.answer.assert_awaited_once_with("Dev-оплата подтверждена.")
|
||||
|
||||
async def test_dev_confirmation_rejects_other_user(self):
|
||||
callback = AsyncMock()
|
||||
|
||||
@@ -9,7 +9,6 @@ import apinex_client
|
||||
import bot
|
||||
import user_store
|
||||
|
||||
|
||||
class _StubState:
|
||||
def __init__(self, mode="lobby"):
|
||||
self.value = bot.MODE_STATES[mode].state
|
||||
@@ -995,8 +994,8 @@ class SubscriptionTests(unittest.IsolatedAsyncioTestCase):
|
||||
# Подписка не активируется до реальной оплаты.
|
||||
sub = user_store.subscription_row(102, self.db_path)
|
||||
self.assertIsNone(sub["last_paid_at"])
|
||||
msg_calls = [c.args[0] for c in message.answer.call_args_list]
|
||||
self.assertTrue(any("Счёт" in m for m in msg_calls))
|
||||
invoice.assert_awaited_once_with(message.bot, 555, None, days=30)
|
||||
message.answer.assert_not_awaited()
|
||||
|
||||
|
||||
async def test_balance_command_shows_subscription_info(self):
|
||||
@@ -1012,8 +1011,8 @@ class SubscriptionTests(unittest.IsolatedAsyncioTestCase):
|
||||
with patch.object(bot.asyncio, "to_thread", side_effect=run_in_thread):
|
||||
await bot.balance_command(message, _StubState("subscription"))
|
||||
|
||||
self.assertIn("осталось", message.answer.call_args.args[0].lower())
|
||||
self.assertIn("пробный период", message.answer.call_args.args[0].lower())
|
||||
self.assertIn("Подписка активна", message.answer.call_args.args[0])
|
||||
self.assertNotIn("пробный период", message.answer.call_args.args[0].lower())
|
||||
renew_markup = message.answer.call_args.kwargs["reply_markup"]
|
||||
self.assertEqual(
|
||||
renew_markup.keyboard[0][0].text,
|
||||
@@ -1038,8 +1037,8 @@ class SubscriptionTests(unittest.IsolatedAsyncioTestCase):
|
||||
|
||||
# ответ должен быть о блокировке, а не о генерации
|
||||
msg_calls = [c.args[0] for c in message.answer.call_args_list]
|
||||
self.assertTrue(any("истёк" in msg for msg in msg_calls))
|
||||
self.assertTrue(any("звёзд" in msg for msg in msg_calls))
|
||||
self.assertIn("Подписка закончилась", " ".join(msg_calls))
|
||||
self.assertFalse(any("звёзд" in msg for msg in msg_calls))
|
||||
|
||||
async def test_clear_preserves_subscription(self):
|
||||
user_store.ensure_subscription(105, self.db_path)
|
||||
|
||||
Reference in new issue
Block a user