from contextlib import contextmanager from unittest import mock import pytest from django.db import transaction from infrasynth.webhooks.models import ( OutboundDelivery, OutboundEndpoint, OutboundSubscription, ) from infrasynth.webhooks.registry import EventDefinition, EventRegistry pytestmark = pytest.mark.django_db @contextmanager def _emit_and_run_commit_hooks(): with transaction.atomic(): yield connection = transaction.get_connection() pending = list(connection.run_on_commit) connection.run_on_commit = [] for entry in pending: func = entry[1] func() @pytest.fixture(autouse=True) def clean_registry(): EventRegistry._events.clear() yield EventRegistry._events.clear() class TestRegister: def test_register_creates_definition(self): EventRegistry.register("helpdesk.ticket.created") event = EventRegistry.get_registered_events()["helpdesk.ticket.created"] assert isinstance(event, EventDefinition) assert event.name == "helpdesk.ticket.created" def test_register_with_metadata(self): EventRegistry.register( "helpdesk.ticket.resolved", description="Ticket resolved", example_payload={"ticket_id": 1}, schema={"type": "object"}, ) event = EventRegistry.get_registered_events()["helpdesk.ticket.resolved"] assert event.description == "Ticket resolved" assert event.example_payload == {"ticket_id": 1} assert event.schema == {"type": "object"} def test_get_registered_events_returns_copy(self): EventRegistry.register("evt.a") events = EventRegistry.get_registered_events() events["evt.a"] = EventDefinition(name="mutated") assert "evt.a" in EventRegistry.get_registered_events() assert EventRegistry.get_registered_events()["evt.a"].name == "evt.a" class TestEmit: @pytest.fixture def endpoint(self): return OutboundEndpoint.objects.create( name="Target", url="https://example.com/hook", secret="test-secret", ) def test_emit_delivers_to_subscriber(self, endpoint): subscription = OutboundSubscription.objects.create(endpoint=endpoint, event_name="helpdesk.ticket.created") with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: post.return_value.status_code = 200 post.return_value.text = "ok" with _emit_and_run_commit_hooks(): EventRegistry.emit("helpdesk.ticket.created", {"ticket_id": 1}) delivery = OutboundDelivery.objects.get(subscription=subscription) assert delivery.status == OutboundDelivery.Status.SUCCESS assert delivery.payload == '{"ticket_id": 1}' def test_emit_wildcard_subscription(self, endpoint): subscription = OutboundSubscription.objects.create(endpoint=endpoint, event_name="*") with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: post.return_value.status_code = 200 post.return_value.text = "ok" with _emit_and_run_commit_hooks(): EventRegistry.emit("any.event", {"x": 1}) assert OutboundDelivery.objects.filter(subscription=subscription).exists() def test_emit_inactive_subscription_skipped(self, endpoint): OutboundSubscription.objects.create(endpoint=endpoint, event_name="evt.x", is_active=False) with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: with _emit_and_run_commit_hooks(): EventRegistry.emit("evt.x", {}) post.assert_not_called() assert not OutboundDelivery.objects.exists() def test_emit_inactive_endpoint_skipped(self, endpoint): OutboundSubscription.objects.create(endpoint=endpoint, event_name="evt.x", is_active=True) endpoint.is_active = False endpoint.save(update_fields=["is_active"]) with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: with _emit_and_run_commit_hooks(): EventRegistry.emit("evt.x", {}) post.assert_not_called() def test_emit_no_subscriptions_noop(self): with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: with _emit_and_run_commit_hooks(): EventRegistry.emit("unsubscribed.event", {}) post.assert_not_called() def test_emit_sends_payload_template(self, endpoint): subscription = OutboundSubscription.objects.create( endpoint=endpoint, event_name="evt.template", payload_template='{"event": "{{ event.type }}"}', ) with mock.patch("infrasynth.webhooks.dispatch.requests.post") as post: post.return_value.status_code = 200 post.return_value.text = "ok" with _emit_and_run_commit_hooks(): EventRegistry.emit("evt.template", {"type": "created"}) delivery = OutboundDelivery.objects.get(subscription=subscription) assert delivery.payload == '{"event": "created"}'