feat: introduced pytest powered tests
This commit is contained in:
106
tests/test_services_and_payments.py
Normal file
106
tests/test_services_and_payments.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# ruff: noqa: PLR2004
|
||||
|
||||
import hashlib
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from external.pally import BillStatus
|
||||
from schemas.plans import AddonData, PricingPlans
|
||||
from services.payments import validate_pally_signature
|
||||
from services.subscriptions import calculate_order_total, should_apply_immediately
|
||||
|
||||
|
||||
def pricing():
|
||||
return PricingPlans(
|
||||
device_price=100,
|
||||
addons=[
|
||||
AddonData(id="a", name="A", price=25, free_threshold=0, is_enabled=True),
|
||||
AddonData(id="b", name="B", price=50, free_threshold=0, is_enabled=True),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_price_calculation_handles_fractional_months_and_unknown_addons():
|
||||
assert calculate_order_total(pricing(), devices=2, addon_ids=["a"], duration_days=15) == 112.5
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
calculate_order_total(pricing(), devices=1, addon_ids=["missing"], duration_days=30)
|
||||
|
||||
|
||||
def test_should_apply_immediately_distinguishes_upgrade_from_downgrade():
|
||||
now = datetime.now(UTC)
|
||||
subscription = SimpleNamespace(
|
||||
devices=2,
|
||||
expires_at=now + timedelta(days=10),
|
||||
addons=[SimpleNamespace(addon_id="a")],
|
||||
)
|
||||
upgrade = SimpleNamespace(
|
||||
devices=3, addons=[SimpleNamespace(addon_id="a"), SimpleNamespace(addon_id="b")]
|
||||
)
|
||||
downgrade = SimpleNamespace(devices=1, addons=[SimpleNamespace(addon_id="a")])
|
||||
|
||||
assert should_apply_immediately(
|
||||
order=upgrade, subscription=subscription, pricing=pricing(), now=now
|
||||
)
|
||||
assert not should_apply_immediately(
|
||||
order=downgrade, subscription=subscription, pricing=pricing(), now=now
|
||||
)
|
||||
assert should_apply_immediately(order=downgrade, subscription=None, pricing=pricing(), now=now)
|
||||
|
||||
|
||||
def test_pally_signature_is_exact_and_case_sensitive():
|
||||
signature = hashlib.md5(b"10:42:test-token").hexdigest().upper()
|
||||
with patch("services.payments.cfg.pally_token", "test-token"):
|
||||
assert validate_pally_signature("10", "42", signature)
|
||||
assert not validate_pally_signature("10", "42", signature.lower())
|
||||
assert not validate_pally_signature("11", "42", signature)
|
||||
|
||||
|
||||
def callback_data(**overrides):
|
||||
data = {
|
||||
"InvId": "42",
|
||||
"OutSum": "10",
|
||||
"Commission": "0",
|
||||
"TrsId": "transaction",
|
||||
"Status": BillStatus.SUCCESS,
|
||||
"CurrencyIn": "RUB",
|
||||
"SignatureValue": "valid",
|
||||
}
|
||||
data.update(overrides)
|
||||
return data
|
||||
|
||||
|
||||
def test_callback_rejects_invalid_signature_before_processing(client):
|
||||
with patch("routes.payments.pally.validate_pally_signature", return_value=False):
|
||||
response = client.post("/payments/pally/result", data=callback_data())
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json()["detail"] == "Invalid signature."
|
||||
|
||||
|
||||
def test_callback_acknowledges_valid_non_success_and_non_numeric_invoice(client):
|
||||
with patch("routes.payments.pally.validate_pally_signature", return_value=True):
|
||||
failed_response = client.post("/payments/pally/result", data=callback_data(Status="Failed"))
|
||||
invalid_id_response = client.post(
|
||||
"/payments/pally/result", data=callback_data(InvId="order-42")
|
||||
)
|
||||
|
||||
assert failed_response.status_code == invalid_id_response.status_code == 200
|
||||
assert failed_response.json() == invalid_id_response.json() == "OK"
|
||||
|
||||
|
||||
def test_callback_uses_balance_amount_when_present(client):
|
||||
process = AsyncMock()
|
||||
with (
|
||||
patch("routes.payments.pally.validate_pally_signature", return_value=True),
|
||||
patch("routes.payments.pally.process_subscription_purchase", process),
|
||||
):
|
||||
response = client.post("/payments/pally/result", data=callback_data(BalanceAmount="7.9"))
|
||||
|
||||
assert response.status_code == 200
|
||||
process.assert_awaited_once()
|
||||
assert process.await_args.kwargs["invoice_id"] == 42
|
||||
assert process.await_args.kwargs["amount"] == 7
|
||||
Reference in New Issue
Block a user