DealDocumentScreening/tests/integration/test_billing_checkout.py

145 lines
4.2 KiB
Python

"""Billing checkout integration tests (ticket 012).
Uses respx to mock ЮKassa; enables YOOKASSA_ENABLED in the test env.
"""
from __future__ import annotations
import uuid
import httpx
import pytest
import respx
from sqlalchemy import text
from contract_check.core.db.session import create_session_factory
from tests.integration.conftest import user_token
pytestmark = pytest.mark.integration
def _unique_tg() -> int:
return abs(int(uuid.uuid4().int % 900000000000)) + 100000000000
async def _auth_env(client: httpx.AsyncClient, infra: dict[str, str]) -> tuple[int, str]:
tg = _unique_tg()
token = await user_token(client, infra, tg)
return tg, token
@pytest.fixture
async def yookassa_enabled():
"""Temporarily turn on YOOKASSA_ENABLED for tests."""
import os
old = os.environ.get("YOOKASSA_ENABLED")
os.environ["YOOKASSA_ENABLED"] = "true"
os.environ["YOOKASSA_SHOP_ID"] = "123456"
os.environ["YOOKASSA_SECRET_KEY"] = "test-secret"
os.environ["YOOKASSA_RETURN_BASE_URL"] = "https://example.com/pay/{invoice_id}"
os.environ["PRICE_PER_DOC_KOPECKS"] = "19900"
yield
if old is None:
os.environ.pop("YOOKASSA_ENABLED", None)
else:
os.environ["YOOKASSA_ENABLED"] = old
os.environ.pop("YOOKASSA_SHOP_ID", None)
os.environ.pop("YOOKASSA_SECRET_KEY", None)
os.environ.pop("YOOKASSA_RETURN_BASE_URL", None)
@respx.mock
async def test_checkout_topup_creates_invoice(
client: httpx.AsyncClient,
infra: dict[str, str],
yookassa_enabled,
) -> None:
get_settings.cache_clear()
_, token = await _auth_env(client, infra)
respx.post("https://api.yookassa.ru/v3/payments").mock(
return_value=httpx.Response(
200,
json={
"id": "pay-topup-1",
"status": "pending",
"confirmation": {"confirmation_url": "https://yoo.test/topup-1"},
},
)
)
r = await client.post(
"/api/v1/billing/checkout",
json={"kind": "topup", "credits": 5},
headers={"Authorization": f"Bearer {token}"},
)
assert r.status_code == 201, r.text
body = r.json()
assert body["invoice_id"]
assert body["confirmation_url"] == "https://yoo.test/topup-1"
# Invoice persisted as pending.
factory = create_session_factory()
async with factory() as s:
row = (
await s.execute(
text(
"SELECT amount, kind, credits_purchased, status, external_id, confirmation_url FROM invoices WHERE id = :id"
),
{"id": uuid.UUID(body["invoice_id"])},
)
).first()
assert row is not None
assert int(row[0]) == 5 * 19900
assert row[1] == "topup"
assert row[2] == 5
assert row[3] == "pending"
assert row[4] == "pay-topup-1"
assert row[5] == "https://yoo.test/topup-1"
async def test_checkout_payments_disabled_503(
client: httpx.AsyncClient, infra: dict[str, str]
) -> None:
_, token = await _auth_env(client, infra)
r = await client.post(
"/api/v1/billing/checkout",
json={"kind": "topup", "credits": 1},
headers={"Authorization": f"Bearer {token}"},
)
assert r.status_code == 503
async def test_checkout_invalid_pack_422(
client: httpx.AsyncClient,
infra: dict[str, str],
yookassa_enabled,
) -> None:
get_settings.cache_clear()
_, token = await _auth_env(client, infra)
r = await client.post(
"/api/v1/billing/checkout",
json={"kind": "topup", "credits": 7},
headers={"Authorization": f"Bearer {token}"},
)
assert r.status_code == 422
async def test_plans_catalog_visible_when_payments_off(
client: httpx.AsyncClient,
) -> None:
r = await client.get("/api/v1/billing/plans")
assert r.status_code == 200
body = r.json()
assert len(body["plans"]) == 4
assert body["price_per_doc_kopecks"] == 19900
async def test_invoices_requires_auth(client: httpx.AsyncClient) -> None:
r = await client.get("/api/v1/billing/invoices")
assert r.status_code == 401
# Import needed inside fixture to avoid circular import issues at collection time.
from contract_check.core.config import get_settings # noqa: E402