From 78835b9182af0188d345b436abb6eef1605fb7d7 Mon Sep 17 00:00:00 2001 From: Jiahao Ren Date: Thu, 30 Apr 2026 16:54:53 +0000 Subject: [PATCH] fix: disallow nesting of part_of_a_transaction Add durable=True to the atomic call inside part_of_a_transaction so that nesting is explicitly disallowed. This prevents tests from accidentally simulating nested transaction behaviour, which does not reflect real-world usage. Fixes #150 --- CHANGELOG.md | 2 ++ src/django_subatomic/test.py | 2 +- tests/test_db.py | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbec40..bb24987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - Added MariaDB and SQLite to the test matrix. +- Disallowed nesting of `part_of_a_transaction` to prevent accidental + simulation of nested transaction behaviour in tests. Fixes #150. ## [1.0.0] - 2026-04-16 diff --git a/src/django_subatomic/test.py b/src/django_subatomic/test.py index d29f64e..3fde7b5 100644 --- a/src/django_subatomic/test.py +++ b/src/django_subatomic/test.py @@ -32,5 +32,5 @@ def part_of_a_transaction(using: str | None = None) -> Generator[None]: Note that this does not handle after-commit callback simulation. If you need that, use [`transaction`][django_subatomic.db.transaction] instead. """ - with transaction.atomic(using=using): + with transaction.atomic(using=using, durable=True): yield diff --git a/tests/test_db.py b/tests/test_db.py index 602cab4..d47a55e 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -413,6 +413,50 @@ def test_creates_savepoint( assert release_savepoint["sql"].startswith("RELEASE SAVEPOINT ") +class TestPartOfATransaction: + @pytest.mark.django_db(transaction=True) + def test_fails_when_nested_inside_another_part_of_a_transaction(self) -> None: + """ + `part_of_a_transaction` cannot be nested inside another `part_of_a_transaction`. + """ + with pytest.raises( + RuntimeError, + match=re.escape( + "A durable atomic block cannot be nested within another atomic block." + ), + ): + with test.part_of_a_transaction(): + with test.part_of_a_transaction(): + ... + + @pytest.mark.django_db(transaction=True) + def test_fails_when_nested_inside_an_atomic_block(self) -> None: + """ + `part_of_a_transaction` cannot be nested inside an existing atomic block. + """ + with pytest.raises( + RuntimeError, + match=re.escape( + "A durable atomic block cannot be nested within another atomic block." + ), + ): + with django_transaction.atomic(): + with test.part_of_a_transaction(): + ... + + @pytest.mark.django_db(transaction=True) + def test_creates_transaction(self) -> None: + """ + A transaction is active within the context manager. + """ + assert django_transaction.get_autocommit() is True + + with test.part_of_a_transaction(): + assert django_transaction.get_autocommit() is False + + assert django_transaction.get_autocommit() is True + + class TestTransactionIfNotAlready: def test_transaction_already_exists( self, django_assert_num_queries: pytest_django.DjangoAssertNumQueries