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

204 lines
6.9 KiB
Python

import uuid
import pytest
from django.dispatch import receiver
from django.test import override_settings
from infrasynth.audit.models import SecurityEvent
from infrasynth.billing.models import BillingPlan, PaymentGateway
from infrasynth.billing.services import BillingService
from infrasynth.billing.signals import (
invoice_generated,
subscription_cancelled,
subscription_created,
)
from infrasynth.notifications.models import ChannelConfig, NotificationDispatch, NotificationTemplate
from infrasynth.notifications.services import NotificationService
from infrasynth.shared.enums import ChannelType
from infrasynth.webhooks.signals import outbound_delivery_succeeded
pytestmark = pytest.mark.django_db
@pytest.fixture
def gateway():
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 notification_template():
return NotificationTemplate.objects.create(
slug="sub-created",
name="Subscription Created",
channel=ChannelType.EMAIL,
subject_template="Your subscription is active",
body_template="Hello, your {{ plan }} plan is now active.",
is_html=False,
)
@pytest.fixture
def channel_config():
return ChannelConfig.objects.create(
slug="email",
channel_type=ChannelType.EMAIL,
display_name="Email Channel",
config={},
is_active=True,
)
class TestSubscriptionCreatedSignalIntegration:
def test_subscription_created_signal_integration(self, user, plan, gateway):
sent = []
receiver_fn = lambda **kw: sent.append(kw) # noqa: E731
subscription_created.connect(receiver_fn, weak=False)
try:
BillingService().create_subscription(plan, user, gateway, external_id="sub_int_1")
assert len(sent) == 1
assert sent[0]["user"] == user
assert sent[0]["plan_slug"] == "pro"
assert sent[0]["gateway"] == "fake"
assert sent[0]["external_id"] == "sub_int_1"
assert sent[0]["signal"] is subscription_created
finally:
subscription_created.disconnect(receiver_fn)
class TestBillingSignalTriggersNotificationDispatch:
@override_settings(
INFRASYNTH_NOTIFICATIONS={
"DISPATCH_BACKEND": "celery",
"CHANNELS": {
"email": {
"primary": "infrasynth.notifications.channels.email_smtp.SMTPChannel",
},
},
}
)
def test_billing_signal_triggers_notification_dispatch(
self, user, plan, gateway, notification_template, channel_config
):
dispatch_created = []
@receiver(subscription_created, weak=False)
def handle_sub_created(**kwargs):
svc = NotificationService()
d = svc.send(
recipient=kwargs["user"].email,
subject="Subscription Active",
body="Your subscription is active.",
channel=ChannelType.EMAIL,
template=notification_template,
context={"plan": kwargs["plan_slug"]},
)
dispatch_created.append(d)
try:
BillingService().create_subscription(plan, user, gateway, external_id="sub_notif_1")
assert len(dispatch_created) == 1
dispatch = dispatch_created[0]
assert isinstance(dispatch, NotificationDispatch)
assert dispatch.recipient == user.email
assert dispatch.channel == ChannelType.EMAIL
assert dispatch.template == notification_template
assert dispatch.status == NotificationDispatch.Status.PENDING
finally:
subscription_created.disconnect(handle_sub_created)
class TestWebhookSignalTriggersAuditLog:
def test_webhook_signal_triggers_audit_log(self, user):
events_created = []
@receiver(outbound_delivery_succeeded, weak=False)
def create_security_event(**kwargs):
event = SecurityEvent.objects.create(
event_type="webhook_delivery_succeeded",
actor=user,
metadata={
"delivery_id": kwargs["delivery_id"],
"event_name": kwargs.get("event_name", ""),
"status_code": kwargs.get("status_code", 0),
},
request_id=str(uuid.uuid4()),
)
events_created.append(event)
try:
outbound_delivery_succeeded.send(
sender=None,
delivery_id=42,
event_name="subscription.created",
status_code=200,
)
assert len(events_created) == 1
event = SecurityEvent.objects.get(pk=events_created[0].pk)
assert event.event_type == "webhook_delivery_succeeded"
assert event.actor == user
assert event.metadata["delivery_id"] == 42
assert event.metadata["event_name"] == "subscription.created"
assert event.metadata["status_code"] == 200
finally:
outbound_delivery_succeeded.disconnect(create_security_event)
class TestSubscriptionCancelledSignal:
def test_subscription_cancelled_signal(self, user, plan, gateway):
subscription = BillingService().create_subscription(plan, user, gateway, external_id="sub_to_cancel")
sent = []
receiver_fn = lambda **kw: sent.append(kw) # noqa: E731
subscription_cancelled.connect(receiver_fn, weak=False)
try:
BillingService().cancel_subscription(subscription)
assert len(sent) == 1
assert sent[0]["user"] == user
assert sent[0]["plan_slug"] == "pro"
assert sent[0]["reason"] == "user_requested"
assert sent[0]["signal"] is subscription_cancelled
finally:
subscription_cancelled.disconnect(receiver_fn)
class TestInvoiceGeneratedSignal:
def test_invoice_generated_signal(self, user, plan, gateway):
subscription = BillingService().create_subscription(plan, user, gateway, external_id="sub_inv_1")
sent = []
receiver_fn = lambda **kw: sent.append(kw) # noqa: E731
invoice_generated.connect(receiver_fn, weak=False)
try:
invoice = BillingService().generate_invoice(subscription)
assert len(sent) == 1
assert sent[0]["invoice_id"] == invoice.id
assert sent[0]["amount"] == 49.0
assert sent[0]["signal"] is invoice_generated
finally:
invoice_generated.disconnect(receiver_fn)