pytest-neon 0.4.0__tar.gz → 0.5.1__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.
Files changed (23) hide show
  1. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/.github/workflows/release.yml +7 -0
  2. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/CLAUDE.md +9 -1
  3. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/PKG-INFO +88 -4
  4. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/README.md +84 -0
  5. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/pyproject.toml +4 -4
  6. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/src/pytest_neon/__init__.py +1 -1
  7. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/src/pytest_neon/plugin.py +105 -8
  8. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_integration.py +75 -0
  9. pytest_neon-0.5.1/tests/test_migrations.py +77 -0
  10. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/.env.example +0 -0
  11. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/.github/workflows/tests.yml +0 -0
  12. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/.gitignore +0 -0
  13. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/.neon +0 -0
  14. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/LICENSE +0 -0
  15. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/src/pytest_neon/py.typed +0 -0
  16. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/conftest.py +0 -0
  17. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_branch_lifecycle.py +0 -0
  18. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_cli_options.py +0 -0
  19. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_env_var.py +0 -0
  20. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_fixture_errors.py +0 -0
  21. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_reset_behavior.py +0 -0
  22. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/tests/test_skip_behavior.py +0 -0
  23. {pytest_neon-0.4.0 → pytest_neon-0.5.1}/uv.lock +0 -0
@@ -80,6 +80,13 @@ jobs:
80
80
  git tag "v$VERSION"
81
81
  git push origin main --tags
82
82
 
83
+ - name: Create GitHub Release
84
+ env:
85
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86
+ VERSION: ${{ steps.bump.outputs.version }}
87
+ run: |
88
+ gh release create "v$VERSION" --generate-notes
89
+
83
90
  - name: Build and publish
84
91
  env:
85
92
  UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
@@ -1,5 +1,9 @@
1
1
  # Claude Code Instructions for pytest-neon
2
2
 
3
+ ## Understanding the Plugin
4
+
5
+ Read `README.md` for complete documentation on how to use this plugin, including fixtures, configuration options, and migration support.
6
+
3
7
  ## Project Overview
4
8
 
5
9
  This is a pytest plugin that provides isolated Neon database branches for integration testing. Each test gets isolated database state via branch reset after each test.
@@ -7,6 +11,8 @@ This is a pytest plugin that provides isolated Neon database branches for integr
7
11
  ## Key Architecture
8
12
 
9
13
  - **Entry point**: `src/pytest_neon/plugin.py` - Contains all fixtures and pytest hooks
14
+ - **Migration fixture**: `_neon_migration_branch` - Session-scoped, parent for all test branches
15
+ - **User migration hook**: `neon_apply_migrations` - Session-scoped no-op, users override to run migrations
10
16
  - **Core fixture**: `neon_branch` - Creates branch (module-scoped), resets after each test (function-scoped wrapper), sets `DATABASE_URL`, yields `NeonBranch` dataclass
11
17
  - **Shared fixture**: `neon_branch_shared` - Module-scoped, no reset between tests
12
18
  - **Convenience fixtures**: `neon_connection`, `neon_connection_psycopg`, `neon_engine` - Optional, require extras
@@ -19,7 +25,9 @@ This is a pytest plugin that provides isolated Neon database branches for integr
19
25
  ## Important Patterns
20
26
 
21
27
  ### Fixture Scopes
22
- - `_neon_branch_for_reset`: `scope="module"` - internal, creates one branch per test file
28
+ - `_neon_migration_branch`: `scope="session"` - internal, parent for all test branches, migrations run here
29
+ - `neon_apply_migrations`: `scope="session"` - user overrides to run migrations
30
+ - `_neon_branch_for_reset`: `scope="module"` - internal, creates one branch per test file from migration branch
23
31
  - `neon_branch`: `scope="function"` - wraps the above, resets branch after each test
24
32
  - `neon_branch_shared`: `scope="module"` - one branch per test file, no reset
25
33
  - Connection fixtures: `scope="function"` (default) - fresh connection per test
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-neon
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Pytest plugin for Neon database branch isolation in tests
5
- Project-URL: Homepage, https://github.com/zain/pytest-neon
6
- Project-URL: Repository, https://github.com/zain/pytest-neon
7
- Project-URL: Issues, https://github.com/zain/pytest-neon/issues
5
+ Project-URL: Homepage, https://github.com/ZainRizvi/pytest-neon
6
+ Project-URL: Repository, https://github.com/ZainRizvi/pytest-neon
7
+ Project-URL: Issues, https://github.com/ZainRizvi/pytest-neon/issues
8
8
  Author: Zain Rizvi
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
@@ -200,6 +200,90 @@ def test_query(neon_engine):
200
200
  assert result.scalar() == 1
201
201
  ```
202
202
 
203
+ ## Migrations
204
+
205
+ pytest-neon supports running migrations once before tests, with all test resets preserving the migrated state.
206
+
207
+ ### How It Works
208
+
209
+ When you override the `neon_apply_migrations` fixture, the plugin uses a two-branch architecture:
210
+
211
+ ```
212
+ Parent Branch (your configured parent)
213
+ └── Migration Branch (session-scoped)
214
+ │ ↑ migrations run here ONCE
215
+ └── Test Branch (module-scoped)
216
+ ↑ resets to migration branch after each test
217
+ ```
218
+
219
+ This means:
220
+ - Migrations run **once per test session** (not per test or per module)
221
+ - Each test reset restores to the **post-migration state**
222
+ - Tests always see your migrated schema
223
+
224
+ ### Setup
225
+
226
+ Override the `neon_apply_migrations` fixture in your `conftest.py`:
227
+
228
+ **Alembic:**
229
+ ```python
230
+ # conftest.py
231
+ import subprocess
232
+ import pytest
233
+
234
+ @pytest.fixture(scope="session")
235
+ def neon_apply_migrations(_neon_migration_branch):
236
+ """Run Alembic migrations before tests."""
237
+ # DATABASE_URL is already set to the migration branch
238
+ subprocess.run(["alembic", "upgrade", "head"], check=True)
239
+ ```
240
+
241
+ **Django:**
242
+ ```python
243
+ # conftest.py
244
+ import pytest
245
+
246
+ @pytest.fixture(scope="session")
247
+ def neon_apply_migrations(_neon_migration_branch):
248
+ """Run Django migrations before tests."""
249
+ from django.core.management import call_command
250
+ call_command("migrate", "--noinput")
251
+ ```
252
+
253
+ **Raw SQL:**
254
+ ```python
255
+ # conftest.py
256
+ import pytest
257
+
258
+ @pytest.fixture(scope="session")
259
+ def neon_apply_migrations(_neon_migration_branch):
260
+ """Apply schema from SQL file."""
261
+ import psycopg
262
+ with psycopg.connect(_neon_migration_branch.connection_string) as conn:
263
+ with open("schema.sql") as f:
264
+ conn.execute(f.read())
265
+ conn.commit()
266
+ ```
267
+
268
+ **Custom migration tool:**
269
+ ```python
270
+ # conftest.py
271
+ import pytest
272
+
273
+ @pytest.fixture(scope="session")
274
+ def neon_apply_migrations(_neon_migration_branch):
275
+ """Run custom migrations."""
276
+ from myapp.migrations import run_migrations
277
+ run_migrations(_neon_migration_branch.connection_string)
278
+ ```
279
+
280
+ ### Important Notes
281
+
282
+ - The `_neon_migration_branch` parameter gives you access to the `NeonBranch` object with `connection_string`, `branch_id`, etc.
283
+ - `DATABASE_URL` (or your configured env var) is automatically set when the fixture runs
284
+ - If you don't override `neon_apply_migrations`, no migrations run (the fixture is a no-op by default)
285
+ - Migrations run before any test branches are created, so all tests see the same migrated schema
286
+
203
287
  ## Configuration
204
288
 
205
289
  ### Environment Variables
@@ -158,6 +158,90 @@ def test_query(neon_engine):
158
158
  assert result.scalar() == 1
159
159
  ```
160
160
 
161
+ ## Migrations
162
+
163
+ pytest-neon supports running migrations once before tests, with all test resets preserving the migrated state.
164
+
165
+ ### How It Works
166
+
167
+ When you override the `neon_apply_migrations` fixture, the plugin uses a two-branch architecture:
168
+
169
+ ```
170
+ Parent Branch (your configured parent)
171
+ └── Migration Branch (session-scoped)
172
+ │ ↑ migrations run here ONCE
173
+ └── Test Branch (module-scoped)
174
+ ↑ resets to migration branch after each test
175
+ ```
176
+
177
+ This means:
178
+ - Migrations run **once per test session** (not per test or per module)
179
+ - Each test reset restores to the **post-migration state**
180
+ - Tests always see your migrated schema
181
+
182
+ ### Setup
183
+
184
+ Override the `neon_apply_migrations` fixture in your `conftest.py`:
185
+
186
+ **Alembic:**
187
+ ```python
188
+ # conftest.py
189
+ import subprocess
190
+ import pytest
191
+
192
+ @pytest.fixture(scope="session")
193
+ def neon_apply_migrations(_neon_migration_branch):
194
+ """Run Alembic migrations before tests."""
195
+ # DATABASE_URL is already set to the migration branch
196
+ subprocess.run(["alembic", "upgrade", "head"], check=True)
197
+ ```
198
+
199
+ **Django:**
200
+ ```python
201
+ # conftest.py
202
+ import pytest
203
+
204
+ @pytest.fixture(scope="session")
205
+ def neon_apply_migrations(_neon_migration_branch):
206
+ """Run Django migrations before tests."""
207
+ from django.core.management import call_command
208
+ call_command("migrate", "--noinput")
209
+ ```
210
+
211
+ **Raw SQL:**
212
+ ```python
213
+ # conftest.py
214
+ import pytest
215
+
216
+ @pytest.fixture(scope="session")
217
+ def neon_apply_migrations(_neon_migration_branch):
218
+ """Apply schema from SQL file."""
219
+ import psycopg
220
+ with psycopg.connect(_neon_migration_branch.connection_string) as conn:
221
+ with open("schema.sql") as f:
222
+ conn.execute(f.read())
223
+ conn.commit()
224
+ ```
225
+
226
+ **Custom migration tool:**
227
+ ```python
228
+ # conftest.py
229
+ import pytest
230
+
231
+ @pytest.fixture(scope="session")
232
+ def neon_apply_migrations(_neon_migration_branch):
233
+ """Run custom migrations."""
234
+ from myapp.migrations import run_migrations
235
+ run_migrations(_neon_migration_branch.connection_string)
236
+ ```
237
+
238
+ ### Important Notes
239
+
240
+ - The `_neon_migration_branch` parameter gives you access to the `NeonBranch` object with `connection_string`, `branch_id`, etc.
241
+ - `DATABASE_URL` (or your configured env var) is automatically set when the fixture runs
242
+ - If you don't override `neon_apply_migrations`, no migrations run (the fixture is a no-op by default)
243
+ - Migrations run before any test branches are created, so all tests see the same migrated schema
244
+
161
245
  ## Configuration
162
246
 
163
247
  ### Environment Variables
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-neon"
7
- version = "0.4.0"
7
+ version = "0.5.1"
8
8
  description = "Pytest plugin for Neon database branch isolation in tests"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -45,9 +45,9 @@ dev = [
45
45
  ]
46
46
 
47
47
  [project.urls]
48
- Homepage = "https://github.com/zain/pytest-neon"
49
- Repository = "https://github.com/zain/pytest-neon"
50
- Issues = "https://github.com/zain/pytest-neon/issues"
48
+ Homepage = "https://github.com/ZainRizvi/pytest-neon"
49
+ Repository = "https://github.com/ZainRizvi/pytest-neon"
50
+ Issues = "https://github.com/ZainRizvi/pytest-neon/issues"
51
51
 
52
52
  [project.entry-points.pytest11]
53
53
  neon = "pytest_neon.plugin"
@@ -9,7 +9,7 @@ from pytest_neon.plugin import (
9
9
  neon_engine,
10
10
  )
11
11
 
12
- __version__ = "0.4.0"
12
+ __version__ = "0.5.1"
13
13
  __all__ = [
14
14
  "NeonBranch",
15
15
  "neon_branch",
@@ -137,11 +137,20 @@ def _get_config_value(
137
137
 
138
138
  def _create_neon_branch(
139
139
  request: pytest.FixtureRequest,
140
+ parent_branch_id_override: str | None = None,
141
+ branch_expiry_override: int | None = None,
142
+ branch_name_suffix: str = "",
140
143
  ) -> Generator[NeonBranch, None, None]:
141
144
  """
142
145
  Internal helper that creates and manages a Neon branch lifecycle.
143
146
 
144
147
  This is the core implementation used by branch fixtures.
148
+
149
+ Args:
150
+ request: Pytest fixture request
151
+ parent_branch_id_override: If provided, use this as parent instead of config
152
+ branch_expiry_override: If provided, use this expiry instead of config
153
+ branch_name_suffix: Optional suffix for branch name (e.g., "-migrated", "-test")
145
154
  """
146
155
  config = request.config
147
156
 
@@ -149,7 +158,8 @@ def _create_neon_branch(
149
158
  project_id = _get_config_value(
150
159
  config, "neon_project_id", "NEON_PROJECT_ID", "neon_project_id"
151
160
  )
152
- parent_branch_id = _get_config_value(
161
+ # Use override if provided, otherwise read from config
162
+ parent_branch_id = parent_branch_id_override or _get_config_value(
153
163
  config, "neon_parent_branch", "NEON_PARENT_BRANCH_ID", "neon_parent_branch"
154
164
  )
155
165
  database_name = _get_config_value(
@@ -164,9 +174,13 @@ def _create_neon_branch(
164
174
  if keep_branches is None:
165
175
  keep_branches = config.getini("neon_keep_branches")
166
176
 
167
- branch_expiry = config.getoption("neon_branch_expiry", default=None)
168
- if branch_expiry is None:
169
- branch_expiry = int(config.getini("neon_branch_expiry"))
177
+ # Use override if provided, otherwise read from config
178
+ if branch_expiry_override is not None:
179
+ branch_expiry = branch_expiry_override
180
+ else:
181
+ branch_expiry = config.getoption("neon_branch_expiry", default=None)
182
+ if branch_expiry is None:
183
+ branch_expiry = int(config.getini("neon_branch_expiry"))
170
184
 
171
185
  env_var_name = _get_config_value(
172
186
  config, "neon_env_var", "", "neon_env_var", "DATABASE_URL"
@@ -185,7 +199,7 @@ def _create_neon_branch(
185
199
  neon = NeonAPI(api_key=api_key)
186
200
 
187
201
  # Generate unique branch name
188
- branch_name = f"pytest-{os.urandom(4).hex()}"
202
+ branch_name = f"pytest-{os.urandom(4).hex()}{branch_name_suffix}"
189
203
 
190
204
  # Build branch creation payload
191
205
  branch_config: dict[str, Any] = {"name": branch_name}
@@ -311,12 +325,86 @@ def _reset_branch_to_parent(branch: NeonBranch, api_key: str) -> None:
311
325
  response.raise_for_status()
312
326
 
313
327
 
328
+ @pytest.fixture(scope="session")
329
+ def _neon_migration_branch(
330
+ request: pytest.FixtureRequest,
331
+ ) -> Generator[NeonBranch, None, None]:
332
+ """
333
+ Session-scoped branch where migrations are applied.
334
+
335
+ This branch is created from the configured parent and serves as
336
+ the parent for all test branches. Migrations run once per session
337
+ on this branch.
338
+
339
+ Note: The migration branch cannot have an expiry because Neon doesn't
340
+ allow creating child branches from branches with expiration dates.
341
+ Cleanup relies on the fixture teardown at session end.
342
+ """
343
+ # No expiry - Neon doesn't allow children from branches with expiry
344
+ yield from _create_neon_branch(
345
+ request,
346
+ branch_expiry_override=0,
347
+ branch_name_suffix="-migrated",
348
+ )
349
+
350
+
351
+ @pytest.fixture(scope="session")
352
+ def neon_apply_migrations(_neon_migration_branch: NeonBranch) -> None:
353
+ """
354
+ Override this fixture to run migrations on the test database.
355
+
356
+ The migration branch is already created and DATABASE_URL is set.
357
+ Migrations run once per test session, before any tests execute.
358
+
359
+ Example in conftest.py:
360
+
361
+ @pytest.fixture(scope="session")
362
+ def neon_apply_migrations(_neon_migration_branch):
363
+ import subprocess
364
+ subprocess.run(["alembic", "upgrade", "head"], check=True)
365
+
366
+ Or with Django:
367
+
368
+ @pytest.fixture(scope="session")
369
+ def neon_apply_migrations(_neon_migration_branch):
370
+ from django.core.management import call_command
371
+ call_command("migrate", "--noinput")
372
+
373
+ Or with raw SQL:
374
+
375
+ @pytest.fixture(scope="session")
376
+ def neon_apply_migrations(_neon_migration_branch):
377
+ import psycopg
378
+ with psycopg.connect(_neon_migration_branch.connection_string) as conn:
379
+ with open("schema.sql") as f:
380
+ conn.execute(f.read())
381
+ conn.commit()
382
+
383
+ Args:
384
+ _neon_migration_branch: The migration branch with connection details.
385
+ Use _neon_migration_branch.connection_string to connect directly,
386
+ or rely on DATABASE_URL which is already set.
387
+ """
388
+ pass # No-op by default - users override this fixture to run migrations
389
+
390
+
314
391
  @pytest.fixture(scope="module")
315
392
  def _neon_branch_for_reset(
316
393
  request: pytest.FixtureRequest,
394
+ _neon_migration_branch: NeonBranch,
395
+ neon_apply_migrations: None, # Ensures migrations run first
317
396
  ) -> Generator[NeonBranch, None, None]:
318
- """Internal fixture that creates a branch for reset-based isolation."""
319
- yield from _create_neon_branch(request)
397
+ """
398
+ Internal fixture that creates a test branch from the migration branch.
399
+
400
+ The test branch is created as a child of the migration branch, so resets
401
+ restore to post-migration state rather than the original parent state.
402
+ """
403
+ yield from _create_neon_branch(
404
+ request,
405
+ parent_branch_id_override=_neon_migration_branch.branch_id,
406
+ branch_name_suffix="-test",
407
+ )
320
408
 
321
409
 
322
410
  @pytest.fixture(scope="function")
@@ -383,6 +471,8 @@ def neon_branch(
383
471
  @pytest.fixture(scope="module")
384
472
  def neon_branch_shared(
385
473
  request: pytest.FixtureRequest,
474
+ _neon_migration_branch: NeonBranch,
475
+ neon_apply_migrations: None, # Ensures migrations run first
386
476
  ) -> Generator[NeonBranch, None, None]:
387
477
  """
388
478
  Provide a shared Neon database branch for all tests in a module.
@@ -391,6 +481,9 @@ def neon_branch_shared(
391
481
  tests without resetting. This is the fastest option but tests can see
392
482
  each other's data modifications.
393
483
 
484
+ If you override the `neon_apply_migrations` fixture, migrations will run
485
+ once before the first test, and this branch will include the migrated schema.
486
+
394
487
  Use this when:
395
488
  - Tests are read-only or don't interfere with each other
396
489
  - You manually clean up test data within each test
@@ -408,7 +501,11 @@ def neon_branch_shared(
408
501
  # Fast: no reset between tests, but be careful about data leakage
409
502
  conn_string = neon_branch_shared.connection_string
410
503
  """
411
- yield from _create_neon_branch(request)
504
+ yield from _create_neon_branch(
505
+ request,
506
+ parent_branch_id_override=_neon_migration_branch.branch_id,
507
+ branch_name_suffix="-shared",
508
+ )
412
509
 
413
510
 
414
511
  @pytest.fixture
@@ -81,6 +81,81 @@ class TestRealBranchCreation:
81
81
  assert os.environ.get("DATABASE_URL") == neon_branch.connection_string
82
82
 
83
83
 
84
+ class TestMigrations:
85
+ """Test migration support with real Neon branches."""
86
+
87
+ def test_migrations_persist_across_resets(self, pytester, tmp_path):
88
+ """Test that migrations run once and persist across test resets."""
89
+ # Write a conftest that tracks migration and verifies table exists
90
+ conftest = """
91
+ import os
92
+ import pytest
93
+
94
+ # Track if migrations ran
95
+ migrations_ran = [False]
96
+
97
+ @pytest.fixture(scope="session")
98
+ def neon_apply_migrations(_neon_migration_branch):
99
+ \"\"\"Create a test table via migration.\"\"\"
100
+ try:
101
+ import psycopg
102
+ except ImportError:
103
+ pytest.skip("psycopg not installed")
104
+
105
+ with psycopg.connect(_neon_migration_branch.connection_string) as conn:
106
+ with conn.cursor() as cur:
107
+ cur.execute(\"\"\"
108
+ CREATE TABLE IF NOT EXISTS migration_test (
109
+ id SERIAL PRIMARY KEY,
110
+ value TEXT NOT NULL
111
+ )
112
+ \"\"\")
113
+ conn.commit()
114
+ migrations_ran[0] = True
115
+
116
+ def pytest_sessionfinish(session, exitstatus):
117
+ assert migrations_ran[0], "Migrations should have run"
118
+ """
119
+ pytester.makeconftest(conftest)
120
+
121
+ # Write tests that verify the migrated table exists and data resets
122
+ pytester.makepyfile(
123
+ """
124
+ import psycopg
125
+
126
+ def test_first_insert(neon_branch):
127
+ \"\"\"Insert data - table should exist from migration.\"\"\"
128
+ with psycopg.connect(neon_branch.connection_string) as conn:
129
+ with conn.cursor() as cur:
130
+ cur.execute("INSERT INTO migration_test (value) VALUES ('first')")
131
+ conn.commit()
132
+
133
+ with conn.cursor() as cur:
134
+ cur.execute("SELECT COUNT(*) FROM migration_test")
135
+ count = cur.fetchone()[0]
136
+ assert count == 1
137
+
138
+ def test_second_insert_after_reset(neon_branch):
139
+ \"\"\"After reset, table exists but previous data is gone.\"\"\"
140
+ with psycopg.connect(neon_branch.connection_string) as conn:
141
+ # Table should still exist (from migration)
142
+ with conn.cursor() as cur:
143
+ cur.execute("SELECT COUNT(*) FROM migration_test")
144
+ count = cur.fetchone()[0]
145
+ # Data from first test should be gone after reset
146
+ assert count == 0
147
+
148
+ # Insert new data
149
+ with conn.cursor() as cur:
150
+ cur.execute("INSERT INTO migration_test (value) VALUES ('second')")
151
+ conn.commit()
152
+ """
153
+ )
154
+
155
+ result = pytester.runpytest("-v")
156
+ result.assert_outcomes(passed=2)
157
+
158
+
84
159
  class TestRealDatabaseConnectivity:
85
160
  """Test actual database connectivity."""
86
161
 
@@ -0,0 +1,77 @@
1
+ """Tests for migration support."""
2
+
3
+
4
+ class TestMigrationFixtureOrder:
5
+ """Test that migrations run before test branches are created."""
6
+
7
+ def test_migrations_run_before_test_branch_created(self, pytester):
8
+ """Verify neon_apply_migrations is called before test branch exists."""
9
+ pytester.makeconftest(
10
+ """
11
+ import os
12
+ import pytest
13
+ from dataclasses import dataclass
14
+
15
+ execution_order = []
16
+
17
+ @dataclass
18
+ class FakeNeonBranch:
19
+ branch_id: str
20
+ project_id: str
21
+ connection_string: str
22
+ host: str
23
+ parent_id: str
24
+
25
+ @pytest.fixture(scope="session")
26
+ def _neon_migration_branch():
27
+ execution_order.append("migration_branch_created")
28
+ branch = FakeNeonBranch(
29
+ branch_id="br-migration",
30
+ project_id="proj-test",
31
+ connection_string="postgresql://migration",
32
+ host="test.neon.tech",
33
+ parent_id="br-parent",
34
+ )
35
+ os.environ["DATABASE_URL"] = branch.connection_string
36
+ yield branch
37
+
38
+ @pytest.fixture(scope="session")
39
+ def neon_apply_migrations(_neon_migration_branch):
40
+ execution_order.append("migrations_applied")
41
+ # User would run migrations here
42
+
43
+ @pytest.fixture(scope="module")
44
+ def _neon_branch_for_reset(_neon_migration_branch, neon_apply_migrations):
45
+ execution_order.append("test_branch_created")
46
+ branch = FakeNeonBranch(
47
+ branch_id="br-test",
48
+ project_id="proj-test",
49
+ connection_string="postgresql://test",
50
+ host="test.neon.tech",
51
+ parent_id=_neon_migration_branch.branch_id,
52
+ )
53
+ yield branch
54
+
55
+ @pytest.fixture(scope="function")
56
+ def neon_branch(_neon_branch_for_reset):
57
+ yield _neon_branch_for_reset
58
+
59
+ def pytest_sessionfinish(session, exitstatus):
60
+ # Verify order: migration branch -> migrations -> test branch
61
+ assert execution_order == [
62
+ "migration_branch_created",
63
+ "migrations_applied",
64
+ "test_branch_created",
65
+ ], f"Wrong order: {execution_order}"
66
+ """
67
+ )
68
+
69
+ pytester.makepyfile(
70
+ """
71
+ def test_uses_branch(neon_branch):
72
+ assert neon_branch.parent_id == "br-migration"
73
+ """
74
+ )
75
+
76
+ result = pytester.runpytest("-v")
77
+ result.assert_outcomes(passed=1)
File without changes
File without changes
File without changes
File without changes
File without changes