Skip to content
This repository was archived by the owner on Aug 19, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion django_cf/db/base_engine.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re
import sqlparse
from django.core.exceptions import ImproperlyConfigured
from django.db import DatabaseError, Error, DataError, OperationalError, \
IntegrityError, InternalError, ProgrammingError, NotSupportedError, InterfaceError
from django.db.backends.sqlite3.base import DatabaseWrapper as SQLiteDatabaseWrapper
Expand Down Expand Up @@ -58,7 +59,37 @@ class CFDatabaseIntrospection(SQLiteDatabaseIntrospection):


class CFDatabaseCreation(SQLiteDatabaseCreation):
pass
def _create_test_db(self, verbosity, autoclobber, keepdb=False):
"""
Prevent accidental test runs against production Cloudflare databases.

Cloudflare D1 and Durable Objects do not support isolated test databases
or transactions. Running Django tests directly against these backends will
DESTROY all production data because the backend connects via
CLOUDFLARE_DATABASE_ID (or binding), not via the NAME setting, so the
test runner ends up migrating and flushing the production database.
"""
raise ImproperlyConfigured(
"\n"
"Running Django tests against Cloudflare D1/Durable Objects is not "
"supported because these backends do not support isolated test "
"databases or transactions.\n\n"
"Testing against these backends will DESTROY your production data.\n\n"
"To run tests safely, create a separate Django settings module for "
"testing and switch to a different database engine there. Example:\n\n"
" # settings/test.py\n"
" from .settings import *\n\n"
" DATABASES = {\n"
" 'default': {\n"
" 'ENGINE': 'django.db.backends.sqlite3',\n"
" 'NAME': BASE_DIR / 'test_db.sqlite3',\n"
" }\n"
" }\n\n"
"Then run tests with:\n"
" python manage.py test --settings=settings.test\n\n"
"Or point CLOUDFLARE_DATABASE_ID to a dedicated test D1 database in "
"your test settings module.\n"
)


class CFDatabaseClient(SQLiteDatabaseClient):
Expand Down
53 changes: 53 additions & 0 deletions tests/db/test_creation_safety.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Tests for CFDatabaseCreation test database safety guard."""
import pytest
from unittest.mock import MagicMock
from django.core.exceptions import ImproperlyConfigured

from django_cf.db.base_engine import CFDatabaseCreation


class TestCFDatabaseCreationTestSafety:
"""Tests for CFDatabaseCreation _create_test_db safety guard."""

def _get_creation(self, test_settings=None):
"""Create a CFDatabaseCreation instance with mocked connection."""
mock_connection = MagicMock()
mock_connection.settings_dict = {"TEST": test_settings or {}}
return CFDatabaseCreation(mock_connection)

def test_create_test_db_always_raises_error(self):
"""Test that _create_test_db ALWAYS raises ImproperlyConfigured."""
creation = self._get_creation(test_settings={})

with pytest.raises(ImproperlyConfigured) as exc_info:
creation._create_test_db(verbosity=0, autoclobber=False)

error_msg = str(exc_info.value)
assert "Running Django tests against Cloudflare D1/Durable Objects is not supported" in error_msg
assert "DESTROY your production data" in error_msg
assert "settings/test.py" in error_msg
assert "django.db.backends.sqlite3" in error_msg

def test_create_test_db_raises_even_when_test_name_is_set(self):
"""Test that _create_test_db still raises when TEST['NAME'] is configured."""
# Setting TEST['NAME'] does NOT isolate D1 databases because the D1
# backend connects via CLOUDFLARE_DATABASE_ID, not NAME.
creation = self._get_creation(test_settings={"NAME": ":memory:"})

with pytest.raises(ImproperlyConfigured) as exc_info:
creation._create_test_db(verbosity=0, autoclobber=False)

error_msg = str(exc_info.value)
assert "DESTROY your production data" in error_msg

def test_error_message_includes_safe_config_example(self):
"""Test that error message includes example safe configuration."""
creation = self._get_creation(test_settings={})

with pytest.raises(ImproperlyConfigured) as exc_info:
creation._create_test_db(verbosity=0, autoclobber=False)

error_msg = str(exc_info.value)
assert "settings/test.py" in error_msg
assert "--settings=settings.test" in error_msg
assert "django.db.backends.sqlite3" in error_msg