mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
77 lines
3.4 KiB
Python
77 lines
3.4 KiB
Python
"""
|
|
Regression tests for the worker_ready hook in `dispatcharr/celery.py`
|
|
that eagerly discovers plugins so their @shared_task definitions
|
|
register with the worker before beat starts firing.
|
|
|
|
Without this hook, plugins shipping module-level @shared_task code
|
|
(e.g. cron-scheduled background jobs) silently miss the first beat
|
|
tick after every worker restart — the worker logs
|
|
`Received unregistered task` and beat advances `last_run_at` anyway,
|
|
hiding the failure. See #1244.
|
|
"""
|
|
import weakref
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from django.test import SimpleTestCase
|
|
|
|
|
|
class WorkerReadyPluginDiscoveryTests(SimpleTestCase):
|
|
def test_invokes_discover_plugins_with_sync_db_false(self):
|
|
"""The handler must call PluginManager.discover_plugins(sync_db=False).
|
|
sync_db=False is intentional: discovery on every worker boot must
|
|
not touch the DB schema, just import plugin modules so their
|
|
@shared_task decorators run."""
|
|
from dispatcharr.celery import discover_plugins_on_worker_ready
|
|
|
|
mock_pm = MagicMock()
|
|
with patch(
|
|
"apps.plugins.loader.PluginManager.get", return_value=mock_pm
|
|
) as mock_get:
|
|
discover_plugins_on_worker_ready()
|
|
|
|
mock_get.assert_called_once()
|
|
mock_pm.discover_plugins.assert_called_once_with(sync_db=False)
|
|
|
|
def test_swallows_plugin_loader_errors(self):
|
|
"""If the plugin loader explodes, the worker must still come up —
|
|
the handler must not propagate exceptions."""
|
|
from dispatcharr.celery import discover_plugins_on_worker_ready
|
|
|
|
with patch(
|
|
"apps.plugins.loader.PluginManager.get",
|
|
side_effect=RuntimeError("plugin loader exploded"),
|
|
):
|
|
# Must NOT raise.
|
|
discover_plugins_on_worker_ready()
|
|
|
|
def test_swallows_discover_plugins_errors(self):
|
|
"""Failure inside discover_plugins itself (e.g. one plugin's
|
|
plugin.py has an import error) must also be swallowed — one bad
|
|
plugin shouldn't keep the worker from coming up."""
|
|
from dispatcharr.celery import discover_plugins_on_worker_ready
|
|
|
|
mock_pm = MagicMock()
|
|
mock_pm.discover_plugins.side_effect = ImportError("bad plugin")
|
|
with patch(
|
|
"apps.plugins.loader.PluginManager.get", return_value=mock_pm
|
|
):
|
|
# Must NOT raise.
|
|
discover_plugins_on_worker_ready()
|
|
|
|
def test_handler_is_connected_to_worker_ready(self):
|
|
"""The connect decorator must have wired the handler into the
|
|
worker_ready signal so Celery actually fires it at startup."""
|
|
from celery.signals import worker_ready
|
|
from dispatcharr.celery import discover_plugins_on_worker_ready
|
|
|
|
# signal.receivers is a list of (lookup_key, weakref_or_func) pairs.
|
|
# The handler is registered with weak=False so the function appears
|
|
# directly (not as a dead weakref).
|
|
receivers = [r for _, r in worker_ready.receivers]
|
|
# Dereference weakrefs; pass direct references through as-is.
|
|
callables = [r() if isinstance(r, weakref.ref) else r for r in receivers]
|
|
assert discover_plugins_on_worker_ready in receivers or \
|
|
any(getattr(c, "__wrapped__", c) is discover_plugins_on_worker_ready for c in callables), (
|
|
"discover_plugins_on_worker_ready was not connected to "
|
|
"Celery's worker_ready signal"
|
|
)
|