import pytest from infrasynth.webhooks.models import ( InboundEndpoint, InboundEvent, OutboundDelivery, OutboundEndpoint, OutboundSubscription, ) pytestmark = pytest.mark.django_db OUTBOUND_ENDPOINTS_URL = "/api/webhooks/outbound/endpoints/" OUTBOUND_SUBSCRIPTIONS_URL = "/api/webhooks/outbound/subscriptions/" OUTBOUND_DELIVERIES_URL = "/api/webhooks/outbound/deliveries/" INBOUND_ENDPOINTS_URL = "/api/webhooks/inbound/endpoints/" INBOUND_EVENTS_URL = "/api/webhooks/inbound/events/" @pytest.fixture def endpoint(): return OutboundEndpoint.objects.create( name="Target", url="https://example.com/hook", secret="test-secret", ) @pytest.fixture def subscription(endpoint): return OutboundSubscription.objects.create(endpoint=endpoint, event_name="evt.test") @pytest.fixture def inbound_endpoint(): return InboundEndpoint.objects.create( name="Stripe", slug="stripe", source="stripe", secret="wh-secret", handler="infrasynth.webhooks.inbound.handlers.BaseInboundHandler", ) class TestOutboundEndpointViewSet: def test_list(self, authenticated_client, endpoint): response = authenticated_client.get(OUTBOUND_ENDPOINTS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["name"] == "Target" def test_requires_auth(self, api_client, endpoint): assert api_client.get(OUTBOUND_ENDPOINTS_URL).status_code == 401 def test_create(self, authenticated_client): response = authenticated_client.post( OUTBOUND_ENDPOINTS_URL, { "name": "New", "url": "https://example.com/new", "secret": "s", }, format="json", ) assert response.status_code == 201 assert OutboundEndpoint.objects.filter(name="New").exists() def test_update_and_delete(self, authenticated_client, endpoint): response = authenticated_client.patch( f"{OUTBOUND_ENDPOINTS_URL}{endpoint.id}/", {"name": "Renamed"}, format="json", ) assert response.status_code == 200 response = authenticated_client.delete(f"{OUTBOUND_ENDPOINTS_URL}{endpoint.id}/") assert response.status_code == 204 assert not OutboundEndpoint.objects.filter(pk=endpoint.pk).exists() class TestOutboundSubscriptionViewSet: def test_list(self, authenticated_client, subscription): response = authenticated_client.get(OUTBOUND_SUBSCRIPTIONS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["event_name"] == "evt.test" def test_list_includes_endpoint_info(self, authenticated_client, subscription): response = authenticated_client.get(OUTBOUND_SUBSCRIPTIONS_URL) assert response.data["results"][0]["endpoint_info"]["name"] == "Target" def test_create(self, authenticated_client, endpoint): response = authenticated_client.post( OUTBOUND_SUBSCRIPTIONS_URL, {"endpoint": endpoint.id, "event_name": "evt.new"}, format="json", ) assert response.status_code == 201 assert OutboundSubscription.objects.filter(event_name="evt.new").exists() def test_requires_auth(self, api_client, subscription): assert api_client.get(OUTBOUND_SUBSCRIPTIONS_URL).status_code == 401 def test_duplicate_event_rejected(self, authenticated_client, subscription): response = authenticated_client.post( OUTBOUND_SUBSCRIPTIONS_URL, { "endpoint": subscription.endpoint_id, "event_name": "evt.test", }, format="json", ) assert response.status_code == 400 class TestOutboundDeliveryViewSet: def test_list(self, authenticated_client, subscription): OutboundDelivery.objects.create(subscription=subscription, payload={"a": 1}, status="success") response = authenticated_client.get(OUTBOUND_DELIVERIES_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["payload"] == {"a": 1} def test_does_not_allow_create(self, authenticated_client): assert authenticated_client.post(OUTBOUND_DELIVERIES_URL, {}, format="json").status_code == 405 def test_requires_auth(self, api_client): assert api_client.get(OUTBOUND_DELIVERIES_URL).status_code == 401 class TestInboundEndpointViewSet: def test_list(self, authenticated_client, inbound_endpoint): response = authenticated_client.get(INBOUND_ENDPOINTS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["slug"] == "stripe" def test_create(self, authenticated_client): response = authenticated_client.post( INBOUND_ENDPOINTS_URL, { "name": "GitHub", "slug": "github", "source": "github", "secret": "s", "handler": "helpdesk.webhook_handlers.JiraWebhookHandler", }, format="json", ) assert response.status_code == 201 assert InboundEndpoint.objects.filter(slug="github").exists() def test_requires_auth(self, api_client, inbound_endpoint): assert api_client.get(INBOUND_ENDPOINTS_URL).status_code == 401 class TestInboundEventViewSet: def test_list(self, authenticated_client, inbound_endpoint): InboundEvent.objects.create( endpoint=inbound_endpoint, event_type="invoice.paid", raw_payload={"id": "inv_1"}, ) response = authenticated_client.get(INBOUND_EVENTS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["event_type"] == "invoice.paid" def test_requires_auth(self, api_client, inbound_endpoint): assert api_client.get(INBOUND_EVENTS_URL).status_code == 401 class TestInboundReceiveView: URL = "/api/webhooks/inbound/receive/{slug}/" def test_receive_creates_event(self, api_client, inbound_endpoint): response = api_client.post( self.URL.format(slug="stripe"), { "payload": {"type": "invoice.paid"}, "headers": {"X-Event-Type": "invoice.paid"}, }, format="json", ) assert response.status_code == 201 event = InboundEvent.objects.get(endpoint=inbound_endpoint) assert event.event_type == "invoice.paid" assert event.raw_payload == {"type": "invoice.paid"} def test_receive_is_public(self, api_client, inbound_endpoint): response = api_client.post( self.URL.format(slug="stripe"), {"payload": {}, "headers": {}}, format="json", ) assert response.status_code == 201 def test_receive_unknown_slug_404(self, api_client): response = api_client.post( self.URL.format(slug="unknown"), {"payload": {}, "headers": {}}, format="json", ) assert response.status_code == 404 def test_receive_inactive_endpoint_404(self, api_client, inbound_endpoint): inbound_endpoint.is_active = False inbound_endpoint.save(update_fields=["is_active"]) response = api_client.post( self.URL.format(slug="stripe"), {"payload": {}, "headers": {}}, format="json", ) assert response.status_code == 404 def test_receive_missing_payload_400(self, api_client, inbound_endpoint): response = api_client.post(self.URL.format(slug="stripe"), {}, format="json") assert response.status_code == 400 def test_receive_emits_signal(self, api_client, inbound_endpoint): from infrasynth.webhooks.signals import inbound_event_received sent = [] receiver = lambda **kw: sent.append(kw) # noqa: E731 inbound_event_received.connect(receiver, weak=False) api_client.post( self.URL.format(slug="stripe"), {"payload": {"a": 1}, "headers": {}}, format="json", ) assert sent assert sent[0]["payload"] == {"a": 1} inbound_event_received.disconnect(receiver)