|
7 | 7 | from typing import Sequence, Union, cast
|
8 | 8 | from unittest import mock, skipIf
|
9 | 9 |
|
| 10 | +import django |
10 | 11 | from django.core.exceptions import SuspiciousOperation
|
11 | 12 | from django.core.management import call_command, execute_from_command_line
|
12 | 13 | from django.db import connection, connections, transaction
|
13 | 14 | from django.db.models import QuerySet
|
14 | 15 | from django.db.utils import IntegrityError, OperationalError
|
15 |
| -from django.test import TransactionTestCase, override_settings |
| 16 | +from django.test import TestCase, TransactionTestCase, override_settings |
16 | 17 | from django.urls import reverse
|
17 | 18 | from django.utils import timezone
|
18 | 19 |
|
|
22 | 23 | logger as db_worker_logger,
|
23 | 24 | )
|
24 | 25 | from django_tasks.backends.database.models import DBTaskResult
|
25 |
| -from django_tasks.backends.database.utils import exclusive_transaction, normalize_uuid |
| 26 | +from django_tasks.backends.database.utils import ( |
| 27 | + connection_requires_manual_exclusive_transaction, |
| 28 | + exclusive_transaction, |
| 29 | + normalize_uuid, |
| 30 | +) |
26 | 31 | from django_tasks.exceptions import ResultDoesNotExist
|
27 | 32 | from tests import tasks as test_tasks
|
28 | 33 |
|
@@ -211,7 +216,7 @@ def test_missing_task_path(self) -> None:
|
211 | 216 | def test_check(self) -> None:
|
212 | 217 | errors = list(default_task_backend.check())
|
213 | 218 |
|
214 |
| - self.assertEqual(len(errors), 0) |
| 219 | + self.assertEqual(len(errors), 0, errors) |
215 | 220 |
|
216 | 221 | @override_settings(INSTALLED_APPS=[])
|
217 | 222 | def test_database_backend_app_missing(self) -> None:
|
@@ -873,8 +878,46 @@ def test_get_locked_with_locked_rows(self) -> None:
|
873 | 878 | normalize_uuid(result_2.id),
|
874 | 879 | )
|
875 | 880 | self.assertEqual(
|
876 |
| - normalize_uuid(DBTaskResult.objects.get_locked().id), # type:ignore[union-attr] |
| 881 | + normalize_uuid(DBTaskResult.objects.get_locked().id), # type:ignore |
877 | 882 | normalize_uuid(result_2.id),
|
878 | 883 | )
|
879 | 884 | finally:
|
880 | 885 | new_connection.close()
|
| 886 | + |
| 887 | + |
| 888 | +class ConnectionExclusiveTranscationTestCase(TestCase): |
| 889 | + def setUp(self) -> None: |
| 890 | + self.connection = connections.create_connection("default") |
| 891 | + |
| 892 | + def tearDown(self) -> None: |
| 893 | + self.connection.close() |
| 894 | + |
| 895 | + @skipIf(connection.vendor == "sqlite", "SQLite handled separately") |
| 896 | + def test_non_sqlite(self) -> None: |
| 897 | + self.assertFalse( |
| 898 | + connection_requires_manual_exclusive_transaction(self.connection) |
| 899 | + ) |
| 900 | + |
| 901 | + @skipIf( |
| 902 | + django.VERSION >= (5, 1), |
| 903 | + "Newer Django versions support custom transaction modes", |
| 904 | + ) |
| 905 | + @skipIf(connection.vendor != "sqlite", "SQLite only") |
| 906 | + def test_old_django_requires_manual_transaction(self) -> None: |
| 907 | + self.assertTrue( |
| 908 | + connection_requires_manual_exclusive_transaction(self.connection) |
| 909 | + ) |
| 910 | + |
| 911 | + @skipIf(django.VERSION < (5, 1), "Old Django versions require manual transactions") |
| 912 | + @skipIf(connection.vendor != "sqlite", "SQLite only") |
| 913 | + def test_explicit_transaction(self) -> None: |
| 914 | + # HACK: Set the attribute manually |
| 915 | + self.connection.transaction_mode = None # type:ignore[attr-defined] |
| 916 | + self.assertTrue( |
| 917 | + connection_requires_manual_exclusive_transaction(self.connection) |
| 918 | + ) |
| 919 | + |
| 920 | + self.connection.transaction_mode = "EXCLUSIVE" # type:ignore[attr-defined] |
| 921 | + self.assertFalse( |
| 922 | + connection_requires_manual_exclusive_transaction(self.connection) |
| 923 | + ) |
0 commit comments