from contextlib import contextmanager from unittest import mock import pytest from django.core.exceptions import ValidationError from django.db import transaction from django.dispatch import receiver from django.test import override_settings from infrasynth.audit.models import SecurityEvent from infrasynth.features.models import FeatureFlag, FeatureFlagOverride from infrasynth.features.registry import FeatureRegistry from infrasynth.features.services import FeatureService from infrasynth.notifications.resolvers import VariableResolverRegistry from infrasynth.webhooks.models import ( OutboundDelivery, OutboundEndpoint, OutboundSubscription, ) from infrasynth.webhooks.registry import EventRegistry from infrasynth.webhooks.signals import ( outbound_delivery_failed, outbound_delivery_succeeded, ) from infrasynth.workflows.validators import DataValidatorRegistry 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_registries(): saved_events = dict(EventRegistry._events) saved_features = dict(FeatureRegistry._features) saved_resolvers = dict(VariableResolverRegistry._resolvers) saved_validators = dict(DataValidatorRegistry._validators) yield EventRegistry._events = saved_events FeatureRegistry._features = saved_features VariableResolverRegistry._resolvers = saved_resolvers DataValidatorRegistry._validators = saved_validators class TestOutboundDeliverySignalToAuditIntegration: def test_outbound_delivery_signal_triggers_audit_event(self, user): endpoint = OutboundEndpoint.objects.create( name="Test Endpoint", url="https://example.com/hook", secret="test-secret", ) subscription = OutboundSubscription.objects.create( endpoint=endpoint, event_name="test.event", ) delivery = OutboundDelivery.objects.create( subscription=subscription, payload={"key": "value"}, status=OutboundDelivery.Status.RETRYING, ) @receiver(outbound_delivery_succeeded) def create_audit_on_success(sender, **kwargs): SecurityEvent.objects.create( event_type="webhook_outbound_success", actor=user, ip_address="127.0.0.1", metadata={ "delivery_id": kwargs.get("delivery_id"), "event_name": kwargs.get("event_name"), "status_code": kwargs.get("status_code"), }, request_id="integration-test-001", ) outbound_delivery_succeeded.send( sender=OutboundDelivery, delivery_id=delivery.id, event_name="test.event", status_code=200, ) assert SecurityEvent.objects.filter(event_type="webhook_outbound_success").exists() se = SecurityEvent.objects.get(event_type="webhook_outbound_success") assert se.metadata["delivery_id"] == delivery.id assert se.metadata["event_name"] == "test.event" assert se.metadata["status_code"] == 200 assert se.actor == user def test_outbound_delivery_failed_signal_triggers_audit_event(self, user): endpoint = OutboundEndpoint.objects.create( name="Test Endpoint", url="https://example.com/hook", secret="test-secret", ) subscription = OutboundSubscription.objects.create( endpoint=endpoint, event_name="test.event", ) delivery = OutboundDelivery.objects.create( subscription=subscription, payload={"key": "value"}, status=OutboundDelivery.Status.RETRYING, ) @receiver(outbound_delivery_failed) def create_audit_on_failure(sender, **kwargs): SecurityEvent.objects.create( event_type="webhook_outbound_failed", actor=user, ip_address="127.0.0.1", metadata={ "delivery_id": kwargs.get("delivery_id"), "event_name": kwargs.get("event_name"), "error": kwargs.get("error"), }, request_id="integration-test-002", ) outbound_delivery_failed.send( sender=OutboundDelivery, delivery_id=delivery.id, event_name="test.event", error="Connection timed out", ) assert SecurityEvent.objects.filter(event_type="webhook_outbound_failed").exists() se = SecurityEvent.objects.get(event_type="webhook_outbound_failed") assert se.metadata["delivery_id"] == delivery.id assert se.metadata["error"] == "Connection timed out" assert se.actor == user class TestEventRegistryEmissionIntegration: @override_settings(CELERY_TASK_ALWAYS_EAGER=True) def test_event_registry_emission_creates_outbound_deliveries(self): EventRegistry.register( "integration.order.created", description="Order created event", example_payload={"order_id": "ord_001"}, ) endpoint = OutboundEndpoint.objects.create( name="Integration Endpoint", url="https://example.com/hook", secret="test-secret", ) subscription = OutboundSubscription.objects.create( endpoint=endpoint, event_name="integration.order.created", ) payload = {"order_id": "ord_001", "amount": 100} 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("integration.order.created", payload) assert OutboundDelivery.objects.filter(subscription=subscription).exists() delivery = OutboundDelivery.objects.get(subscription=subscription) assert delivery.status == OutboundDelivery.Status.SUCCESS assert delivery.payload is not None assert delivery.response_status == 200 class TestFeatureRegistryRegistrationAndDiscovery: def test_feature_registry_registration_and_discovery(self, user): FeatureRegistry.register( "app_a.premium_feature", name="Premium Feature", description="A premium feature from App A", default=False, category="app_a", ) FeatureRegistry.register( "app_b.analytics", name="Analytics", description="Analytics feature from App B", default=True, category="app_b", ) service = FeatureService() assert service.is_enabled("app_a.premium_feature") is False assert service.is_enabled("app_b.analytics") is True assert service.is_enabled("nonexistent.feature") is False flag = FeatureFlag.objects.create( slug="app_a.premium_feature", name="Premium Feature Override", is_active=True, rollout_percentage=100, ) assert service.is_enabled("app_a.premium_feature") is True FeatureFlagOverride.objects.create( flag=flag, user=user, is_enabled=False, ) assert service.is_enabled("app_a.premium_feature", user=user) is False FeatureFlag.objects.create( slug="app_c.experimental", name="Experimental", is_active=False, rollout_percentage=100, ) assert service.is_enabled("app_c.experimental") is False class TestVariableResolverRegistryCrossApp: def test_variable_resolver_registry_cross_app(self): @VariableResolverRegistry.register( "user_name", label="User Name", description="Resolves the user's full name", namespace="app_a", ) def resolve_user_name(recipient, context, request=None): return context.get("user_name", "Unknown") @VariableResolverRegistry.register( "order_total", label="Order Total", description="Resolves the order total", namespace="app_b", ) def resolve_order_total(recipient, context, request=None): return context.get("order_total", 0) context_a = {"user_name": "Alice"} result_a = VariableResolverRegistry.resolve("user_name", "alice@example.com", context_a, namespace="app_a") assert result_a == "Alice" context_b = {"order_total": 99.99} result_b = VariableResolverRegistry.resolve("order_total", "alice@example.com", context_b, namespace="app_b") assert result_b == 99.99 result_unknown = VariableResolverRegistry.resolve("nonexistent", "alice@example.com", {}, namespace="app_a") assert result_unknown is None available_a = VariableResolverRegistry.get_available_variables(namespace="app_a") assert len(available_a) == 1 assert "app_a:user_name" in available_a available_b = VariableResolverRegistry.get_available_variables(namespace="app_b") assert len(available_b) == 1 assert "app_b:order_total" in available_b available_all = VariableResolverRegistry.get_available_variables() assert len(available_all) == 2 class TestDataValidatorRegistryCrossApp: def test_data_validator_registry_cross_app(self): class AppAValidator: def validate(self, node, data, context): return {"validated": True, **data} class AppBValidator: def validate(self, node, data, context): if not data.get("required_field"): raise ValidationError({"required_field": "This field is required."}) return data DataValidatorRegistry.register("app_a_workflow", AppAValidator()) DataValidatorRegistry.register("app_b_workflow", AppBValidator()) validator_a = DataValidatorRegistry.get("app_a_workflow") assert validator_a is not None result = validator_a.validate(None, {"key": "val"}, {}) assert result["validated"] is True assert result["key"] == "val" validator_b = DataValidatorRegistry.get("app_b_workflow") assert validator_b is not None result_valid = validator_b.validate(None, {"required_field": "present"}, {}) assert result_valid == {"required_field": "present"} with pytest.raises(ValidationError) as exc_info: validator_b.validate(None, {}, {}) assert "required_field" in exc_info.value.message_dict missing = DataValidatorRegistry.get("nonexistent_workflow") assert missing is None