infrasynth-backend-kit/tests/test_billing/test_views.py
jcv-dev 551b42eab5 feat: production-hardening pass across the kit
Close the gaps between the documented contract (API-STANDARD, TENANCY,
ENTITLEMENTS) and the implementation, and remove committed build artifacts.

Security:
- verify + process inbound webhooks (HMAC/handler verify, size limit,
  timestamp tolerance, idempotency via InboundEvent.external_id)
- real 2FA login flow (pre-auth challenge; tokens only after verify/recovery)
- wire HybridPermission into security/audit views; add API-key rotate and
  users/<id>/permissions|roles endpoints
- tenant-scoped throttling on by default; webhook replay protection
- verify MercadoPago webhook signatures
- login brute-force guard, configurable password policy, real ALTCHA PoW

Correctness:
- apply verified billing webhooks idempotently (subscription/entitlement/
  invoice/PaymentTransaction); scheduled payment lifecycle jobs
- capture audit update diffs automatically; add audit retention purge
- working notification retries, per-channel rate limits, log retention
- pluggable virus scanner, upload-size limit, pipeline toggle
- feature rollout %/environment targeting; settings-driven registrations
- workflow guards (instance cap, route depth, self-assignment, clone on re-entry)
- wire every previously-dead INFRASYNTH_* setting; drop truly dead ones

Delivery:
- README + CHANGELOG; CI format check + coverage gate
- keep test media out of the tree; untrack .coverage, __pycache__,
  egg-info, docs/ and invoice artifacts
2026-09-24 10:41:21 -05:00

254 lines
9 KiB
Python

import pytest
from infrasynth.billing.models import App, Entitlement, Invoice, PaymentGateway, PaymentTransaction, Plan, Subscription
from infrasynth.features.models import FeatureFlag
from infrasynth.shared.enums import MonetizationModel, SubscriptionStatus
pytestmark = pytest.mark.django_db
GATEWAYS_URL = "/api/v1/billing/gateways/"
APPS_URL = "/api/v1/billing/apps/"
PLANS_URL = "/api/v1/billing/plans/"
ENTITLEMENTS_URL = "/api/v1/billing/entitlements/"
SUBSCRIPTIONS_URL = "/api/v1/billing/subscriptions/"
INVOICES_URL = "/api/v1/billing/invoices/"
TRANSACTIONS_URL = "/api/v1/billing/transactions/"
CHECKOUT_URL = "/api/v1/billing/checkout/"
WEBHOOK_URL = "/api/v1/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 app():
return App.objects.create(slug="messenger", name="Messenger", monetization=MonetizationModel.SUBSCRIPTION)
@pytest.fixture
def plan(app, gateway):
return Plan.objects.create(
app=app,
slug="pro",
name="Pro",
price_amount=4900,
price_currency="USD",
interval="monthly",
gateway=gateway,
external_id="price_123",
)
@pytest.fixture
def subscription(tenant, plan, gateway):
return Subscription.all_objects.create(
tenant=tenant,
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(SUBSCRIPTIONS_URL).status_code == 404
assert authenticated_client.get(INVOICES_URL).status_code == 404
def test_catalog_is_public(self, api_client, plan):
assert api_client.get(APPS_URL).status_code == 200
assert api_client.get(PLANS_URL).status_code == 200
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 TestPlanViewSet:
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.slug}/")
assert response.status_code == 200
assert response.data["price_amount"] == 4900
assert response.data["gateway_info"]["slug"] == "fake"
assert response.data["app_info"]["slug"] == "messenger"
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.slug}/").status_code == 404
class TestEntitlementViewSet:
def test_list_entitlements(self, authenticated_client, tenant, plan):
Entitlement.all_objects.create(tenant=tenant, app=plan.app, plan=plan)
response = authenticated_client.get(ENTITLEMENTS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["app_info"]["slug"] == "messenger"
def test_requires_auth(self, api_client, tenant, plan):
Entitlement.all_objects.create(tenant=tenant, app=plan.app, plan=plan)
assert api_client.get(ENTITLEMENTS_URL).status_code == 401
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
def test_checkout_action(self, authenticated_client, plan):
response = authenticated_client.post(
CHECKOUT_URL,
{"app": "messenger", "plan": "pro"},
format="json",
)
assert response.status_code == 200
assert response.data["checkout_url"] == "https://checkout.example.com/cs_fake_123"
assert response.data["app_slug"] == "messenger"
class TestInvoiceViewSet:
def test_list_invoices(self, authenticated_client, tenant, subscription):
Invoice.all_objects.create(
tenant=tenant,
subscription=subscription,
invoice_number="INV-1",
amount=4900,
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):
assert api_client.get(INVOICES_URL).status_code == 401
class TestPaymentTransactionViewSet:
def test_list_transactions(self, authenticated_client, tenant, subscription):
PaymentTransaction.all_objects.create(
tenant=tenant,
invoice=None,
gateway=subscription.gateway,
amount=4900,
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