1
0
mirror of https://github.com/django/django.git synced 2025-10-07 22:09:09 +00:00
django/tests/tasks/test_dummy_backend.py
Jake Howard 4289966d1b Fixed #35859 -- Added background Tasks framework interface.
This work implements what was defined in DEP 14
(https://github.com/django/deps/blob/main/accepted/0014-background-workers.rst).

Thanks to Raphael Gaschignard, Eric Holscher, Ran Benita, Sarah Boyce,
Jacob Walls, and Natalia Bidart for the reviews.
2025-09-16 17:28:32 -03:00

338 lines
12 KiB
Python

from typing import cast
from unittest import mock
from django.db import transaction
from django.db.utils import ConnectionHandler
from django.tasks import TaskResultStatus, default_task_backend, task_backends
from django.tasks.backends.dummy import DummyBackend
from django.tasks.base import Task
from django.tasks.exceptions import InvalidTask, TaskResultDoesNotExist
from django.test import (
SimpleTestCase,
TransactionTestCase,
override_settings,
skipIfDBFeature,
)
from . import tasks as test_tasks
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"QUEUES": [],
"ENQUEUE_ON_COMMIT": False,
}
}
)
class DummyBackendTestCase(SimpleTestCase):
def setUp(self):
default_task_backend.clear()
def test_using_correct_backend(self):
self.assertEqual(default_task_backend, task_backends["default"])
self.assertIsInstance(task_backends["default"], DummyBackend)
self.assertEqual(default_task_backend.alias, "default")
self.assertEqual(default_task_backend.options, {})
def test_enqueue_task(self):
for task in [test_tasks.noop_task, test_tasks.noop_task_async]:
with self.subTest(task):
result = cast(Task, task).enqueue(1, two=3)
self.assertEqual(result.status, TaskResultStatus.READY)
self.assertIs(result.is_finished, False)
self.assertIsNone(result.started_at)
self.assertIsNone(result.last_attempted_at)
self.assertIsNone(result.finished_at)
with self.assertRaisesMessage(ValueError, "Task has not finished yet"):
result.return_value
self.assertEqual(result.task, task)
self.assertEqual(result.args, [1])
self.assertEqual(result.kwargs, {"two": 3})
self.assertEqual(result.attempts, 0)
self.assertIn(result, default_task_backend.results)
async def test_enqueue_task_async(self):
for task in [test_tasks.noop_task, test_tasks.noop_task_async]:
with self.subTest(task):
result = await cast(Task, task).aenqueue()
self.assertEqual(result.status, TaskResultStatus.READY)
self.assertIs(result.is_finished, False)
self.assertIsNone(result.started_at)
self.assertIsNone(result.last_attempted_at)
self.assertIsNone(result.finished_at)
with self.assertRaisesMessage(ValueError, "Task has not finished yet"):
result.return_value
self.assertEqual(result.task, task)
self.assertEqual(result.args, [])
self.assertEqual(result.kwargs, {})
self.assertEqual(result.attempts, 0)
self.assertIn(result, default_task_backend.results)
def test_get_result(self):
result = default_task_backend.enqueue(test_tasks.noop_task, (), {})
new_result = default_task_backend.get_result(result.id)
self.assertEqual(result, new_result)
async def test_get_result_async(self):
result = await default_task_backend.aenqueue(test_tasks.noop_task, (), {})
new_result = await default_task_backend.aget_result(result.id)
self.assertEqual(result, new_result)
def test_refresh_result(self):
result = default_task_backend.enqueue(
test_tasks.calculate_meaning_of_life, (), {}
)
enqueued_result = default_task_backend.results[0]
object.__setattr__(enqueued_result, "status", TaskResultStatus.SUCCESSFUL)
self.assertEqual(result.status, TaskResultStatus.READY)
result.refresh()
self.assertEqual(result.status, TaskResultStatus.SUCCESSFUL)
async def test_refresh_result_async(self):
result = await default_task_backend.aenqueue(
test_tasks.calculate_meaning_of_life, (), {}
)
enqueued_result = default_task_backend.results[0]
object.__setattr__(enqueued_result, "status", TaskResultStatus.SUCCESSFUL)
self.assertEqual(result.status, TaskResultStatus.READY)
await result.arefresh()
self.assertEqual(result.status, TaskResultStatus.SUCCESSFUL)
async def test_get_missing_result(self):
with self.assertRaises(TaskResultDoesNotExist):
default_task_backend.get_result("123")
with self.assertRaises(TaskResultDoesNotExist):
await default_task_backend.aget_result("123")
def test_enqueue_on_commit(self):
self.assertIs(
default_task_backend._get_enqueue_on_commit_for_task(
test_tasks.enqueue_on_commit_task
),
True,
)
def test_enqueue_logs(self):
with self.assertLogs("django.tasks", level="DEBUG") as captured_logs:
result = test_tasks.noop_task.enqueue()
self.assertEqual(len(captured_logs.output), 1)
self.assertIn("enqueued", captured_logs.output[0])
self.assertIn(result.id, captured_logs.output[0])
def test_errors(self):
result = test_tasks.noop_task.enqueue()
self.assertEqual(result.errors, [])
def test_validate_disallowed_async_task(self):
with mock.patch.multiple(default_task_backend, supports_async_task=False):
with self.assertRaisesMessage(
InvalidTask, "Backend does not support async Tasks."
):
default_task_backend.validate_task(test_tasks.noop_task_async)
def test_check(self):
errors = list(default_task_backend.check())
self.assertEqual(len(errors), 0, errors)
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"ENQUEUE_ON_COMMIT": True,
}
}
)
@mock.patch("django.tasks.backends.base.connections", ConnectionHandler({}))
def test_enqueue_on_commit_with_no_databases(self):
self.assertIn(
"tasks.E001", {error.id for error in default_task_backend.check()}
)
def test_takes_context(self):
result = test_tasks.get_task_id.enqueue()
self.assertEqual(result.status, TaskResultStatus.READY)
def test_clear(self):
result = test_tasks.noop_task.enqueue()
default_task_backend.get_result(result.id)
default_task_backend.clear()
with self.assertRaisesMessage(TaskResultDoesNotExist, result.id):
default_task_backend.get_result(result.id)
def test_validate_on_enqueue(self):
task_with_custom_queue_name = test_tasks.noop_task.using(
queue_name="unknown_queue"
)
with override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"QUEUES": ["queue-1"],
"ENQUEUE_ON_COMMIT": False,
}
}
):
with self.assertRaisesMessage(
InvalidTask, "Queue 'unknown_queue' is not valid for backend"
):
task_with_custom_queue_name.enqueue()
async def test_validate_on_aenqueue(self):
task_with_custom_queue_name = test_tasks.noop_task.using(
queue_name="unknown_queue"
)
with override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"QUEUES": ["queue-1"],
"ENQUEUE_ON_COMMIT": False,
}
}
):
with self.assertRaisesMessage(
InvalidTask, "Queue 'unknown_queue' is not valid for backend"
):
await task_with_custom_queue_name.aenqueue()
class DummyBackendTransactionTestCase(TransactionTestCase):
available_apps = []
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"ENQUEUE_ON_COMMIT": True,
}
}
)
def test_wait_until_transaction_commit(self):
self.assertIs(default_task_backend.enqueue_on_commit, True)
self.assertIs(
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
True,
)
with transaction.atomic():
test_tasks.noop_task.enqueue()
self.assertEqual(len(default_task_backend.results), 0)
self.assertEqual(len(default_task_backend.results), 1)
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"ENQUEUE_ON_COMMIT": False,
}
}
)
def test_doesnt_wait_until_transaction_commit(self):
self.assertIs(default_task_backend.enqueue_on_commit, False)
self.assertIs(
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
False,
)
with transaction.atomic():
result = test_tasks.noop_task.enqueue()
self.assertIsNotNone(result.enqueued_at)
self.assertEqual(len(default_task_backend.results), 1)
self.assertEqual(len(default_task_backend.results), 1)
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
}
}
)
def test_wait_until_transaction_by_default(self):
self.assertIs(default_task_backend.enqueue_on_commit, True)
self.assertIs(
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
True,
)
with transaction.atomic():
result = test_tasks.noop_task.enqueue()
self.assertIsNone(result.enqueued_at)
self.assertEqual(len(default_task_backend.results), 0)
self.assertEqual(len(default_task_backend.results), 1)
self.assertIsNone(result.enqueued_at)
result.refresh()
self.assertIsNotNone(result.enqueued_at)
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"ENQUEUE_ON_COMMIT": False,
}
}
)
def test_task_specific_enqueue_on_commit(self):
self.assertIs(default_task_backend.enqueue_on_commit, False)
self.assertIs(test_tasks.enqueue_on_commit_task.enqueue_on_commit, True)
self.assertIs(
default_task_backend._get_enqueue_on_commit_for_task(
test_tasks.enqueue_on_commit_task
),
True,
)
with transaction.atomic():
result = test_tasks.enqueue_on_commit_task.enqueue()
self.assertIsNone(result.enqueued_at)
self.assertEqual(len(default_task_backend.results), 0)
self.assertEqual(len(default_task_backend.results), 1)
self.assertIsNone(result.enqueued_at)
result.refresh()
self.assertIsNotNone(result.enqueued_at)
@override_settings(
TASKS={
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
"ENQUEUE_ON_COMMIT": True,
}
}
)
@skipIfDBFeature("supports_transactions")
def test_enqueue_on_commit_with_no_transactions(self):
self.assertIn(
"tasks.E002", {error.id for error in default_task_backend.check()}
)