Summary
When running Django tests with django-cf's D1 backend (django_cf.d1_api or django_cf.db.backends.d1), Django's test runner does not create a separate test database. Instead, it connects directly to the production D1 database and wipes all data during test setup/teardown.
Impact
This is a critical data-loss bug. My production Cloudflare D1 was wiped out. But luckily the Cloudflare D1 has Time travel enabled by default. Therefore I was able to restore the data.
Steps to Reproduce
- Configure Django with the D1 API backend:
DATABASES = {
'default': {
'ENGINE': 'django_cf.d1_api', # or django_cf.db.backends.d1
'CLOUDFLARE_DATABASE_ID': '...',
'CLOUDFLARE_ACCOUNT_ID': '...',
'CLOUDFLARE_TOKEN': '...',
}
}
- Have existing data in the D1 database (e.g., tables from previous migrations).
- Run
python manage.py test.
Expected Behavior
Django's test runner should either:
- Create a separate, isolated test database on D1, or
- Raise a clear error/warning that testing against D1 is unsafe, or
- Use an in-memory SQLite database for tests by default.
Actual Behavior
The test runner executes migrations, flushes data, and runs tests directly against the configured D1 database. Since D1 does not support database-level cloning or rollback (transactions are disabled), all production data is permanently lost.
Root Cause Analysis
Looking at the D1 backend implementation:
- In
django_cf/db/backends/d1/base.py, DatabaseWrapper inherits from CFDatabaseWrapper.
CFDatabaseCreation inherits from Django's SQLiteDatabaseCreation.
- The backend does not override the test database creation methods (
_create_test_db, _destroy_test_db, etc.) to account for D1's serverless nature.
- Unlike SQLite (which creates a local file), D1 is a remote HTTP API. Django cannot simply create a "test_" prefixed database file.
- The
supports_transactions = False flag is set, but Django still attempts to flush and migrate the "test" schema, which in practice targets the same database.
Environment
django-cf version: >=0.1.1 (tested on 0.1.1, likely affects 0.2.x as well)
- Django version:
>=5.0
- Python version:
>=3.10
Suggested Fix
Short-term (Immediate)
Override DatabaseCreation in the D1 backend to raise a ImproperlyConfigured exception if manage.py test is detected, with a message like:
"Running Django tests against Cloudflare D1 is not supported because D1 does not support isolated test databases. Please configure a separate test database (e.g., SQLite or a dedicated D1 database) using the TEST setting."
Long-term
Implement proper test database isolation, such as:
- SQLite fallback for tests: If
TEST configuration is not provided, default to an in-memory SQLite database for tests.
- D1 test database creation: Allow users to specify a separate
CLOUDFLARE_DATABASE_ID under DATABASES['default']['TEST'], and create/destroy that via the D1 API during test runs.
Example safe configuration pattern:
DATABASES = {
'default': {
'ENGINE': 'django_cf.db.backends.d1',
'CLOUDFLARE_DATABASE_ID': os.getenv('CLOUDFLARE_DATABASE_ID'),
'TEST': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
},
}
}
Additional Context
This bug has already caused complete data loss in a production D1 database. I discovered this after running python manage.py test and finding all tables and data erased. Because D1 does not support transactions or point-in-time recovery, the data is unrecoverable.
Please prioritize this issue, as it poses a serious risk to anyone using django-cf with D1 in production.
Summary
When running Django tests with
django-cf's D1 backend (django_cf.d1_apiordjango_cf.db.backends.d1), Django's test runner does not create a separate test database. Instead, it connects directly to the production D1 database and wipes all data during test setup/teardown.Impact
This is a critical data-loss bug. My production Cloudflare D1 was wiped out. But luckily the Cloudflare D1 has Time travel enabled by default. Therefore I was able to restore the data.
Steps to Reproduce
python manage.py test.Expected Behavior
Django's test runner should either:
Actual Behavior
The test runner executes migrations, flushes data, and runs tests directly against the configured D1 database. Since D1 does not support database-level cloning or rollback (transactions are disabled), all production data is permanently lost.
Root Cause Analysis
Looking at the D1 backend implementation:
django_cf/db/backends/d1/base.py,DatabaseWrapperinherits fromCFDatabaseWrapper.CFDatabaseCreationinherits from Django'sSQLiteDatabaseCreation._create_test_db,_destroy_test_db, etc.) to account for D1's serverless nature.supports_transactions = Falseflag is set, but Django still attempts to flush and migrate the "test" schema, which in practice targets the same database.Environment
django-cfversion:>=0.1.1(tested on 0.1.1, likely affects 0.2.x as well)>=5.0>=3.10Suggested Fix
Short-term (Immediate)
Override
DatabaseCreationin the D1 backend to raise aImproperlyConfiguredexception ifmanage.py testis detected, with a message like:Long-term
Implement proper test database isolation, such as:
TESTconfiguration is not provided, default to an in-memory SQLite database for tests.CLOUDFLARE_DATABASE_IDunderDATABASES['default']['TEST'], and create/destroy that via the D1 API during test runs.Example safe configuration pattern:
Additional Context
This bug has already caused complete data loss in a production D1 database. I discovered this after running
python manage.py testand finding all tables and data erased. Because D1 does not support transactions or point-in-time recovery, the data is unrecoverable.Please prioritize this issue, as it poses a serious risk to anyone using django-cf with D1 in production.