infrasynth-backend-kit/tests/test_billing/test_views.py
2026-08-28 14:38:47 -05:00

222 lines
7.4 KiB
Python

import pytest
from infrasynth.billing.models import (
BillingPlan,
Invoice,
PaymentGateway,
PaymentTransaction,
Subscription,
)
from infrasynth.features.models import FeatureFlag
from infrasynth.shared.enums import SubscriptionStatus
pytestmark = pytest.mark.django_db
GATEWAYS_URL = "/api/billing/gateways/"
PLANS_URL = "/api/billing/plans/"
SUBSCRIPTIONS_URL = "/api/billing/subscriptions/"
INVOICES_URL = "/api/billing/invoices/"
TRANSACTIONS_URL = "/api/billing/transactions/"
WEBHOOK_URL = "/api/billing/webhook/receive/"
@pytest.fixture
def billing_enabled():
return FeatureFlag.objects.create(slug="billing", name="Billing", is_active=True)
@pytest.fixture
def gateway(billing_enabled):
return PaymentGateway.objects.create(
slug="fake",
display_name="Fake",
gateway_class="tests.helpers.FakeGateway",
is_active=True,
)
@pytest.fixture
def plan(gateway):
return BillingPlan.objects.create(
slug="pro",
name="Pro",
price_amount="49.00",
price_currency="USD",
interval="monthly",
gateway=gateway,
external_id="price_123",
)
@pytest.fixture
def subscription(user, plan, gateway):
return Subscription.objects.create(
user=user,
plan=plan,
gateway=gateway,
external_id="sub_1",
status=SubscriptionStatus.ACTIVE,
)
class TestFeatureGate:
def test_disabled_feature_returns_404(self, authenticated_client):
assert authenticated_client.get(GATEWAYS_URL).status_code == 404
assert authenticated_client.get(PLANS_URL).status_code == 404
assert authenticated_client.get(SUBSCRIPTIONS_URL).status_code == 404
class TestPaymentGatewayViewSet:
def test_list_gateways(self, authenticated_client, gateway):
response = authenticated_client.get(GATEWAYS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["slug"] == "fake"
def test_create_gateway(self, authenticated_client, billing_enabled):
response = authenticated_client.post(
GATEWAYS_URL,
{
"slug": "stripe",
"display_name": "Stripe",
"gateway_class": "infrasynth.billing.gateways.stripe.StripeGateway",
},
format="json",
)
assert response.status_code == 201
assert PaymentGateway.objects.count() == 1
def test_requires_auth(self, api_client, gateway):
assert api_client.get(GATEWAYS_URL).status_code == 401
class TestBillingPlanViewSet:
def test_list_plans_public(self, api_client, plan):
response = api_client.get(PLANS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["slug"] == "pro"
def test_retrieve_plan(self, api_client, plan):
response = api_client.get(f"{PLANS_URL}{plan.id}/")
assert response.status_code == 200
assert response.data["price_amount"] == "49.00"
assert response.data["gateway_info"]["slug"] == "fake"
def test_retrieve_inactive_plan_404(self, api_client, plan):
plan.is_active = False
plan.save(update_fields=["is_active"])
assert api_client.get(f"{PLANS_URL}{plan.id}/").status_code == 404
class TestSubscriptionViewSet:
def test_list_subscriptions(self, authenticated_client, subscription):
response = authenticated_client.get(SUBSCRIPTIONS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["status"] == "active"
def test_requires_auth(self, api_client, subscription):
assert api_client.get(SUBSCRIPTIONS_URL).status_code == 401
def test_subscribe_action(self, authenticated_client, plan):
response = authenticated_client.post(
f"{SUBSCRIPTIONS_URL}subscribe/",
{"plan_slug": "pro"},
format="json",
)
assert response.status_code == 200
assert response.data["checkout_url"] == "https://checkout.example.com/cs_fake_123"
assert response.data["session_id"] == "cs_fake_123"
assert response.data["gateway"] == "fake"
def test_subscribe_invalid_plan(self, authenticated_client):
response = authenticated_client.post(
f"{SUBSCRIPTIONS_URL}subscribe/",
{"plan_slug": "nope"},
format="json",
)
assert response.status_code == 404
class TestInvoiceViewSet:
def test_list_invoices(self, authenticated_client, subscription, user):
Invoice.objects.create(
subscription=subscription,
user=user,
invoice_number="INV-1",
amount=49,
currency="USD",
status="open",
)
response = authenticated_client.get(INVOICES_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["invoice_number"] == "INV-1"
def test_requires_auth(self, api_client, subscription, user):
assert api_client.get(INVOICES_URL).status_code == 401
class TestPaymentTransactionViewSet:
def test_list_transactions(self, authenticated_client, subscription, user):
PaymentTransaction.objects.create(
invoice=None,
gateway=subscription.gateway,
amount=49,
currency="USD",
status="approved",
)
response = authenticated_client.get(TRANSACTIONS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["status"] == "approved"
def test_requires_auth(self, api_client, billing_enabled):
assert api_client.get(TRANSACTIONS_URL).status_code == 401
class TestWebhookReceiveView:
def test_receive_processed(self, api_client, gateway):
response = api_client.post(
WEBHOOK_URL,
{
"gateway_slug": "fake",
"event": "checkout.session.completed",
"data": {"id": "cs_1"},
},
format="json",
)
assert response.status_code == 200
assert response.data["status"] == "processed"
assert response.data["event_type"] == "checkout.session.completed"
def test_receive_ignored_event(self, api_client, gateway):
response = api_client.post(
WEBHOOK_URL,
{"gateway_slug": "fake", "event": "ignored", "data": {}},
format="json",
)
assert response.status_code == 200
assert response.data["status"] == "ignored"
def test_receive_uses_first_active_gateway(self, api_client, gateway):
response = api_client.post(
WEBHOOK_URL,
{"event": "checkout.session.completed", "data": {}},
format="json",
)
assert response.status_code == 200
assert response.data["event_type"] == "checkout.session.completed"
def test_receive_no_gateway_404(self, api_client):
response = api_client.post(WEBHOOK_URL, {"event": "x", "data": {}}, format="json")
assert response.status_code == 404
def test_receive_is_public(self, api_client, gateway):
response = api_client.post(
WEBHOOK_URL,
{"gateway_slug": "fake", "event": "e", "data": {}},
format="json",
)
assert response.status_code == 200