pytest-dblift 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.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-dblift
3
+ Version: 0.1.0
4
+ Summary: pytest plugin for DBLift migrations
5
+ Author: DBLift
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/dblift/dblift
8
+ Classifier: Framework :: Pytest
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: dblift>=3.9
16
+ Requires-Dist: pytest>=7.3
17
+ Requires-Dist: sqlalchemy>=2.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest-xdist>=3.3; extra == "dev"
20
+
21
+ # pytest-dblift
22
+
23
+ pytest plugin for [DBLift](https://github.com/dblift/dblift). It applies your migrations in tests and exposes a `DBLiftClient`.
24
+
25
+ This is a **separate PyPI package**, not `dblift[pytest]`.
26
+
27
+ ## Install
28
+
29
+ SQLite (default, stdlib driver):
30
+
31
+ ```bash
32
+ pip install pytest-dblift
33
+ ```
34
+
35
+ Any other engine: install the matching dblift extra so the native driver is present. The plugin does not install drivers and does not open a second connection — it calls `create_engine(url)` then `DBLiftClient.from_sqlalchemy`.
36
+
37
+ ```bash
38
+ pip install pytest-dblift "dblift[postgresql]"
39
+ pytest --dblift-url "postgresql+psycopg://user:pass@localhost/app_test"
40
+ ```
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ def test_schema(dblift_migrated_db, dblift_client):
46
+ info = dblift_client.info()
47
+ assert info.pending_migrations == []
48
+ ```
49
+
50
+ `dblift_migrated_db` applies pending migrations (function scope). `dblift_client` is a session-scoped `DBLiftClient`. No fixture is autouse: request what you need.
51
+
52
+ ## Fixtures
53
+
54
+ | Fixture | Scope | Role |
55
+ | --- | --- | --- |
56
+ | `dblift_config` | session | Dict with `url`, `migrations_dir`, optional `schema`. Override in `conftest.py`. |
57
+ | `dblift_engine` | session | SQLAlchemy engine from that URL. Override to inject your app engine. |
58
+ | `dblift_client` | session | `DBLiftClient.from_sqlalchemy(...)`. |
59
+ | `dblift_migrated_db` | function | `client.migrate()`, then yield the client. |
60
+ | `dblift_empty_db` | function | `client.clean(clean_enabled=True)`, then yield the client. |
61
+ | `dblift_validate` | function | Callable: `dblift_validate(**kwargs)` runs `client.validate` and asserts success. |
62
+ | `dblift_undo` | function | Callable: `dblift_undo(**kwargs)` runs `client.undo` and asserts success. Does not migrate. |
63
+
64
+ Undo uses companion `U*` scripts (same as dblift). There is no `undo()` function inside a `V*.py` file.
65
+
66
+ ```python
67
+ def test_rollback(dblift_migrated_db, dblift_undo):
68
+ result = dblift_undo(target_version="0")
69
+ assert result.success
70
+ ```
71
+
72
+ Override config in `tests/conftest.py`:
73
+
74
+ ```python
75
+ import pytest
76
+
77
+ @pytest.fixture(scope="session")
78
+ def dblift_config():
79
+ return {
80
+ "url": "postgresql+psycopg://user:pass@localhost/app_test",
81
+ "migrations_dir": "migrations",
82
+ }
83
+ ```
84
+
85
+ ## CLI options
86
+
87
+ | Option | What it does |
88
+ | --- | --- |
89
+ | `--dblift-url` | Database URL when `dblift_config` is not overridden. Default: a temp SQLite **file**. |
90
+ | `--dblift-migrations-dir` | One migrations directory (not a comma-separated list). Default: `migrations`. |
91
+
92
+ pytest-xdist: only the default SQLite file is per-worker (`test_gw0.db`, …). A URL you pass with `--dblift-url` is used as-is.
@@ -0,0 +1,72 @@
1
+ # pytest-dblift
2
+
3
+ pytest plugin for [DBLift](https://github.com/dblift/dblift). It applies your migrations in tests and exposes a `DBLiftClient`.
4
+
5
+ This is a **separate PyPI package**, not `dblift[pytest]`.
6
+
7
+ ## Install
8
+
9
+ SQLite (default, stdlib driver):
10
+
11
+ ```bash
12
+ pip install pytest-dblift
13
+ ```
14
+
15
+ Any other engine: install the matching dblift extra so the native driver is present. The plugin does not install drivers and does not open a second connection — it calls `create_engine(url)` then `DBLiftClient.from_sqlalchemy`.
16
+
17
+ ```bash
18
+ pip install pytest-dblift "dblift[postgresql]"
19
+ pytest --dblift-url "postgresql+psycopg://user:pass@localhost/app_test"
20
+ ```
21
+
22
+ ## Quickstart
23
+
24
+ ```python
25
+ def test_schema(dblift_migrated_db, dblift_client):
26
+ info = dblift_client.info()
27
+ assert info.pending_migrations == []
28
+ ```
29
+
30
+ `dblift_migrated_db` applies pending migrations (function scope). `dblift_client` is a session-scoped `DBLiftClient`. No fixture is autouse: request what you need.
31
+
32
+ ## Fixtures
33
+
34
+ | Fixture | Scope | Role |
35
+ | --- | --- | --- |
36
+ | `dblift_config` | session | Dict with `url`, `migrations_dir`, optional `schema`. Override in `conftest.py`. |
37
+ | `dblift_engine` | session | SQLAlchemy engine from that URL. Override to inject your app engine. |
38
+ | `dblift_client` | session | `DBLiftClient.from_sqlalchemy(...)`. |
39
+ | `dblift_migrated_db` | function | `client.migrate()`, then yield the client. |
40
+ | `dblift_empty_db` | function | `client.clean(clean_enabled=True)`, then yield the client. |
41
+ | `dblift_validate` | function | Callable: `dblift_validate(**kwargs)` runs `client.validate` and asserts success. |
42
+ | `dblift_undo` | function | Callable: `dblift_undo(**kwargs)` runs `client.undo` and asserts success. Does not migrate. |
43
+
44
+ Undo uses companion `U*` scripts (same as dblift). There is no `undo()` function inside a `V*.py` file.
45
+
46
+ ```python
47
+ def test_rollback(dblift_migrated_db, dblift_undo):
48
+ result = dblift_undo(target_version="0")
49
+ assert result.success
50
+ ```
51
+
52
+ Override config in `tests/conftest.py`:
53
+
54
+ ```python
55
+ import pytest
56
+
57
+ @pytest.fixture(scope="session")
58
+ def dblift_config():
59
+ return {
60
+ "url": "postgresql+psycopg://user:pass@localhost/app_test",
61
+ "migrations_dir": "migrations",
62
+ }
63
+ ```
64
+
65
+ ## CLI options
66
+
67
+ | Option | What it does |
68
+ | --- | --- |
69
+ | `--dblift-url` | Database URL when `dblift_config` is not overridden. Default: a temp SQLite **file**. |
70
+ | `--dblift-migrations-dir` | One migrations directory (not a comma-separated list). Default: `migrations`. |
71
+
72
+ pytest-xdist: only the default SQLite file is per-worker (`test_gw0.db`, …). A URL you pass with `--dblift-url` is used as-is.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=83.0.0", "wheel>=0.46.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytest-dblift"
7
+ version = "0.1.0"
8
+ description = "pytest plugin for DBLift migrations"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "DBLift" }]
13
+ dependencies = [
14
+ "dblift>=3.9",
15
+ "pytest>=7.3",
16
+ "sqlalchemy>=2.0",
17
+ ]
18
+ classifiers = [
19
+ "Framework :: Pytest",
20
+ "License :: OSI Approved :: Apache Software License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ ]
25
+ urls = { Homepage = "https://github.com/dblift/dblift" }
26
+
27
+ [project.entry-points.pytest11]
28
+ dblift = "pytest_dblift.plugin"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest-xdist>=3.3"]
32
+
33
+ [tool.setuptools.packages.find]
34
+ include = ["pytest_dblift*"]
@@ -0,0 +1,3 @@
1
+ """pytest-dblift: pytest plugin for DBLift migrations."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,71 @@
1
+ """URL resolution and DBLiftClient construction for pytest-dblift fixtures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import pytest
9
+
10
+ from api import DBLiftClient
11
+
12
+
13
+ def _worker_id(config: pytest.Config) -> str:
14
+ """Return xdist worker id ('gw0', ...) or 'master' when not under xdist."""
15
+ workerinput = getattr(config, "workerinput", None)
16
+ if workerinput:
17
+ return workerinput.get("workerid", "master")
18
+ return "master"
19
+
20
+
21
+ def default_sqlite_file_url(
22
+ tmp_path_factory: pytest.TempPathFactory, config: pytest.Config | None = None
23
+ ) -> str:
24
+ """Session-scoped temp SQLite file URL. Under xdist, suffix the filename with the worker id."""
25
+ base = tmp_path_factory.mktemp("dblift_pytest", numbered=True)
26
+ wid = _worker_id(config) if config is not None else "master"
27
+ if wid != "master":
28
+ db_path = base / f"test_{wid}.db"
29
+ else:
30
+ db_path = base / "test.db"
31
+ return f"sqlite:///{db_path}"
32
+
33
+
34
+ def resolve_dblift_config(
35
+ pytestconfig: pytest.Config,
36
+ *,
37
+ tmp_path_factory: pytest.TempPathFactory,
38
+ ) -> dict[str, Any]:
39
+ """Build config dict from CLI options + defaults.
40
+
41
+ Returns dict with 'url' and 'migrations_dir' (absolute path str).
42
+ A relative migrations dir is resolved against pytest rootdir.
43
+ """
44
+ url = pytestconfig.getoption("--dblift-url")
45
+ if not url:
46
+ url = default_sqlite_file_url(tmp_path_factory, pytestconfig)
47
+
48
+ raw_mig = pytestconfig.getoption("--dblift-migrations-dir") or "migrations"
49
+ rootdir = getattr(pytestconfig, "rootdir", None) or Path.cwd()
50
+ rootdir = Path(rootdir)
51
+ mig_path = Path(raw_mig)
52
+ if not mig_path.is_absolute():
53
+ mig_path = (rootdir / mig_path).resolve()
54
+
55
+ return {
56
+ "url": url,
57
+ "migrations_dir": str(mig_path),
58
+ }
59
+
60
+
61
+ def create_dblift_client(
62
+ engine: Any,
63
+ *,
64
+ migrations_dir: str | Path | list[str | Path] | None,
65
+ schema: str | None = None,
66
+ ) -> DBLiftClient:
67
+ return DBLiftClient.from_sqlalchemy(
68
+ engine,
69
+ migrations_dir=migrations_dir,
70
+ schema=schema,
71
+ )
@@ -0,0 +1,84 @@
1
+ """pytest-dblift fixtures.
2
+
3
+ Session scope for config/engine/client. Function scope for migrate/clean/validate/undo.
4
+ No autouse: tests opt in by requesting a fixture.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Callable, Iterator
10
+
11
+ import pytest
12
+ from sqlalchemy import create_engine
13
+
14
+ from api import DBLiftClient
15
+
16
+ from ._client import create_dblift_client, resolve_dblift_config
17
+
18
+
19
+ @pytest.fixture(scope="session")
20
+ def dblift_config(
21
+ pytestconfig: pytest.Config, tmp_path_factory: pytest.TempPathFactory
22
+ ) -> dict[str, Any]:
23
+ """Session config from CLI or temp SQLite. Overridable in consumer conftest.py."""
24
+ return resolve_dblift_config(pytestconfig, tmp_path_factory=tmp_path_factory)
25
+
26
+
27
+ @pytest.fixture(scope="session")
28
+ def dblift_engine(dblift_config: dict[str, Any]) -> Iterator[Any]:
29
+ engine = create_engine(dblift_config["url"])
30
+ yield engine
31
+ engine.dispose()
32
+
33
+
34
+ @pytest.fixture(scope="session")
35
+ def dblift_client(dblift_engine: Any, dblift_config: dict[str, Any]) -> Iterator[DBLiftClient]:
36
+ client = create_dblift_client(
37
+ dblift_engine,
38
+ migrations_dir=dblift_config.get("migrations_dir"),
39
+ schema=dblift_config.get("schema"),
40
+ )
41
+ yield client
42
+ client.close()
43
+
44
+
45
+ @pytest.fixture
46
+ def dblift_migrated_db(dblift_client: DBLiftClient) -> Iterator[DBLiftClient]:
47
+ result = dblift_client.migrate()
48
+ assert getattr(result, "success", False), (
49
+ f"migrate failed: {getattr(result, 'error_message', result)}"
50
+ )
51
+ yield dblift_client
52
+
53
+
54
+ @pytest.fixture
55
+ def dblift_empty_db(dblift_client: DBLiftClient) -> Iterator[DBLiftClient]:
56
+ result = dblift_client.clean(clean_enabled=True)
57
+ assert getattr(result, "success", False), (
58
+ f"clean failed: {getattr(result, 'error_message', result)}"
59
+ )
60
+ yield dblift_client
61
+
62
+
63
+ @pytest.fixture
64
+ def dblift_validate(dblift_client: DBLiftClient) -> Callable[..., Any]:
65
+ def _run_validate(**kwargs: Any) -> Any:
66
+ result = dblift_client.validate(**kwargs)
67
+ assert getattr(result, "success", False), (
68
+ f"validate failed: {getattr(result, 'error_message', result)}"
69
+ )
70
+ return result
71
+
72
+ return _run_validate
73
+
74
+
75
+ @pytest.fixture
76
+ def dblift_undo(dblift_client: DBLiftClient) -> Callable[..., Any]:
77
+ def _run_undo(**kwargs: Any) -> Any:
78
+ result = dblift_client.undo(**kwargs)
79
+ assert getattr(result, "success", False), (
80
+ f"undo failed: {getattr(result, 'error_message', result)}"
81
+ )
82
+ return result
83
+
84
+ return _run_undo
@@ -0,0 +1,31 @@
1
+ """pytest11 entry: CLI options and fixture loading."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ pytest_plugins = ["pytest_dblift.fixtures"]
8
+
9
+
10
+ def pytest_addoption(parser: pytest.Parser) -> None:
11
+ group = parser.getgroup("dblift", "dblift pytest integration")
12
+ group.addoption(
13
+ "--dblift-url",
14
+ action="store",
15
+ default=None,
16
+ help="Database URL for dblift (e.g. sqlite:////tmp/test.db or postgresql+psycopg://...). "
17
+ "Used when no dblift_config fixture override is provided.",
18
+ )
19
+ group.addoption(
20
+ "--dblift-migrations-dir",
21
+ action="store",
22
+ default="migrations",
23
+ help="Path to the migrations directory. Defaults to migrations.",
24
+ )
25
+
26
+
27
+ def pytest_configure(config: pytest.Config) -> None:
28
+ config.addinivalue_line(
29
+ "markers",
30
+ "dblift: marks tests as using dblift fixtures (provided by pytest-dblift)",
31
+ )
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-dblift
3
+ Version: 0.1.0
4
+ Summary: pytest plugin for DBLift migrations
5
+ Author: DBLift
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/dblift/dblift
8
+ Classifier: Framework :: Pytest
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: dblift>=3.9
16
+ Requires-Dist: pytest>=7.3
17
+ Requires-Dist: sqlalchemy>=2.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest-xdist>=3.3; extra == "dev"
20
+
21
+ # pytest-dblift
22
+
23
+ pytest plugin for [DBLift](https://github.com/dblift/dblift). It applies your migrations in tests and exposes a `DBLiftClient`.
24
+
25
+ This is a **separate PyPI package**, not `dblift[pytest]`.
26
+
27
+ ## Install
28
+
29
+ SQLite (default, stdlib driver):
30
+
31
+ ```bash
32
+ pip install pytest-dblift
33
+ ```
34
+
35
+ Any other engine: install the matching dblift extra so the native driver is present. The plugin does not install drivers and does not open a second connection — it calls `create_engine(url)` then `DBLiftClient.from_sqlalchemy`.
36
+
37
+ ```bash
38
+ pip install pytest-dblift "dblift[postgresql]"
39
+ pytest --dblift-url "postgresql+psycopg://user:pass@localhost/app_test"
40
+ ```
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ def test_schema(dblift_migrated_db, dblift_client):
46
+ info = dblift_client.info()
47
+ assert info.pending_migrations == []
48
+ ```
49
+
50
+ `dblift_migrated_db` applies pending migrations (function scope). `dblift_client` is a session-scoped `DBLiftClient`. No fixture is autouse: request what you need.
51
+
52
+ ## Fixtures
53
+
54
+ | Fixture | Scope | Role |
55
+ | --- | --- | --- |
56
+ | `dblift_config` | session | Dict with `url`, `migrations_dir`, optional `schema`. Override in `conftest.py`. |
57
+ | `dblift_engine` | session | SQLAlchemy engine from that URL. Override to inject your app engine. |
58
+ | `dblift_client` | session | `DBLiftClient.from_sqlalchemy(...)`. |
59
+ | `dblift_migrated_db` | function | `client.migrate()`, then yield the client. |
60
+ | `dblift_empty_db` | function | `client.clean(clean_enabled=True)`, then yield the client. |
61
+ | `dblift_validate` | function | Callable: `dblift_validate(**kwargs)` runs `client.validate` and asserts success. |
62
+ | `dblift_undo` | function | Callable: `dblift_undo(**kwargs)` runs `client.undo` and asserts success. Does not migrate. |
63
+
64
+ Undo uses companion `U*` scripts (same as dblift). There is no `undo()` function inside a `V*.py` file.
65
+
66
+ ```python
67
+ def test_rollback(dblift_migrated_db, dblift_undo):
68
+ result = dblift_undo(target_version="0")
69
+ assert result.success
70
+ ```
71
+
72
+ Override config in `tests/conftest.py`:
73
+
74
+ ```python
75
+ import pytest
76
+
77
+ @pytest.fixture(scope="session")
78
+ def dblift_config():
79
+ return {
80
+ "url": "postgresql+psycopg://user:pass@localhost/app_test",
81
+ "migrations_dir": "migrations",
82
+ }
83
+ ```
84
+
85
+ ## CLI options
86
+
87
+ | Option | What it does |
88
+ | --- | --- |
89
+ | `--dblift-url` | Database URL when `dblift_config` is not overridden. Default: a temp SQLite **file**. |
90
+ | `--dblift-migrations-dir` | One migrations directory (not a comma-separated list). Default: `migrations`. |
91
+
92
+ pytest-xdist: only the default SQLite file is per-worker (`test_gw0.db`, …). A URL you pass with `--dblift-url` is used as-is.
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ pytest_dblift/__init__.py
4
+ pytest_dblift/_client.py
5
+ pytest_dblift/fixtures.py
6
+ pytest_dblift/plugin.py
7
+ pytest_dblift.egg-info/PKG-INFO
8
+ pytest_dblift.egg-info/SOURCES.txt
9
+ pytest_dblift.egg-info/dependency_links.txt
10
+ pytest_dblift.egg-info/entry_points.txt
11
+ pytest_dblift.egg-info/requires.txt
12
+ pytest_dblift.egg-info/top_level.txt
13
+ tests/test_fixtures_sqlite.py
14
+ tests/test_plugin_options.py
15
+ tests/test_undo.py
16
+ tests/test_xdist_isolation.py
@@ -0,0 +1,2 @@
1
+ [pytest11]
2
+ dblift = pytest_dblift.plugin
@@ -0,0 +1,6 @@
1
+ dblift>=3.9
2
+ pytest>=7.3
3
+ sqlalchemy>=2.0
4
+
5
+ [dev]
6
+ pytest-xdist>=3.3
@@ -0,0 +1 @@
1
+ pytest_dblift
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,96 @@
1
+ """SQLite tests for pytest-dblift helpers and fixtures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import pytest
9
+ from sqlalchemy import text
10
+
11
+ from api import DBLiftClient
12
+
13
+
14
+ def test_resolve_dblift_config_reads_cli_url(
15
+ tmp_path_factory: pytest.TempPathFactory,
16
+ ) -> None:
17
+ from pytest_dblift._client import resolve_dblift_config
18
+
19
+ class DummyConfig:
20
+ rootdir = "/tmp"
21
+
22
+ def getoption(self, name: str, default: Any = None) -> Any:
23
+ if name == "--dblift-url":
24
+ return "sqlite:////tmp/dblift_custom_test.db"
25
+ if name == "--dblift-migrations-dir":
26
+ return "migrations"
27
+ return default
28
+
29
+ cfg = resolve_dblift_config(DummyConfig(), tmp_path_factory=tmp_path_factory)
30
+ assert "dblift_custom_test.db" in cfg["url"]
31
+ assert "migrations" in cfg["migrations_dir"]
32
+
33
+
34
+ def test_worker_id_master_without_xdist(pytestconfig: pytest.Config) -> None:
35
+ from pytest_dblift._client import _worker_id
36
+
37
+ if getattr(pytestconfig, "workerinput", None):
38
+ pytest.skip("this assertion is for a non-xdist controller process")
39
+ assert _worker_id(pytestconfig) == "master"
40
+
41
+
42
+ def test_dblift_config_defaults_to_sqlite_file(dblift_config: dict[str, Any]) -> None:
43
+ assert "sqlite" in dblift_config["url"]
44
+ assert ":memory:" not in dblift_config["url"]
45
+ assert (Path(dblift_config["migrations_dir"]) / "V1__init.sql").is_file()
46
+
47
+
48
+ def test_dblift_engine_connects(dblift_engine: Any, dblift_config: dict[str, Any]) -> None:
49
+ from sqlalchemy.engine import Engine
50
+
51
+ assert isinstance(dblift_engine, Engine)
52
+ with dblift_engine.connect() as conn:
53
+ conn.execute(text("SELECT 1"))
54
+ rendered = dblift_engine.url.render_as_string(hide_password=False)
55
+ assert rendered.startswith("sqlite")
56
+
57
+
58
+ def test_dblift_client_is_public_client(dblift_client: DBLiftClient) -> None:
59
+ assert isinstance(dblift_client, DBLiftClient)
60
+ info = dblift_client.info()
61
+ assert hasattr(info, "pending_migrations")
62
+
63
+
64
+ def test_migrated_db_applies_migrations(
65
+ dblift_migrated_db: DBLiftClient, dblift_engine: Any
66
+ ) -> None:
67
+ assert dblift_migrated_db.info().pending_migrations == []
68
+ with dblift_engine.connect() as conn:
69
+ count = conn.execute(text("SELECT COUNT(*) FROM pytest_dblift_smoke")).scalar()
70
+ assert count == 0
71
+
72
+
73
+ def test_empty_db_cleans_schema(
74
+ dblift_migrated_db: DBLiftClient, dblift_empty_db: DBLiftClient, dblift_engine: Any
75
+ ) -> None:
76
+ with dblift_engine.connect() as conn:
77
+ try:
78
+ conn.execute(text("SELECT COUNT(*) FROM pytest_dblift_smoke"))
79
+ exists = True
80
+ except Exception:
81
+ exists = False
82
+ assert not exists
83
+
84
+
85
+ def test_validate_callable_succeeds(
86
+ dblift_migrated_db: DBLiftClient, dblift_validate: Any
87
+ ) -> None:
88
+ dblift_migrated_db.migrate()
89
+ result = dblift_validate()
90
+ assert result.success is True
91
+ result2 = dblift_validate(target_version=None)
92
+ assert result2.success is True
93
+
94
+
95
+ def test_dblift_validate_is_callable(dblift_validate: Any) -> None:
96
+ assert callable(dblift_validate)
@@ -0,0 +1,20 @@
1
+ """CLI options registered by the pytest-dblift plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+
8
+
9
+ def test_help_lists_dblift_url_and_migrations_dir() -> None:
10
+ result = subprocess.run(
11
+ [sys.executable, "-m", "pytest", "--help"],
12
+ check=False,
13
+ capture_output=True,
14
+ text=True,
15
+ )
16
+ assert result.returncode == 0, result.stderr
17
+ help_text = result.stdout
18
+ assert "--dblift-url" in help_text
19
+ assert "--dblift-migrations-dir" in help_text
20
+ assert "--dblift-no-migrate" not in help_text
@@ -0,0 +1,36 @@
1
+ """dblift_undo uses companion U* scripts, not an undo() function inside V*.py."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from sqlalchemy import text
8
+
9
+ from api import DBLiftClient
10
+
11
+
12
+ def test_dblift_undo_reverts_via_companion_script(
13
+ dblift_migrated_db: DBLiftClient,
14
+ dblift_undo: Any,
15
+ dblift_engine: Any,
16
+ ) -> None:
17
+ with dblift_engine.connect() as conn:
18
+ conn.execute(text("INSERT INTO pytest_dblift_smoke (name) VALUES ('before-undo')"))
19
+ conn.commit()
20
+ count = conn.execute(text("SELECT COUNT(*) FROM pytest_dblift_smoke")).scalar()
21
+ assert count == 1
22
+
23
+ result = dblift_undo(target_version="0")
24
+ assert result.success is True
25
+
26
+ with dblift_engine.connect() as conn:
27
+ try:
28
+ conn.execute(text("SELECT COUNT(*) FROM pytest_dblift_smoke"))
29
+ exists = True
30
+ except Exception:
31
+ exists = False
32
+ assert not exists
33
+
34
+
35
+ def test_dblift_undo_is_callable(dblift_undo: Any) -> None:
36
+ assert callable(dblift_undo)
@@ -0,0 +1,35 @@
1
+ """Default SQLite URLs are per xdist worker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import pytest
8
+ from sqlalchemy import text
9
+
10
+ from pytest_dblift._client import _worker_id
11
+
12
+
13
+ def test_xdist_worker_isolation(
14
+ pytestconfig: pytest.Config,
15
+ dblift_config: dict[str, Any],
16
+ dblift_migrated_db: Any,
17
+ dblift_engine: Any,
18
+ ) -> None:
19
+ wid = _worker_id(pytestconfig)
20
+ url = dblift_config["url"]
21
+
22
+ if wid != "master":
23
+ assert wid in url, (
24
+ f"worker isolation missing: worker {wid!r} not in default url {url!r}"
25
+ )
26
+
27
+ assert "sqlite" in url and ":memory:" not in url
28
+ tag = f"iso-{wid}"
29
+ with dblift_engine.connect() as conn:
30
+ conn.execute(text("INSERT INTO pytest_dblift_smoke (name) VALUES (:n)"), {"n": tag})
31
+ conn.commit()
32
+ cnt = conn.execute(
33
+ text("SELECT COUNT(*) FROM pytest_dblift_smoke WHERE name = :n"), {"n": tag}
34
+ ).scalar()
35
+ assert cnt == 1