From ccb44aec6c982759606500501017b0003d7c11cd Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Wed, 3 Jun 2026 16:43:46 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(backend)=20close=20thread=20DB=20c?= =?UTF-8?q?onnections=20to=20fix=20test=20teardown=20OperationalError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When all the tests are ran, there is a PytestWarning log present to warn us that there is remaining database connection open and can not be closed. It appears that the tests about concurrence are responsible of leaving connection open. We need to manually close them in these tests. --- CHANGELOG.md | 5 ++++ .../test_api_documents_children_create.py | 19 ++++++++----- .../documents/test_api_documents_create.py | 27 ++++++++++++------- .../test_api_documents_create_for_owner.py | 26 +++++++++++------- src/backend/core/tests/test_models_users.py | 10 ++++++- 5 files changed, 60 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d785dc2a9..2370d20e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to ## [Unreleased] +### Fixed + +- 🐛(backend) close thread DB connections to fix test teardown + OperationalError #2385 + ## [v5.2.0] - 2026-06-03 ### Added diff --git a/src/backend/core/tests/documents/test_api_documents_children_create.py b/src/backend/core/tests/documents/test_api_documents_children_create.py index 355e9236c..d5e25f7bb 100644 --- a/src/backend/core/tests/documents/test_api_documents_children_create.py +++ b/src/backend/core/tests/documents/test_api_documents_children_create.py @@ -8,6 +8,8 @@ from unittest import mock from unittest.mock import patch from uuid import uuid4 +from django.db import connection + import pytest from rest_framework.test import APIClient @@ -286,12 +288,17 @@ def test_api_documents_create_document_children_race_condition(): factories.UserDocumentAccessFactory(user=user, document=document, role="owner") def create_document(): - return client.post( - f"/api/v1.0/documents/{document.id}/children/", - { - "title": "my child", - }, - ) + try: + return client.post( + f"/api/v1.0/documents/{document.id}/children/", + { + "title": "my child", + }, + ) + finally: + # Close this worker thread's thread-local database connection so it + # does not linger and block dropping the test database at teardown. + connection.close() with ThreadPoolExecutor(max_workers=2) as executor: future1 = executor.submit(create_document) diff --git a/src/backend/core/tests/documents/test_api_documents_create.py b/src/backend/core/tests/documents/test_api_documents_create.py index c2896e2aa..46d34ccf7 100644 --- a/src/backend/core/tests/documents/test_api_documents_create.py +++ b/src/backend/core/tests/documents/test_api_documents_create.py @@ -6,6 +6,8 @@ from concurrent.futures import ThreadPoolExecutor from unittest import mock from uuid import uuid4 +from django.db import connection + import pytest from rest_framework.test import APIClient @@ -70,16 +72,21 @@ def test_api_documents_create_document_race_condition(): """ def create_document(title): - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - return client.post( - "/api/v1.0/documents/", - { - "title": title, - }, - format="json", - ) + try: + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + return client.post( + "/api/v1.0/documents/", + { + "title": title, + }, + format="json", + ) + finally: + # Close this worker thread's thread-local database connection so it + # does not linger and block dropping the test database at teardown. + connection.close() with ThreadPoolExecutor(max_workers=2) as executor: future1 = executor.submit(create_document, "my document 1") diff --git a/src/backend/core/tests/documents/test_api_documents_create_for_owner.py b/src/backend/core/tests/documents/test_api_documents_create_for_owner.py index cf02674c2..7c15e7f1a 100644 --- a/src/backend/core/tests/documents/test_api_documents_create_for_owner.py +++ b/src/backend/core/tests/documents/test_api_documents_create_for_owner.py @@ -9,6 +9,7 @@ from unittest import mock from unittest.mock import patch from django.core import mail +from django.db import connection from django.test import override_settings import pytest @@ -480,16 +481,21 @@ def test_api_documents_create_document_race_condition(): """ def create_document(title): - user = factories.UserFactory() - client = APIClient() - client.force_login(user) - return client.post( - "/api/v1.0/documents/", - { - "title": title, - }, - format="json", - ) + try: + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + return client.post( + "/api/v1.0/documents/", + { + "title": title, + }, + format="json", + ) + finally: + # Close this worker thread's thread-local database connection so it + # does not linger and block dropping the test database at teardown. + connection.close() with ThreadPoolExecutor(max_workers=2) as executor: future1 = executor.submit(create_document, "my document 1") diff --git a/src/backend/core/tests/test_models_users.py b/src/backend/core/tests/test_models_users.py index f1b044db4..b8bc20ffc 100644 --- a/src/backend/core/tests/test_models_users.py +++ b/src/backend/core/tests/test_models_users.py @@ -7,6 +7,7 @@ from concurrent.futures import ThreadPoolExecutor from unittest.mock import patch from django.core.exceptions import ValidationError +from django.db import connection from django.test.utils import override_settings import pytest @@ -341,7 +342,14 @@ def test_models_users_duplicate_onboarding_sandbox_race_condition(): """ def create_user(): - return factories.UserFactory() + try: + return factories.UserFactory() + finally: + # Each worker thread gets its own thread-local database connection. + # Close it explicitly so it does not linger and block dropping the + # test database during teardown (OperationalError: "database is being + # accessed by other users"). + connection.close() template_document = factories.DocumentFactory(title="Getting started with Docs") with (