Files
remnawave-minishop/tests/test_webapp_payment_status.py
T
3252a8 fbb89793cb fix: separate HWID device renewal flows
Keep one-off device top-ups scoped to the active subscription term and move device renewal into subscription checkout.

Carry HWID renewal metadata through provider callbacks and webhooks, including YooKassa saved-card flows.

Add admin extension controls, docs, demo data, and regression coverage.
2026-06-03 23:51:46 +03:00

194 lines
6.7 KiB
Python

from types import SimpleNamespace
from unittest import IsolatedAsyncioTestCase
from unittest.mock import AsyncMock, patch
import bot.app.web.subscription_webapp # noqa: F401
from bot.app.web.webapp import billing as billing_module
from bot.payment_providers.base import WebAppPaymentContext
from bot.payment_providers.yookassa import create_webapp_payment
class _SessionFactory:
def __call__(self):
return self
async def __aenter__(self):
return SimpleNamespace()
async def __aexit__(self, exc_type, exc, tb):
return False
class WebAppPaymentStatusTests(IsolatedAsyncioTestCase):
async def test_yookassa_pending_payment_refresh_processes_succeeded_provider_status(self):
payment = SimpleNamespace(
payment_id=42,
user_id=1001,
provider="yookassa",
status="pending_yookassa",
yookassa_payment_id="yk_42",
provider_payment_id=None,
)
refreshed_payment = SimpleNamespace(
payment_id=42,
user_id=1001,
provider="yookassa",
status="succeeded",
yookassa_payment_id="yk_42",
provider_payment_id=None,
)
yookassa_service = SimpleNamespace(
configured=True,
get_payment_info=AsyncMock(
return_value={
"id": "yk_42",
"status": "succeeded",
"paid": True,
"amount_value": 100.0,
"amount_currency": "RUB",
"metadata": {"user_id": "1001", "payment_db_id": "42"},
}
),
)
request = SimpleNamespace(
app={
"bot": SimpleNamespace(),
"i18n": SimpleNamespace(),
"settings": SimpleNamespace(),
"panel_service": SimpleNamespace(),
"subscription_service": SimpleNamespace(),
"referral_service": SimpleNamespace(),
"lknpd_service": None,
"yookassa_service": yookassa_service,
}
)
session = AsyncMock()
with (
patch.object(
billing_module.payment_dal,
"get_payment_by_db_id",
AsyncMock(side_effect=[payment, refreshed_payment]),
),
patch(
"bot.payment_providers.yookassa.process_successful_payment",
AsyncMock(),
) as process_success,
):
result = await billing_module._refresh_yookassa_payment_status(
request,
session,
payment,
)
self.assertIs(result, refreshed_payment)
session.commit.assert_awaited_once()
process_success.assert_awaited_once()
provider_payload = process_success.await_args.args[2]
self.assertEqual(provider_payload["amount"], {"value": "100.0", "currency": "RUB"})
async def test_yookassa_webapp_payment_uses_unrestricted_checkout_form(self):
payment_record = SimpleNamespace(payment_id=77)
yookassa_service = SimpleNamespace(
configured=True,
config=SimpleNamespace(
DEFAULT_RECEIPT_EMAIL="receipt@example.test",
autopayments_active=True,
AUTOPAYMENTS_REQUIRE_CARD_BINDING=True,
),
create_payment=AsyncMock(
return_value={
"id": "yk_77",
"status": "pending",
"confirmation_url": "https://yookassa.example/pay",
}
),
)
session = AsyncMock()
ctx = WebAppPaymentContext(
request=SimpleNamespace(app={"yookassa_service": yookassa_service}),
session=session,
user_id=1001,
method="yookassa",
months=1,
price=100.0,
stars_price=None,
description="Subscription",
sale_mode="subscription",
)
with (
patch(
"bot.payment_providers.yookassa.create_webapp_payment_record",
AsyncMock(return_value=payment_record),
),
patch(
"bot.payment_providers.yookassa.payment_dal.update_payment_status_by_db_id",
AsyncMock(),
),
):
response = await create_webapp_payment(ctx)
self.assertEqual(response.status, 200)
yookassa_service.create_payment.assert_awaited_once()
self.assertIs(
yookassa_service.create_payment.await_args.kwargs["save_payment_method"],
False,
)
async def test_payment_status_invalidates_profile_cache_for_succeeded_payment(self):
settings = SimpleNamespace()
payment = SimpleNamespace(payment_id=42, user_id=1001, status="succeeded")
request = SimpleNamespace(
app={"settings": settings, "async_session_factory": _SessionFactory()},
match_info={"payment_id": "42"},
)
with (
patch.object(billing_module, "_require_user_id", return_value=1001),
patch.object(
billing_module.payment_dal,
"get_payment_by_db_id",
AsyncMock(return_value=payment),
),
patch.object(
billing_module,
"_refresh_yookassa_payment_status",
AsyncMock(return_value=payment),
),
patch.object(
billing_module,
"invalidate_webapp_user_caches",
AsyncMock(),
) as invalidate_cache,
):
response = await billing_module.payment_status_route(request)
invalidate_cache.assert_awaited_once_with(settings, 1001, include_devices=True)
self.assertEqual(response.status, 200)
async def test_wata_pending_payment_refresh_delegates_to_provider_service(self):
payment = SimpleNamespace(
payment_id=43,
user_id=1001,
provider="wata",
status="pending_wata",
)
refreshed_payment = SimpleNamespace(
payment_id=43,
user_id=1001,
provider="wata",
status="succeeded",
)
wata_service = SimpleNamespace(
configured=True,
refresh_payment_status=AsyncMock(return_value=refreshed_payment),
)
request = SimpleNamespace(app={"wata_service": wata_service})
session = AsyncMock()
result = await billing_module._refresh_wata_payment_status(request, session, payment)
self.assertIs(result, refreshed_payment)
wata_service.refresh_payment_status.assert_awaited_once_with(session, payment)