from collections.abc import Mapping from typing import Any from unittest.mock import MagicMock import pytest from werkzeug import Request, Response from dify_plugin.core.runtime import Session from dify_plugin.core.trigger_factory import TriggerFactory from dify_plugin.entities import I18nObject, ParameterOption from dify_plugin.entities.provider_config import CredentialType from dify_plugin.entities.trigger import ( EventConfiguration, EventConfigurationExtra, EventDispatch, EventIdentity, EventParameter, Subscription, TriggerProviderConfiguration, TriggerProviderConfigurationExtra, TriggerProviderIdentity, TriggerSubscriptionConstructorConfiguration, TriggerSubscriptionConstructorConfigurationExtra, TriggerSubscriptionConstructorRuntime, UnsubscribeResult, Variables, ) from dify_plugin.interfaces.trigger import Event, EventRuntime, Trigger, TriggerSubscriptionConstructor class MockTriggerProvider(Trigger): """ Mock Trigger Provider """ def _dispatch_event(self, subscription: Subscription, request: Request) -> EventDispatch: """ Dispatch event from webhook """ return EventDispatch(events=["test_event"], response=Response("OK", status=200)) class MockTriggerSubscriptionConstructor(TriggerSubscriptionConstructor): """ Mock Trigger Subscription Constructor """ def _validate_api_key(self, credentials: dict): """ Validate API key """ pass def _create_subscription( self, endpoint: str, credentials: Mapping[str, Any], selected_events: list[str], parameters: Mapping[str, Any] ) -> Subscription: """ Create subscription """ return Subscription( expires_at=1234567890, endpoint=endpoint, properties={ "external_id": "test_external_id", "webhook_secret": "test_secret", }, subscribed_events=selected_events, ) def _delete_subscription(self, subscription: Subscription, credentials: Mapping[str, Any]) -> UnsubscribeResult: """ Delete subscription """ return UnsubscribeResult(success=True, message="Successfully unsubscribed") def _refresh_subscription(self, subscription: Subscription, credentials: Mapping[str, Any]) -> Subscription: """ Refresh subscription """ return Subscription( expires_at=9999999999, endpoint=subscription.endpoint, properties=subscription.properties, ) def _fetch_parameter_options(self, credentials: Mapping[str, Any], parameter: str) -> list[ParameterOption]: """ Fetch parameter options """ return [ ParameterOption(value="option1", label=I18nObject(en_US="Option 1")), ParameterOption(value="option2", label=I18nObject(en_US="Option 2")), ] class MockEventHandler(Event): """ Mock Event """ def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables: """ Transform the webhook request into Variables """ return Variables(variables={"test_variable": "test_value", "event_data": request.get_data(as_text=True)}) def test_trigger_factory_register_and_get_provider(): """ Test trigger factory registration and retrieval of provider """ factory = TriggerFactory() session = MagicMock(spec=Session) # Create provider configuration provider_config = TriggerProviderConfiguration( identity=TriggerProviderIdentity( author="test", name="test_provider", label=I18nObject(en_US="Test Provider"), description=I18nObject(en_US="Test Provider Description"), ), subscription_constructor=TriggerSubscriptionConstructorConfiguration( parameters=[], credentials_schema=[], extra=TriggerSubscriptionConstructorConfigurationExtra( python=TriggerSubscriptionConstructorConfigurationExtra.Python(source="test_constructor.py") ), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py") ), ) # Create trigger configuration trigger_config = EventConfiguration( identity=EventIdentity( author="test", name="test_event", label=I18nObject(en_US="Test Event"), ), parameters=[ EventParameter( name="test_param", label=I18nObject(en_US="Test Parameter"), type=EventParameter.EventParameterType.STRING, ) ], description=I18nObject(en_US="Human description"), extra=EventConfigurationExtra(python=EventConfigurationExtra.Python(source="test_event.py")), output_schema={"test_variable": {"type": "string"}}, ) # Register trigger provider with events factory.register_trigger_provider( configuration=provider_config, provider_cls=MockTriggerProvider, subscription_constructor_cls=MockTriggerSubscriptionConstructor, events={"test_event": (trigger_config, MockEventHandler)}, ) # Test getting provider instance provider = factory.get_trigger_provider("test_provider", session, None, None) assert isinstance(provider, MockTriggerProvider) assert provider.runtime.session == session # Test getting provider class provider_cls = factory.get_provider_cls("test_provider") assert provider_cls == MockTriggerProvider # Test getting configuration config = factory.get_configuration("test_provider") assert config == provider_config def test_trigger_factory_subscription_constructor(): """ Test trigger factory subscription constructor """ factory = TriggerFactory() runtime = TriggerSubscriptionConstructorRuntime( credentials={"api_key": "test_key"}, session=MagicMock(spec=Session), credential_type=CredentialType.API_KEY ) # Create provider configuration with subscription constructor provider_config = TriggerProviderConfiguration( identity=TriggerProviderIdentity( author="test", name="test_provider", label=I18nObject(en_US="Test Provider"), description=I18nObject(en_US="Test Provider Description"), ), subscription_constructor=TriggerSubscriptionConstructorConfiguration( parameters=[], credentials_schema=[], extra=TriggerSubscriptionConstructorConfigurationExtra( python=TriggerSubscriptionConstructorConfigurationExtra.Python(source="test_constructor.py") ), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py") ), ) # Register provider factory.register_trigger_provider( configuration=provider_config, provider_cls=MockTriggerProvider, subscription_constructor_cls=MockTriggerSubscriptionConstructor, events={}, ) # Test has_subscription_constructor assert factory.has_subscription_constructor("test_provider") is True # Test get subscription constructor instance constructor = factory.get_subscription_constructor("test_provider", runtime) assert isinstance(constructor, MockTriggerSubscriptionConstructor) assert constructor.runtime.session == runtime.session # Test get subscription constructor class constructor_cls = factory.get_subscription_constructor_cls("test_provider") assert constructor_cls == MockTriggerSubscriptionConstructor def test_trigger_factory_trigger_events(): """ Test trigger factory trigger event handling """ factory = TriggerFactory() session = MagicMock(spec=Session) # Create configurations provider_config = TriggerProviderConfiguration( identity=TriggerProviderIdentity( author="test", name="test_provider", label=I18nObject(en_US="Test Provider"), description=I18nObject(en_US="Test Provider Description"), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py") ), ) trigger_config = EventConfiguration( identity=EventIdentity( author="test", name="test_event", label=I18nObject(en_US="Test Event"), ), parameters=[], description=I18nObject(en_US="Human description"), extra=EventConfigurationExtra(python=EventConfigurationExtra.Python(source="test_event.py")), ) # Register provider with events registration = factory.register_trigger_provider( configuration=provider_config, provider_cls=MockTriggerProvider, subscription_constructor_cls=None, events={}, ) # Register trigger after provider registration registration.register_trigger( name="test_event", configuration=trigger_config, trigger_cls=MockEventHandler, ) # Test get Event event = factory.get_trigger_event_handler( "test_provider", "test_event", EventRuntime( session=session, credential_type=CredentialType.UNAUTHORIZED, subscription=Subscription( expires_at=1234567890, endpoint="test_endpoint", properties={"external_id": "test_external_id", "webhook_secret": "test_secret"}, ), ), ) assert isinstance(event, MockEventHandler) assert event.runtime.session == session # Test get trigger configuration config = factory.get_trigger_configuration("test_provider", "test_event") assert config == trigger_config # Test iterate events events = factory.iter_events("test_provider") assert "test_event" in events assert events["test_event"][0] == trigger_config assert events["test_event"][1] == MockEventHandler def test_trigger_factory_error_handling(): """ Test trigger factory error handling """ factory = TriggerFactory() session = MagicMock(spec=Session) # Test getting non-existent provider with pytest.raises(ValueError, match="Trigger provider `non_existent` not found"): factory.get_trigger_provider("non_existent", session, None, None) # Create and register a provider provider_config = TriggerProviderConfiguration( identity=TriggerProviderIdentity( author="test", name="test_provider", label=I18nObject(en_US="Test Provider"), description=I18nObject(en_US="Test Provider Description"), ), extra=TriggerProviderConfigurationExtra( python=TriggerProviderConfigurationExtra.Python(source="test_provider.py") ), ) factory.register_trigger_provider( configuration=provider_config, provider_cls=MockTriggerProvider, subscription_constructor_cls=None, events={}, ) # Test duplicate registration with pytest.raises(ValueError, match="Trigger provider `test_provider` is already registered"): factory.register_trigger_provider( configuration=provider_config, provider_cls=MockTriggerProvider, subscription_constructor_cls=None, events={}, ) # Test getting non-existent event with pytest.raises(ValueError, match="Event `non_existent` not found in provider `test_provider`"): factory.get_trigger_event_handler("test_provider", "non_existent", session) # Test getting subscription constructor when none exists runtime = TriggerSubscriptionConstructorRuntime( credentials={"api_key": "test_key"}, session=MagicMock(spec=Session), credential_type=CredentialType.API_KEY ) with pytest.raises(ValueError, match="Trigger provider `test_provider` does not define a subscription constructor"): factory.get_subscription_constructor("test_provider", runtime)