22Django dynamic rollback integration tests.
33
44Tests DjangoMigrationRunner and DjangoRollbackVerifier using an in-process
5- Django configuration with SQLite — no external database required.
5+ Django configuration with a file-backed SQLite database — no external database
6+ required, but Django must be installed.
7+
8+ Uses a named SQLite file (via tmp_path_factory) so that both Django's own
9+ connection and SQLAlchemy's engine see the same database. The :memory: URI
10+ with NullPool would give each engine.connect() call a fresh, empty database,
11+ making SchemaSnapshot comparisons meaningless.
612
713Skipped automatically when Django is not installed.
814"""
2026 "django.contrib.contenttypes" ,
2127 "django.contrib.auth" ,
2228 "tests.django_app" ,
29+ "tests.django_bad_app" ,
2330]
2431
25- DB_URL = "sqlite:///:memory:"
32+
33+ # ── fixtures ──────────────────────────────────────────────────────────────────
2634
2735
28- def _make_runner ():
36+ @pytest .fixture (scope = "module" )
37+ def django_runner (tmp_path_factory ):
2938 from pytest_mrt .adapters .django_runner import DjangoMigrationRunner
3039
31- return DjangoMigrationRunner (
32- db_url = DB_URL ,
40+ db_path = tmp_path_factory .mktemp ("django_db" ) / "test.db"
41+ db_url = f"sqlite:///{ db_path } "
42+
43+ runner = DjangoMigrationRunner (
44+ db_url = db_url ,
3345 installed_apps = INSTALLED_APPS ,
3446 )
35-
36-
37- # ── fixtures ──────────────────────────────────────────────────────────────────
38-
39-
40- @pytest .fixture (scope = "module" )
41- def django_runner ():
42- runner = _make_runner ()
4347 yield runner
4448 runner .dispose ()
4549
@@ -55,8 +59,8 @@ def test_django_runner_get_migrations(django_runner):
5559
5660
5761def test_django_runner_upgrade_downgrade (django_runner ):
58- """upgrade() then downgrade() leaves DB at original state ."""
59- from pytest_mrt .core .schema import SchemaSnapshot
62+ """upgrade() then downgrade() leaves DB schema unchanged ."""
63+ from pytest_mrt .core .schema import SchemaSnapshot , SchemaDiff
6064
6165 migrations = django_runner .get_migrations (apps = ["django_app" ])
6266 assert migrations , "No migrations found in django_app"
@@ -69,8 +73,6 @@ def test_django_runner_upgrade_downgrade(django_runner):
6973 django_runner .downgrade (first .app_label , first .name )
7074
7175 snap_after = SchemaSnapshot .capture (django_runner .engine )
72- from pytest_mrt .core .schema import SchemaDiff
73-
7476 issues = SchemaDiff ().verify_restored (snap_before , snap_after )
7577 assert issues == [], [i .message for i in issues ]
7678
@@ -87,11 +89,21 @@ def test_django_verifier_check_all_pass(django_runner):
8789 assert not failed , "\n " .join (r .failure_summary () for r in failed )
8890
8991
90- def test_django_verifier_noop_downgrade_fails (tmp_path ):
91- """DjangoRollbackVerifier catches a no-op downgrade migration."""
92- pytest .skip (
93- "No-op downgrade detection requires a separate Django app fixture — "
94- "covered by static analysis (D-pattern checks)"
92+ def test_django_verifier_noop_downgrade_fails (django_runner ):
93+ """DjangoRollbackVerifier detects a migration whose downgrade is a no-op."""
94+ from pytest_mrt .adapters .django_verifier import DjangoRollbackVerifier
95+
96+ verifier = DjangoRollbackVerifier (django_runner , timeout = 30 )
97+ results = verifier .check_all (apps = ["django_bad_app" ])
98+
99+ assert results , "No results — django_bad_app migration not found"
100+ # The first result must be a failure: schema drift (leaked table not dropped)
101+ first = results [0 ]
102+ assert not first .passed , (
103+ "Expected noop downgrade to be detected as a failure, but it passed"
104+ )
105+ assert any ("leaked" in f .lower () or "still exists" in f .lower () for f in first .failures ), (
106+ f"Expected 'still exists' schema error, got: { first .failures } "
95107 )
96108
97109
@@ -100,14 +112,14 @@ def test_mrt_config_django_mode():
100112 from pytest_mrt .config import MRTConfig
101113
102114 cfg = MRTConfig (
103- db_url = DB_URL ,
104- django_settings = None , # not activating Django mode here
115+ db_url = "sqlite:///test.db" ,
116+ django_settings = None ,
105117 )
106118 assert cfg .django_settings is None
107119 assert cfg .django_apps is None
108120
109121 cfg_django = MRTConfig (
110- db_url = DB_URL ,
122+ db_url = "sqlite:///test.db" ,
111123 django_settings = "myproject.settings_test" ,
112124 django_apps = ["users" ],
113125 )
0 commit comments