infrasynth-backend-kit/tests/test_webhooks/test_integration.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

302 lines
11 KiB
Python

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