@@ -1111,3 +1111,141 @@ def pytest_configure(config):
11111111 result = pytester .runpytest ("-v" )
11121112 # Normal user test should still run even if injection fails
11131113 assert result .ret in (0 , 1 , 2 ) # not a hard crash
1114+
1115+
1116+ # ── fine-grained step control (#86) ──────────────────────────────────────────
1117+
1118+
1119+ def _two_step_migrations (versions_dir : str ) -> None :
1120+ _add_migration (
1121+ versions_dir ,
1122+ "001_users.py" ,
1123+ textwrap .dedent ("""
1124+ revision = '001'
1125+ down_revision = None
1126+ branch_labels = None
1127+ depends_on = None
1128+ import sqlalchemy as sa
1129+ from alembic import op
1130+ def upgrade():
1131+ op.create_table('users',
1132+ sa.Column('id', sa.Integer, primary_key=True),
1133+ sa.Column('name', sa.String(64), nullable=False),
1134+ )
1135+ def downgrade():
1136+ op.drop_table('users')
1137+ """ ),
1138+ )
1139+ _add_migration (
1140+ versions_dir ,
1141+ "002_posts.py" ,
1142+ textwrap .dedent ("""
1143+ revision = '002'
1144+ down_revision = '001'
1145+ branch_labels = None
1146+ depends_on = None
1147+ import sqlalchemy as sa
1148+ from alembic import op
1149+ def upgrade():
1150+ op.create_table('posts',
1151+ sa.Column('id', sa.Integer, primary_key=True),
1152+ sa.Column('title', sa.String(128), nullable=False),
1153+ )
1154+ def downgrade():
1155+ op.drop_table('posts')
1156+ """ ),
1157+ )
1158+
1159+
1160+ def _table_names (db_url : str ) -> list [str ]:
1161+ engine = create_engine (db_url )
1162+ with engine .connect () as conn :
1163+ rows = conn .execute (text ("SELECT name FROM sqlite_master WHERE type='table'" )).fetchall ()
1164+ engine .dispose ()
1165+ return [r [0 ] for r in rows ]
1166+
1167+
1168+ def test_upgrade_to (alembic_env ):
1169+ _two_step_migrations (alembic_env ["versions" ])
1170+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1171+ fixture = MRTFixture (cfg )
1172+
1173+ fixture .upgrade_to ("001" )
1174+ tables = _table_names (alembic_env ["db_url" ])
1175+ assert "users" in tables
1176+ assert "posts" not in tables
1177+ fixture .reset ()
1178+
1179+
1180+ def test_upgrade_one (alembic_env ):
1181+ _two_step_migrations (alembic_env ["versions" ])
1182+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1183+ fixture = MRTFixture (cfg )
1184+
1185+ fixture .upgrade_to ("001" )
1186+ fixture .upgrade_one ()
1187+ tables = _table_names (alembic_env ["db_url" ])
1188+ assert "users" in tables
1189+ assert "posts" in tables
1190+ fixture .reset ()
1191+
1192+
1193+ def test_downgrade_one (alembic_env ):
1194+ _two_step_migrations (alembic_env ["versions" ])
1195+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1196+ fixture = MRTFixture (cfg )
1197+
1198+ fixture .upgrade ("head" )
1199+ fixture .downgrade_one ()
1200+ tables = _table_names (alembic_env ["db_url" ])
1201+ assert "posts" not in tables
1202+ assert "users" in tables
1203+ fixture .reset ()
1204+
1205+
1206+ def test_downgrade_to (alembic_env ):
1207+ _two_step_migrations (alembic_env ["versions" ])
1208+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1209+ fixture = MRTFixture (cfg )
1210+
1211+ fixture .upgrade ("head" )
1212+ fixture .downgrade_to ("001" )
1213+ tables = _table_names (alembic_env ["db_url" ])
1214+ assert "posts" not in tables
1215+ assert "users" in tables
1216+ fixture .reset ()
1217+
1218+
1219+ def test_current_revision (alembic_env ):
1220+ _two_step_migrations (alembic_env ["versions" ])
1221+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1222+ fixture = MRTFixture (cfg )
1223+
1224+ assert fixture .current_revision () is None
1225+ fixture .upgrade_to ("001" )
1226+ assert fixture .current_revision () == "001"
1227+ fixture .upgrade_one ()
1228+ assert fixture .current_revision () == "002"
1229+ fixture .reset ()
1230+
1231+
1232+ def test_step_control_data_migration_pattern (alembic_env ):
1233+ """Issue #86 use case: insert legacy data at mid-migration, verify transformation."""
1234+ _two_step_migrations (alembic_env ["versions" ])
1235+ cfg = MRTConfig (alembic_ini = alembic_env ["ini" ], db_url = alembic_env ["db_url" ])
1236+ fixture = MRTFixture (cfg )
1237+
1238+ # Upgrade to first migration, seed data, then upgrade further
1239+ fixture .upgrade_to ("001" )
1240+ fixture .seed ("users" , [{"id" : 1 , "name" : "legacy_user" }], pk_col = "id" )
1241+ fixture .upgrade_one ()
1242+
1243+ # Data seeded before second migration should still be there
1244+ engine = create_engine (alembic_env ["db_url" ])
1245+ with engine .connect () as conn :
1246+ result = conn .execute (text ("SELECT name FROM users WHERE id=1" )).fetchone ()
1247+ engine .dispose ()
1248+ assert result is not None
1249+ assert result [0 ] == "legacy_user"
1250+
1251+ fixture .reset ()
0 commit comments