fix(alembic): refuse to run without a named migration target (T-3)

`alembic/env.py` took its target from `get_settings().database_url`,
composed from `.env`'s production `POSTGRES_*` values, so the most natural
command in the repo — a bare `alembic current` — talked to production.
`-x target=test` and `-x target=production` now name the database
explicitly; anything else exits non-zero before any engine is built, and
the one line it prints before migrating never shows a credential.

`run_alembic` (tests/conftest.py) passes `-x target=test` and sets
`TEST_DATABASE_URL` in the subprocess environment instead of overriding
`POSTGRES_*` — the `_components()` shim is gone; nothing else used it.

`make migrate-test`, `make migrate-production` and `make dev-test-server`
are the three documented ways to reach either database now; `dev-test-server`
refuses to start if the resolved database is not `core_api_test`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 00:26:48 +02:00
co-authored by Claude Sonnet 5
parent 237bdf9306
commit 3a3dc959be
10 changed files with 242 additions and 33 deletions
+49
View File
@@ -0,0 +1,49 @@
"""Tests for core-api T-3: alembic refuses to run without a named target.
`alembic/env.py` resolves `-x target=test` or `-x target=production`; no
target, or anything else, must exit non-zero before any engine is built —
not skip, not fall back to the production settings a bare `alembic current`
used to reach by accident. Both tests point the *production* settings
(`POSTGRES_*`) at an unreachable host: if the refusal ever started
connecting before checking the target, these would hang (and the
subprocess timeout below would fire) instead of returning quickly.
"""
import os
import subprocess
from tests.conftest import ALEMBIC_BIN, REPO_ROOT
UNREACHABLE_HOST = "10.255.255.1" # non-routed; a real connection attempt would hang
def _run_with_unreachable_production(*args: str) -> subprocess.CompletedProcess:
env = os.environ.copy()
env["POSTGRES_HOST"] = UNREACHABLE_HOST
env["POSTGRES_USER"] = "probe"
env["POSTGRES_PASSWORD"] = "probe"
env["POSTGRES_DATABASE"] = "probe"
env.pop("TEST_DATABASE_URL", None)
return subprocess.run(
[str(ALEMBIC_BIN), *args],
cwd=str(REPO_ROOT),
env=env,
capture_output=True,
text=True,
timeout=10, # a real connection attempt to an unreachable host hangs well past this
)
def test_no_target_refuses_before_connecting():
result = _run_with_unreachable_production("current")
assert result.returncode != 0
combined = result.stdout + result.stderr
assert "target=test" in combined
assert "target=production" in combined
def test_nonsense_target_refuses_before_connecting():
result = _run_with_unreachable_production("-x", "target=nonsense", "current")
assert result.returncode != 0
combined = result.stdout + result.stderr
assert "target=test" in combined
assert "target=production" in combined