Fixed plugin config
This commit is contained in:
parent
fffd5a6972
commit
d8f70b137d
|
@ -229,6 +229,7 @@ MIDDLEWARE = (
|
|||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"funkwhale_api.plugins.middleware.AttachPluginsConfMiddleware",
|
||||
"funkwhale_api.common.middleware.SPAFallbackMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
|
@ -616,12 +617,12 @@ REST_FRAMEWORK = {
|
|||
"funkwhale_api.federation.parsers.ActivityParser",
|
||||
),
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
|
||||
"funkwhale_api.common.authentication.OAuth2Authentication",
|
||||
"funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS",
|
||||
"funkwhale_api.common.authentication.BearerTokenHeaderAuth",
|
||||
"funkwhale_api.common.authentication.JSONWebTokenAuthentication",
|
||||
"rest_framework.authentication.BasicAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
"funkwhale_api.common.authentication.BasicAuthentication",
|
||||
"funkwhale_api.common.authentication.SessionAuthentication",
|
||||
),
|
||||
"DEFAULT_PERMISSION_CLASSES": (
|
||||
"funkwhale_api.users.oauth.permissions.ScopePermission",
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import ugettext as _
|
||||
from oauth2_provider.contrib.rest_framework import (
|
||||
OAuth2Authentication as BaseOAuth2Authentication,
|
||||
)
|
||||
from rest_framework import exceptions
|
||||
from rest_framework_jwt import authentication
|
||||
from rest_framework_jwt.settings import api_settings
|
||||
from rest_framework.authentication import BasicAuthentication as BaseBasicAuthentication
|
||||
from rest_framework.authentication import (
|
||||
SessionAuthentication as BaseSessionAuthentication,
|
||||
)
|
||||
|
||||
|
||||
class JSONWebTokenAuthenticationQS(authentication.BaseJSONWebTokenAuthentication):
|
||||
from funkwhale_api.plugins import authentication as plugin_authentication
|
||||
|
||||
|
||||
class JSONWebTokenAuthenticationQS(
|
||||
plugin_authentication.AttachPluginsConfMixin,
|
||||
authentication.BaseJSONWebTokenAuthentication,
|
||||
):
|
||||
|
||||
www_authenticate_realm = "api"
|
||||
|
||||
|
@ -22,7 +35,10 @@ class JSONWebTokenAuthenticationQS(authentication.BaseJSONWebTokenAuthentication
|
|||
)
|
||||
|
||||
|
||||
class BearerTokenHeaderAuth(authentication.BaseJSONWebTokenAuthentication):
|
||||
class BearerTokenHeaderAuth(
|
||||
plugin_authentication.AttachPluginsConfMixin,
|
||||
authentication.BaseJSONWebTokenAuthentication,
|
||||
):
|
||||
"""
|
||||
For backward compatibility purpose, we used Authorization: JWT <token>
|
||||
but Authorization: Bearer <token> is probably better.
|
||||
|
@ -65,7 +81,10 @@ class BearerTokenHeaderAuth(authentication.BaseJSONWebTokenAuthentication):
|
|||
return auth
|
||||
|
||||
|
||||
class JSONWebTokenAuthentication(authentication.JSONWebTokenAuthentication):
|
||||
class JSONWebTokenAuthentication(
|
||||
plugin_authentication.AttachPluginsConfMixin,
|
||||
authentication.JSONWebTokenAuthentication,
|
||||
):
|
||||
def authenticate(self, request):
|
||||
auth = super().authenticate(request)
|
||||
|
||||
|
@ -73,3 +92,21 @@ class JSONWebTokenAuthentication(authentication.JSONWebTokenAuthentication):
|
|||
if not auth[0].actor:
|
||||
auth[0].create_actor()
|
||||
return auth
|
||||
|
||||
|
||||
class OAuth2Authentication(
|
||||
plugin_authentication.AttachPluginsConfMixin, BaseOAuth2Authentication
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class BasicAuthentication(
|
||||
plugin_authentication.AttachPluginsConfMixin, BaseBasicAuthentication
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class SessionAuthentication(
|
||||
plugin_authentication.AttachPluginsConfMixin, BaseSessionAuthentication
|
||||
):
|
||||
pass
|
||||
|
|
|
@ -3,6 +3,8 @@ from django.utils import timezone
|
|||
|
||||
from funkwhale_api.music.models import Track
|
||||
|
||||
from . import signals
|
||||
|
||||
|
||||
class Listening(models.Model):
|
||||
creation_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from funkwhale_api import plugins
|
||||
|
||||
|
||||
plugins.hooks.register(
|
||||
plugins.Hook("history.listening.created", providing_args=["listening"])
|
||||
)
|
|
@ -5,6 +5,8 @@ from django import apps
|
|||
|
||||
import logging
|
||||
|
||||
from funkwhale_api.common import session
|
||||
|
||||
from . import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -12,22 +14,28 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class Plugin(apps.AppConfig):
|
||||
_is_funkwhale_plugin = True
|
||||
version = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.hooks = HookRegistry()
|
||||
self.settings = SettingRegistry()
|
||||
self.user_settings = SettingRegistry()
|
||||
self.logger = None
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
logging.info("Loading plugin %s…", self.label)
|
||||
self.logger = logging.getLogger("funkwhale_api.plugin.{}".format(self.name))
|
||||
self.load()
|
||||
logging.info("Plugin %s loaded", self.label)
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
def get_requests_session(self):
|
||||
return session.get_session()
|
||||
|
||||
|
||||
class FuncRegistry(persisting_theory.Registry):
|
||||
def connect(self, hook_name):
|
||||
|
@ -77,6 +85,12 @@ class SignalsRegistry(persisting_theory.Registry):
|
|||
"""
|
||||
Call all handlers connected to hook_name in turn.
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
if not plugins_conf:
|
||||
logger.debug("[Plugin:hook:dispatch] No plugin configured")
|
||||
return
|
||||
|
||||
if hook_name not in self:
|
||||
raise LookupError(hook_name)
|
||||
logger.debug("[Plugin:hook:%s] Dispatching hook", hook_name)
|
||||
|
@ -105,14 +119,20 @@ class SignalsRegistry(persisting_theory.Registry):
|
|||
handler(plugin_conf=row, **kwargs)
|
||||
except Skip:
|
||||
logger.debug("[Plugin:hook:%s] handler skipped", hook_name)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
"[Plugin:hook:%s] unknown exception with handler %s",
|
||||
hook_name,
|
||||
handler,
|
||||
)
|
||||
if settings.PLUGINS_FAIL_LOUDLY:
|
||||
raise e
|
||||
else:
|
||||
logger.debug("[Plugin:hook:%s] handler %s called successfully", handler)
|
||||
logger.debug(
|
||||
"[Plugin:hook:%s] handler %s called successfully",
|
||||
hook_name,
|
||||
handler,
|
||||
)
|
||||
|
||||
logger.debug("[Plugin:hook:%s] Done", hook_name)
|
||||
|
||||
|
@ -155,6 +175,14 @@ def generate_plugins_conf(plugins, user=None):
|
|||
"settings": by_plugin_name[plugin.name] or {},
|
||||
}
|
||||
plugins_conf.append(conf)
|
||||
return plugins_conf
|
||||
|
||||
|
||||
def update_plugins_conf_with_user_settings(plugins_conf, user):
|
||||
if not plugins_conf:
|
||||
return
|
||||
|
||||
from . import models
|
||||
|
||||
if plugins_conf and user and user.is_authenticated:
|
||||
qs = models.UserPlugin.objects.filter(
|
||||
|
@ -170,12 +198,12 @@ def generate_plugins_conf(plugins, user=None):
|
|||
return plugins_conf
|
||||
|
||||
|
||||
def attach_plugins_conf(obj, user):
|
||||
def attach_plugins_conf(obj):
|
||||
from funkwhale_api.common import preferences
|
||||
|
||||
plugins_enabled = preferences.get("plugins__enabled")
|
||||
if plugins_enabled:
|
||||
conf = generate_plugins_conf(plugins=get_all_plugins(), user=user)
|
||||
conf = generate_plugins_conf(plugins=get_all_plugins())
|
||||
else:
|
||||
conf = None
|
||||
setattr(obj, "plugins_conf", conf)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
from funkwhale_api import plugins
|
||||
|
||||
|
||||
class AttachPluginsConfMixin(object):
|
||||
def authenticate(self, request):
|
||||
auth = super().authenticate(request)
|
||||
self.update_plugins_conf(request, auth)
|
||||
return auth
|
||||
|
||||
def update_plugins_conf(self, request, auth):
|
||||
if auth:
|
||||
plugins.update_plugins_conf_with_user_settings(
|
||||
getattr(request, "plugins_conf", []), user=auth[0]
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
from funkwhale_api import plugins
|
||||
|
||||
|
||||
class AttachPluginsConfMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
plugins.attach_plugins_conf(request)
|
||||
return self.get_response(request)
|
|
@ -5,6 +5,8 @@ from rest_framework import authentication, exceptions
|
|||
|
||||
from funkwhale_api.users.models import User
|
||||
|
||||
from funkwhale_api.plugins import authentication as plugin_authentication
|
||||
|
||||
|
||||
def get_token(salt, password):
|
||||
to_hash = password + salt
|
||||
|
@ -41,8 +43,15 @@ def authenticate_salt(username, salt, token):
|
|||
return (user, None)
|
||||
|
||||
|
||||
class SubsonicAuthentication(authentication.BaseAuthentication):
|
||||
class SubsonicAuthentication(
|
||||
plugin_authentication.AttachPluginsConfMixin, authentication.BaseAuthentication
|
||||
):
|
||||
def authenticate(self, request):
|
||||
auth = self.perform_authentication(request)
|
||||
self.update_plugins_conf(request, auth)
|
||||
return auth
|
||||
|
||||
def perform_authentication(self, request):
|
||||
data = request.GET or request.POST
|
||||
username = data.get("u")
|
||||
if not username:
|
||||
|
@ -56,5 +65,4 @@ class SubsonicAuthentication(authentication.BaseAuthentication):
|
|||
|
||||
if p:
|
||||
return authenticate(username, p)
|
||||
|
||||
return authenticate_salt(username, s, t)
|
||||
|
|
|
@ -85,90 +85,81 @@ def test_get_all_plugins(mocker):
|
|||
|
||||
def test_generate_plugins_conf(factories, plugin_class):
|
||||
plugin1 = plugin_class("test1", "test1")
|
||||
plugin2 = plugin_class("test2", "test2")
|
||||
plugin3 = plugin_class("test3", "test3")
|
||||
plugin4 = plugin_class("test4", "test4")
|
||||
plugin2 = plugin_class("test3", "test3")
|
||||
plugin3 = plugin_class("test4", "test4")
|
||||
|
||||
user = factories["users.User"]()
|
||||
# this one is enabled
|
||||
plugin1_db_conf = factories["plugins.Plugin"](name=plugin1.name)
|
||||
# this one is enabled, with additional user-level configuration (see below)
|
||||
plugin2_db_conf = factories["plugins.Plugin"](name=plugin2.name)
|
||||
# this one is disabled at the plugin level, so it shouldn't appear in the final conf
|
||||
plugin3_db_conf = factories["plugins.Plugin"](name=plugin3.name, is_enabled=False)
|
||||
# this one is enabled, but disabled at user level (see below)
|
||||
plugin4_db_conf = factories["plugins.Plugin"](name=plugin4.name)
|
||||
factories["plugins.Plugin"](name=plugin3.name, is_enabled=False)
|
||||
# this one doesn't match any registered app
|
||||
factories["plugins.Plugin"](name="noop")
|
||||
|
||||
# a user-level configuration but with a different user, so irrelevant
|
||||
factories["plugins.UserPlugin"](plugin=plugin1_db_conf)
|
||||
# a user-level configuration but the plugin is disabled
|
||||
factories["plugins.UserPlugin"](user=user, plugin=plugin3_db_conf)
|
||||
# a user-level configuration, plugin is enabled, should be reflected in the final conf
|
||||
plugin2_user_db_conf = factories["plugins.UserPlugin"](
|
||||
user=user, plugin=plugin2_db_conf
|
||||
)
|
||||
# a user-level configuration, plugin is enabled but disabled by user, should be reflected in the final conf
|
||||
factories["plugins.UserPlugin"](user=user, plugin=plugin4_db_conf, is_enabled=False)
|
||||
expected = [{"obj": plugin1, "settings": plugin1_db_conf.config, "user": None}]
|
||||
|
||||
expected = [
|
||||
{"obj": plugin1, "settings": plugin1_db_conf.config, "user": None},
|
||||
{
|
||||
"obj": plugin2,
|
||||
"settings": plugin2_db_conf.config,
|
||||
"user": {"id": user.pk, "settings": plugin2_user_db_conf.config},
|
||||
},
|
||||
{"obj": plugin4, "settings": plugin4_db_conf.config, "user": None},
|
||||
]
|
||||
|
||||
conf = plugins.generate_plugins_conf([plugin1, plugin2, plugin3, plugin4], user=user)
|
||||
conf = plugins.generate_plugins_conf([plugin1, plugin2, plugin3])
|
||||
assert conf == expected
|
||||
|
||||
|
||||
def test_generate_plugins_conf_anonymous_user(factories, plugin_class):
|
||||
def test_update_plugins_conf_with_user_settings(factories, plugin_class):
|
||||
plugin1 = plugin_class("test1", "test1")
|
||||
plugin2 = plugin_class("test2", "test2")
|
||||
plugin3 = plugin_class("test3", "test3")
|
||||
plugin4 = plugin_class("test4", "test4")
|
||||
|
||||
user = factories["users.User"]()
|
||||
# this one is enabled
|
||||
plugin1_db_conf = factories["plugins.Plugin"](name=plugin1.name)
|
||||
# this one is enabled, with additional user-level configuration (see below)
|
||||
plugin2_db_conf = factories["plugins.Plugin"](name=plugin2.name)
|
||||
# this one is disabled at the plugin level, so it shouldn't appear in the final conf
|
||||
plugin3_db_conf = factories["plugins.Plugin"](name=plugin3.name, is_enabled=False)
|
||||
# this one is enabled, but disabled at user level (see below)
|
||||
plugin4_db_conf = factories["plugins.Plugin"](name=plugin4.name)
|
||||
# this one doesn't match any registered app
|
||||
factories["plugins.Plugin"](name="noop")
|
||||
|
||||
# a user-level configuration but with a different user, so irrelevant
|
||||
factories["plugins.UserPlugin"](plugin=plugin1_db_conf)
|
||||
# a user-level configuration but the plugin is disabled
|
||||
factories["plugins.UserPlugin"](user=user, plugin=plugin3_db_conf)
|
||||
# user has enabled this plugin and has custom settings
|
||||
plugin1_user_conf = factories["plugins.UserPlugin"](
|
||||
plugin__name=plugin1.name, user=user
|
||||
)
|
||||
# plugin is disabled by user
|
||||
plugin2_user_conf = factories["plugins.UserPlugin"](
|
||||
plugin__name=plugin2.name, user=user, is_enabled=False
|
||||
)
|
||||
# Plugin is enabled by another user
|
||||
plugin3_user_conf = factories["plugins.UserPlugin"](plugin__name=plugin3.name)
|
||||
|
||||
expected = [
|
||||
{
|
||||
"obj": plugin1,
|
||||
"settings": plugin1_user_conf.plugin.config,
|
||||
"user": {"id": user.pk, "settings": plugin1_user_conf.config},
|
||||
},
|
||||
{"obj": plugin2, "settings": plugin2_user_conf.plugin.config, "user": None},
|
||||
{"obj": plugin3, "settings": plugin3_user_conf.plugin.config, "user": None},
|
||||
]
|
||||
|
||||
conf = plugins.generate_plugins_conf([plugin1, plugin2, plugin3])
|
||||
assert plugins.update_plugins_conf_with_user_settings(conf, user=user) == expected
|
||||
|
||||
|
||||
def test_update_plugins_conf_with_user_settings_anonymous(factories, plugin_class):
|
||||
plugin1 = plugin_class("test1", "test1")
|
||||
plugin2 = plugin_class("test2", "test2")
|
||||
plugin3 = plugin_class("test3", "test3")
|
||||
|
||||
plugin1_db_conf = factories["plugins.Plugin"](name=plugin1.name)
|
||||
plugin2_db_conf = factories["plugins.Plugin"](name=plugin2.name)
|
||||
plugin3_db_conf = factories["plugins.Plugin"](name=plugin3.name)
|
||||
|
||||
expected = [
|
||||
{"obj": plugin1, "settings": plugin1_db_conf.config, "user": None},
|
||||
{"obj": plugin2, "settings": plugin2_db_conf.config, "user": None},
|
||||
{"obj": plugin4, "settings": plugin4_db_conf.config, "user": None},
|
||||
{"obj": plugin3, "settings": plugin3_db_conf.config, "user": None},
|
||||
]
|
||||
|
||||
conf = plugins.generate_plugins_conf([plugin1, plugin2, plugin3, plugin4], user=None)
|
||||
assert conf == expected
|
||||
conf = plugins.generate_plugins_conf([plugin1, plugin2, plugin3])
|
||||
assert plugins.update_plugins_conf_with_user_settings(conf, user=None) == expected
|
||||
|
||||
|
||||
def test_attach_plugins_conf(mocker):
|
||||
request = mocker.Mock()
|
||||
generate_plugins_conf = mocker.patch.object(plugins, "generate_plugins_conf")
|
||||
get_all_plugins = mocker.patch.object(plugins, "get_all_plugins")
|
||||
user = mocker.Mock()
|
||||
|
||||
plugins.attach_plugins_conf(request, user=user)
|
||||
plugins.attach_plugins_conf(request)
|
||||
|
||||
generate_plugins_conf.assert_called_once_with(
|
||||
plugins=get_all_plugins.return_value, user=user
|
||||
)
|
||||
generate_plugins_conf.assert_called_once_with(plugins=get_all_plugins.return_value)
|
||||
assert request.plugins_conf == generate_plugins_conf.return_value
|
||||
|
||||
|
||||
|
@ -176,6 +167,6 @@ def test_attach_plugin_noop_if_plugins_disabled(mocker, preferences):
|
|||
preferences["plugins__enabled"] = False
|
||||
request = mocker.Mock()
|
||||
|
||||
plugins.attach_plugins_conf(request, user=None)
|
||||
plugins.attach_plugins_conf(request)
|
||||
|
||||
assert request.plugins_conf is None
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import pytest
|
||||
|
||||
from funkwhale_api import plugins
|
||||
|
||||
from funkwhale_api.common import authentication as common_authentication
|
||||
from funkwhale_api.subsonic import authentication as subsonic_authentication
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"authentication_class, base_class, patched_method",
|
||||
[
|
||||
(
|
||||
common_authentication.SessionAuthentication,
|
||||
common_authentication.BaseSessionAuthentication,
|
||||
"authenticate",
|
||||
),
|
||||
(
|
||||
common_authentication.JSONWebTokenAuthentication,
|
||||
common_authentication.authentication.JSONWebTokenAuthentication,
|
||||
"authenticate",
|
||||
),
|
||||
(
|
||||
common_authentication.JSONWebTokenAuthenticationQS,
|
||||
common_authentication.authentication.BaseJSONWebTokenAuthentication,
|
||||
"authenticate",
|
||||
),
|
||||
(
|
||||
common_authentication.OAuth2Authentication,
|
||||
common_authentication.BaseOAuth2Authentication,
|
||||
"authenticate",
|
||||
),
|
||||
(
|
||||
common_authentication.BearerTokenHeaderAuth,
|
||||
common_authentication.authentication.BaseJSONWebTokenAuthentication,
|
||||
"authenticate",
|
||||
),
|
||||
(
|
||||
subsonic_authentication.SubsonicAuthentication,
|
||||
subsonic_authentication.SubsonicAuthentication,
|
||||
"perform_authentication",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_authentication_calls_update_plugins_conf_with_user_settings(
|
||||
authentication_class, base_class, patched_method, mocker, api_request
|
||||
):
|
||||
request = api_request.get("/")
|
||||
plugins_conf = mocker.Mock()
|
||||
setattr(request, "plugins_conf", plugins_conf)
|
||||
auth = (mocker.Mock(), None)
|
||||
authentication = authentication_class()
|
||||
base_class_authenticate = mocker.patch.object(
|
||||
base_class, patched_method, return_value=auth
|
||||
)
|
||||
update_plugins_conf_with_user_settings = mocker.patch.object(
|
||||
plugins, "update_plugins_conf_with_user_settings"
|
||||
)
|
||||
|
||||
authentication.authenticate(request)
|
||||
|
||||
update_plugins_conf_with_user_settings.assert_called_once_with(
|
||||
plugins_conf, user=auth[0]
|
||||
)
|
||||
base_class_authenticate.assert_called_once_with(request)
|
|
@ -0,0 +1,15 @@
|
|||
from django.http import HttpResponse
|
||||
|
||||
from funkwhale_api.plugins import middleware
|
||||
|
||||
|
||||
def test_attach_plugins_conf_middleware(mocker):
|
||||
attach_plugins_conf = mocker.patch("funkwhale_api.plugins.attach_plugins_conf")
|
||||
|
||||
get_response = mocker.Mock()
|
||||
get_response.return_value = mocker.Mock(status_code=200)
|
||||
request = mocker.Mock(path="/")
|
||||
m = middleware.AttachPluginsConfMiddleware(get_response)
|
||||
|
||||
assert m(request) == get_response.return_value
|
||||
attach_plugins_conf.assert_called_once_with(request)
|
|
@ -1,3 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from funkwhale_api import plugins
|
||||
|
||||
|
||||
|
@ -30,7 +32,8 @@ def test_hooks_dispatch(mocker, plugin_class):
|
|||
handler2.assert_called_once_with(listening="test", plugin_conf=plugins_conf[1])
|
||||
|
||||
|
||||
def test_hooks_dispatch_exception(mocker, plugin_class):
|
||||
def test_hooks_dispatch_exception_fail_loudly_false(mocker, plugin_class, settings):
|
||||
settings.PLUGINS_FAIL_LOUDLY = False
|
||||
plugin1 = plugin_class("test1", "test1")
|
||||
plugin2 = plugin_class("test2", "test2")
|
||||
hook = plugins.Hook("history.listenings.created", providing_args=["listening"])
|
||||
|
@ -49,3 +52,26 @@ def test_hooks_dispatch_exception(mocker, plugin_class):
|
|||
)
|
||||
handler1.assert_called_once_with(listening="test", plugin_conf=plugins_conf[0])
|
||||
handler2.assert_called_once_with(listening="test", plugin_conf=plugins_conf[1])
|
||||
|
||||
|
||||
def test_hooks_dispatch_exception_fail_loudly_true(mocker, plugin_class, settings):
|
||||
settings.PLUGINS_FAIL_LOUDLY = True
|
||||
plugin1 = plugin_class("test1", "test1")
|
||||
plugin2 = plugin_class("test2", "test2")
|
||||
hook = plugins.Hook("history.listenings.created", providing_args=["listening"])
|
||||
plugins.hooks.register(hook)
|
||||
|
||||
handler1 = mocker.Mock(side_effect=Exception("hello"))
|
||||
handler2 = mocker.stub()
|
||||
plugin1.hooks.connect("history.listenings.created")(handler1)
|
||||
plugin2.hooks.connect("history.listenings.created")(handler2)
|
||||
plugins_conf = [
|
||||
{"obj": plugin1, "user": {"hello": "world"}, "settings": {"foo": "bar"}},
|
||||
{"obj": plugin2},
|
||||
]
|
||||
with pytest.raises(Exception, match=r".*hello.*"):
|
||||
plugins.hooks.dispatch(
|
||||
"history.listenings.created", listening="test", plugins_conf=plugins_conf
|
||||
)
|
||||
handler1.assert_called_once_with(listening="test", plugin_conf=plugins_conf[0])
|
||||
handler2.assert_not_called()
|
||||
|
|
Loading…
Reference in New Issue