Files
meshcore-hub/tests/test_main.py
T
Louis King 34b6e6b328 test: cover Postgres migration helper and CLI command
Raise patch coverage on the v0.14.0 Postgres backend:

- test_db_migrate: _is_superuser plus the full migrate_sqlite_to_postgres
  flow (dry-run, copy, non-empty-target refusal, --truncate, missing schema),
  routing create_database_engine to SQLite files so a postgresql:// target
  satisfies the guard while the run executes SQLite -> SQLite in CI.
- test_main: the `db migrate-to-postgres` CLI command via CliRunner
  (success, dry-run, row-count mismatch, ValueError -> ClickException).
- conftest: neutralise dotenv.load_dotenv before collection. Importing the
  CLI entrypoint runs load_dotenv() at import time, which leaked a local .env
  into os.environ and broke unrelated config/redis tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:11:02 +01:00

128 lines
3.8 KiB
Python

"""Tests for the top-level CLI, focused on ``db migrate-to-postgres``.
The migration engine itself is covered in test_common/test_db_migrate.py; here we
verify the command's wiring: option plumbing, dry-run vs. real output, and how the
MigrationResult (or an error) maps onto exit codes and messages.
"""
from unittest.mock import patch
from click.testing import CliRunner
from meshcore_hub.__main__ import cli
from meshcore_hub.common.db_migrate import MigrationResult, TableResult
def _result(*tables: TableResult, dry_run: bool = False) -> MigrationResult:
return MigrationResult(tables=list(tables), dry_run=dry_run)
def test_migrate_to_postgres_success() -> None:
"""A matching run reports OK per table and exits 0."""
runner = CliRunner()
fake = _result(TableResult("nodes", 5, 5))
with patch(
"meshcore_hub.common.db_migrate.migrate_sqlite_to_postgres",
return_value=fake,
) as mock_migrate:
result = runner.invoke(
cli,
[
"db",
"migrate-to-postgres",
"--source",
"sqlite:///src.db",
"--target",
"postgresql://u@h/db",
],
)
assert result.exit_code == 0
assert "nodes" in result.output
assert "OK" in result.output
assert "Migration complete." in result.output
# Flags thread through to the engine call.
_, kwargs = mock_migrate.call_args
assert kwargs["dry_run"] is False
assert kwargs["truncate"] is False
def test_migrate_to_postgres_dry_run() -> None:
"""Dry run prints a preview and never renders OK/MISMATCH judgements."""
runner = CliRunner()
fake = _result(TableResult("nodes", 3, 0), dry_run=True)
with patch(
"meshcore_hub.common.db_migrate.migrate_sqlite_to_postgres",
return_value=fake,
) as mock_migrate:
result = runner.invoke(
cli,
[
"db",
"migrate-to-postgres",
"--source",
"sqlite:///src.db",
"--target",
"postgresql://u@h/db",
"--dry-run",
],
)
assert result.exit_code == 0
assert "dry-run" in result.output
assert "Dry run complete." in result.output
assert "OK" not in result.output
assert mock_migrate.call_args.kwargs["dry_run"] is True
def test_migrate_to_postgres_mismatch_exits_nonzero() -> None:
"""A row-count mismatch surfaces as a ClickException (non-zero exit)."""
runner = CliRunner()
fake = _result(TableResult("nodes", 5, 4)) # ok == False
with patch(
"meshcore_hub.common.db_migrate.migrate_sqlite_to_postgres",
return_value=fake,
):
result = runner.invoke(
cli,
[
"db",
"migrate-to-postgres",
"--source",
"sqlite:///src.db",
"--target",
"postgresql://u@h/db",
],
)
assert result.exit_code != 0
assert "MISMATCH" in result.output
assert "mismatch" in result.output.lower()
def test_migrate_to_postgres_value_error_becomes_click_exception() -> None:
"""A ValueError from the engine (e.g. bad target) maps to a clean CLI error."""
runner = CliRunner()
with patch(
"meshcore_hub.common.db_migrate.migrate_sqlite_to_postgres",
side_effect=ValueError("Target must be a PostgreSQL database URL"),
):
result = runner.invoke(
cli,
[
"db",
"migrate-to-postgres",
"--source",
"sqlite:///src.db",
"--target",
"sqlite:///bad.db",
],
)
assert result.exit_code != 0
assert "PostgreSQL" in result.output