pytest-mrt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_mrt-0.1.0/.github/workflows/ci.yml +59 -0
- pytest_mrt-0.1.0/.gitignore +18 -0
- pytest_mrt-0.1.0/CONTRIBUTING.md +65 -0
- pytest_mrt-0.1.0/LICENSE +21 -0
- pytest_mrt-0.1.0/PKG-INFO +335 -0
- pytest_mrt-0.1.0/README.md +276 -0
- pytest_mrt-0.1.0/examples/blog/alembic/env.py +30 -0
- pytest_mrt-0.1.0/examples/blog/alembic/versions/001_create_users.py +27 -0
- pytest_mrt-0.1.0/examples/blog/alembic/versions/002_create_posts.py +27 -0
- pytest_mrt-0.1.0/examples/blog/alembic/versions/003_add_bio.py +21 -0
- pytest_mrt-0.1.0/examples/blog/alembic/versions/004_drop_phone.py +26 -0
- pytest_mrt-0.1.0/examples/blog/alembic/versions/005_irreversible_data_migration.py +25 -0
- pytest_mrt-0.1.0/examples/blog/alembic.ini +37 -0
- pytest_mrt-0.1.0/examples/blog/conftest.py +9 -0
- pytest_mrt-0.1.0/examples/blog/test_migrations.py +31 -0
- pytest_mrt-0.1.0/pyproject.toml +63 -0
- pytest_mrt-0.1.0/pytest_mrt/__init__.py +4 -0
- pytest_mrt-0.1.0/pytest_mrt/adapters/__init__.py +0 -0
- pytest_mrt-0.1.0/pytest_mrt/cli.py +63 -0
- pytest_mrt-0.1.0/pytest_mrt/config.py +8 -0
- pytest_mrt-0.1.0/pytest_mrt/core/__init__.py +0 -0
- pytest_mrt-0.1.0/pytest_mrt/core/detector.py +259 -0
- pytest_mrt-0.1.0/pytest_mrt/core/runner.py +39 -0
- pytest_mrt-0.1.0/pytest_mrt/core/schema.py +120 -0
- pytest_mrt-0.1.0/pytest_mrt/core/seeder.py +152 -0
- pytest_mrt-0.1.0/pytest_mrt/core/verifier.py +72 -0
- pytest_mrt-0.1.0/pytest_mrt/plugin.py +77 -0
- pytest_mrt-0.1.0/pytest_mrt/reporter.py +55 -0
- pytest_mrt-0.1.0/tests/__init__.py +0 -0
- pytest_mrt-0.1.0/tests/test_detector.py +218 -0
- pytest_mrt-0.1.0/tests/test_integration.py +303 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install
|
|
24
|
+
run: pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: pytest tests/ -v
|
|
28
|
+
|
|
29
|
+
test-postgres:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
services:
|
|
32
|
+
postgres:
|
|
33
|
+
image: postgres:15
|
|
34
|
+
env:
|
|
35
|
+
POSTGRES_USER: mrt
|
|
36
|
+
POSTGRES_PASSWORD: mrt
|
|
37
|
+
POSTGRES_DB: mrt_test
|
|
38
|
+
options: >-
|
|
39
|
+
--health-cmd pg_isready
|
|
40
|
+
--health-interval 10s
|
|
41
|
+
--health-timeout 5s
|
|
42
|
+
--health-retries 5
|
|
43
|
+
ports:
|
|
44
|
+
- 5432:5432
|
|
45
|
+
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
|
|
49
|
+
- uses: actions/setup-python@v5
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.11"
|
|
52
|
+
|
|
53
|
+
- name: Install
|
|
54
|
+
run: pip install -e ".[dev]" psycopg2-binary
|
|
55
|
+
|
|
56
|
+
- name: Run postgres integration tests
|
|
57
|
+
env:
|
|
58
|
+
MRT_TEST_DB_URL: postgresql://mrt:mrt@localhost:5432/mrt_test
|
|
59
|
+
run: pytest tests/ -v -m postgres --ignore=tests/test_integration.py
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributing to pytest-mrt
|
|
2
|
+
|
|
3
|
+
Thank you for considering a contribution. Here's how to get started.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/croc100/pytest-mrt
|
|
9
|
+
cd pytest-mrt
|
|
10
|
+
python3 -m venv .venv
|
|
11
|
+
source .venv/bin/activate
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Running tests
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# All tests (SQLite, no external dependencies)
|
|
19
|
+
pytest tests/ -v
|
|
20
|
+
|
|
21
|
+
# With PostgreSQL
|
|
22
|
+
MRT_TEST_DB_URL=postgresql://localhost/mrt_test pytest tests/ -v -m postgres
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What to work on
|
|
26
|
+
|
|
27
|
+
Good first issues:
|
|
28
|
+
- Add a new static analysis pattern to `pytest_mrt/core/detector.py`
|
|
29
|
+
- Add a new integration test case to `tests/test_integration.py`
|
|
30
|
+
- Improve error messages in `pytest_mrt/reporter.py`
|
|
31
|
+
|
|
32
|
+
Higher effort:
|
|
33
|
+
- Django Migrations adapter (`pytest_mrt/adapters/django.py`)
|
|
34
|
+
- MySQL support
|
|
35
|
+
- HTML report output
|
|
36
|
+
|
|
37
|
+
## Adding a new risk pattern
|
|
38
|
+
|
|
39
|
+
1. Write a `_check_*` function in `pytest_mrt/core/detector.py`
|
|
40
|
+
2. Add it to the `_CHECKS` list at the bottom
|
|
41
|
+
3. Write a test in `tests/test_detector.py` (both a positive and a negative case)
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
def _check_my_new_pattern(source: str, rev: str, fname: str) -> list[RiskWarning]:
|
|
45
|
+
body = _upgrade_body(source)
|
|
46
|
+
if re.search(r"some_pattern", body):
|
|
47
|
+
return [RiskWarning(rev, fname, "Pattern name",
|
|
48
|
+
"Human-readable explanation of the risk", "error")]
|
|
49
|
+
return []
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Commit style
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
feat: add MySQL support
|
|
56
|
+
fix: handle empty downgrade body correctly
|
|
57
|
+
docs: add Django example
|
|
58
|
+
test: cover NOT NULL without default on existing table
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Pull request checklist
|
|
62
|
+
|
|
63
|
+
- [ ] Tests added for new behavior
|
|
64
|
+
- [ ] All existing tests pass (`pytest tests/ -v`)
|
|
65
|
+
- [ ] `mrt check examples/blog/alembic/versions/` still works
|
pytest_mrt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 croc100
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytest-mrt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Catch database migration rollback failures before they reach production
|
|
5
|
+
Project-URL: Homepage, https://github.com/croc100/pytest-mrt
|
|
6
|
+
Project-URL: Repository, https://github.com/croc100/pytest-mrt
|
|
7
|
+
Project-URL: Issues, https://github.com/croc100/pytest-mrt/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/croc100/pytest-mrt/releases
|
|
9
|
+
Author: croc100
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 croc100
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: alembic,database,migrations,pytest,rollback,sqlalchemy,testing
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Framework :: Pytest
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Topic :: Database
|
|
43
|
+
Classifier: Topic :: Software Development :: Testing
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Requires-Dist: alembic>=1.9
|
|
46
|
+
Requires-Dist: pytest>=7.0
|
|
47
|
+
Requires-Dist: rich>=13.0
|
|
48
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
49
|
+
Requires-Dist: typer>=0.9
|
|
50
|
+
Provides-Extra: asyncpg
|
|
51
|
+
Requires-Dist: asyncpg; extra == 'asyncpg'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: hatch; extra == 'dev'
|
|
54
|
+
Requires-Dist: psycopg2-binary; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
56
|
+
Provides-Extra: postgres
|
|
57
|
+
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# pytest-mrt
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
<strong>Migration Rollback Tester</strong><br>
|
|
64
|
+
Catch database migration disasters before they reach production.
|
|
65
|
+
</p>
|
|
66
|
+
|
|
67
|
+
<p align="center">
|
|
68
|
+
<a href="https://pypi.org/project/pytest-mrt"><img src="https://img.shields.io/pypi/v/pytest-mrt?color=blue" alt="PyPI"></a>
|
|
69
|
+
<a href="https://github.com/croc100/pytest-mrt/actions"><img src="https://img.shields.io/github/actions/workflow/status/croc100/pytest-mrt/ci.yml?branch=main" alt="CI"></a>
|
|
70
|
+
<a href="https://pypi.org/project/pytest-mrt"><img src="https://img.shields.io/pypi/pyversions/pytest-mrt" alt="Python"></a>
|
|
71
|
+
<a href="https://github.com/croc100/pytest-mrt/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License"></a>
|
|
72
|
+
</p>
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## The problem
|
|
77
|
+
|
|
78
|
+
It's 2am. Your new feature is deployed. Something is wrong. You run `alembic downgrade -1`.
|
|
79
|
+
|
|
80
|
+
The command succeeds. But the data is gone.
|
|
81
|
+
|
|
82
|
+
The column came back. The rows didn't.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
This happens because **most tools only check if your migration runs without errors** — not whether your data survives the round-trip. `alembic downgrade` can succeed while silently destroying everything it was supposed to restore.
|
|
87
|
+
|
|
88
|
+
**pytest-mrt** tests the full cycle: seed real data → upgrade → downgrade → verify nothing was lost.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Install
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install pytest-mrt
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Quickstart
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# conftest.py
|
|
104
|
+
from pytest_mrt import MRTConfig
|
|
105
|
+
|
|
106
|
+
def pytest_configure(config):
|
|
107
|
+
config._mrt_config = MRTConfig(
|
|
108
|
+
alembic_ini="alembic.ini",
|
|
109
|
+
db_url="postgresql://localhost/myapp_test",
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# test_migrations.py
|
|
115
|
+
def test_all_migrations_are_reversible(mrt):
|
|
116
|
+
mrt.assert_all_reversible()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
$ pytest test_migrations.py -s
|
|
121
|
+
|
|
122
|
+
──────────── MRT — Migration Rollback Test ────────────
|
|
123
|
+
|
|
124
|
+
✓ 001 reversible
|
|
125
|
+
✓ 002 reversible
|
|
126
|
+
✓ 003 reversible
|
|
127
|
+
✗ 004 data loss detected
|
|
128
|
+
└─ Table 'users': 3/3 rows lost after rollback
|
|
129
|
+
✗ 005 data loss detected
|
|
130
|
+
└─ Table 'users' still exists after rollback — downgrade is incomplete
|
|
131
|
+
|
|
132
|
+
╭─────────────────────────────────────────────────────╮
|
|
133
|
+
│ 2 migration(s) will cause data loss on rollback. │
|
|
134
|
+
│ 004 │
|
|
135
|
+
│ └─ Table 'users': 3/3 rows lost after rollback │
|
|
136
|
+
│ 005 │
|
|
137
|
+
│ └─ Table 'users' still exists after rollback │
|
|
138
|
+
╰─────────────────────────────────────────────────────╯
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## What it catches
|
|
144
|
+
|
|
145
|
+
### Static analysis — before you even run
|
|
146
|
+
|
|
147
|
+
| Pattern | Severity | Why it's dangerous |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `op.drop_column()` in upgrade | 🔴 error | Column data is permanently gone |
|
|
150
|
+
| `op.drop_table()` in upgrade | 🔴 error | All table data is permanently gone |
|
|
151
|
+
| `TRUNCATE` in migration | 🔴 error | Destroys data with no undo |
|
|
152
|
+
| `def downgrade(): pass` | 🔴 error | Rollback silently does nothing |
|
|
153
|
+
| No `downgrade()` function | 🔴 error | Migration is completely irreversible |
|
|
154
|
+
| `RunPython` without `reverse_func` | 🔴 error | Data transformation cannot be undone |
|
|
155
|
+
| `NOT NULL` without `server_default` | 🟡 warning | Will fail on non-empty tables |
|
|
156
|
+
| `ALTER COLUMN type_=...` | 🟡 warning | Type conversion may lose data |
|
|
157
|
+
| `op.execute()` with raw SQL | 🟡 warning | Cannot verify reversibility |
|
|
158
|
+
| Bulk `UPDATE` without reverse | 🟡 warning | One-way data transformation |
|
|
159
|
+
| `ON DELETE CASCADE` added | 🟡 warning | Child rows silently deleted |
|
|
160
|
+
| `CREATE INDEX` without `CONCURRENTLY` | 🟡 warning | Locks table during index build |
|
|
161
|
+
| `ADD COLUMN` with `DEFAULT` | 🟡 warning | Full table rewrite on PostgreSQL < 11 |
|
|
162
|
+
| `CREATE UNIQUE CONSTRAINT` | 🟡 warning | Will fail if duplicates exist |
|
|
163
|
+
| `NOT NULL` without restoring `nullable` | 🟡 warning | Downgrade leaves column in wrong state |
|
|
164
|
+
|
|
165
|
+
Run static analysis without a database:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
mrt check migrations/versions/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
173
|
+
│ Rollback Risk Analysis │
|
|
174
|
+
├──────────┬──────────────────────┬─────────────┬─────────────────────────── │
|
|
175
|
+
│ Revision │ Pattern │ Sev │ Message │
|
|
176
|
+
├──────────┼──────────────────────┼─────────────┼─────────────────────────── │
|
|
177
|
+
│ 004 │ DROP COLUMN │ error │ Data loss on rollback │
|
|
178
|
+
│ 005 │ No-op downgrade │ error │ downgrade() does nothing │
|
|
179
|
+
│ 006 │ INDEX without CONC. │ warning │ Locks table during build │
|
|
180
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
181
|
+
2 error(s), 1 warning(s)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Dynamic verification — with real data
|
|
185
|
+
|
|
186
|
+
pytest-mrt seeds actual rows before each migration, then checks they survive the downgrade:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
def test_specific_revision(mrt):
|
|
190
|
+
result = mrt.check_revision("abc123")
|
|
191
|
+
assert result.passed, result.failure_summary()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Or test everything at once:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
def test_all_migrations(mrt):
|
|
198
|
+
mrt.assert_all_reversible()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## How it works
|
|
204
|
+
|
|
205
|
+
For each migration revision, pytest-mrt:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
1. Capture schema at current state
|
|
209
|
+
2. Seed real data into all existing tables
|
|
210
|
+
3. Run upgrade to this revision
|
|
211
|
+
4. Run downgrade (one step back)
|
|
212
|
+
5. Verify schema is exactly restored
|
|
213
|
+
6. Verify every seeded row survived
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
This catches failures that syntax checks miss:
|
|
217
|
+
- Schema comes back, but seeded rows are gone → **data loss**
|
|
218
|
+
- Downgrade is a no-op, table still exists → **rollback did nothing**
|
|
219
|
+
- Column returns but with wrong type → **schema drift**
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Supported databases
|
|
224
|
+
|
|
225
|
+
| Database | Status |
|
|
226
|
+
|---|---|
|
|
227
|
+
| PostgreSQL | ✅ Full support |
|
|
228
|
+
| SQLite | ✅ Full support (great for CI) |
|
|
229
|
+
| MySQL / MariaDB | 🔜 Planned |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## CI integration
|
|
234
|
+
|
|
235
|
+
Add to your GitHub Actions workflow:
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
- name: Test migration rollbacks
|
|
239
|
+
run: pytest tests/test_migrations.py -v -s
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Or use the static check as a fast pre-flight:
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
- name: Static migration analysis
|
|
246
|
+
run: mrt check migrations/versions/ --strict
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`--strict` makes warnings fail the build, not just errors.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Configuration
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
# conftest.py
|
|
257
|
+
from pytest_mrt import MRTConfig
|
|
258
|
+
|
|
259
|
+
def pytest_configure(config):
|
|
260
|
+
config._mrt_config = MRTConfig(
|
|
261
|
+
alembic_ini="alembic.ini", # path to alembic.ini
|
|
262
|
+
db_url="postgresql://...", # test database URL
|
|
263
|
+
seed_rows=5, # rows to seed per table (default: 3)
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Use environment variables for CI:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
import os
|
|
271
|
+
from pytest_mrt import MRTConfig
|
|
272
|
+
|
|
273
|
+
def pytest_configure(config):
|
|
274
|
+
config._mrt_config = MRTConfig(
|
|
275
|
+
alembic_ini="alembic.ini",
|
|
276
|
+
db_url=os.environ["TEST_DATABASE_URL"],
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Examples
|
|
283
|
+
|
|
284
|
+
See [`examples/blog/`](examples/blog/) for a complete working example with:
|
|
285
|
+
- Safe migrations (add nullable column, create table)
|
|
286
|
+
- Dangerous migrations (drop column with data, no-op downgrade)
|
|
287
|
+
- How pytest-mrt catches each failure
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
cd examples/blog
|
|
291
|
+
pip install pytest-mrt
|
|
292
|
+
pytest test_migrations.py -v -s
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## FAQ
|
|
298
|
+
|
|
299
|
+
**Does it modify my production database?**
|
|
300
|
+
No. pytest-mrt only runs against the database URL you provide in `MRTConfig`. Always use a test database.
|
|
301
|
+
|
|
302
|
+
**Does it work with Django migrations?**
|
|
303
|
+
Django support is on the roadmap. Currently only Alembic is supported.
|
|
304
|
+
|
|
305
|
+
**How is this different from pytest-alembic?**
|
|
306
|
+
`pytest-alembic` checks that migrations run without errors and that your schema matches your models. It does **not** verify that data survives a rollback. pytest-mrt focuses specifically on that gap.
|
|
307
|
+
|
|
308
|
+
**My migration intentionally drops a column. Will this always fail?**
|
|
309
|
+
Yes — dropping a column destroys data. That's exactly what pytest-mrt warns you about. If you want to proceed, you can exclude specific revisions or mark the test as expected-to-fail.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Roadmap
|
|
314
|
+
|
|
315
|
+
- [x] Alembic support
|
|
316
|
+
- [x] Static risk analysis CLI (`mrt check`)
|
|
317
|
+
- [x] Dynamic data integrity verification
|
|
318
|
+
- [x] GitHub Actions CI
|
|
319
|
+
- [ ] Django Migrations support
|
|
320
|
+
- [ ] MySQL / MariaDB support
|
|
321
|
+
- [ ] HTML report output
|
|
322
|
+
- [ ] Per-revision exclusions (`@mrt.skip("004", reason="...")`)
|
|
323
|
+
- [ ] PyPI release
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Contributing
|
|
328
|
+
|
|
329
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
Apache 2.0
|