mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
"""Pure decision logic of scripts/verify_postgres_enums.py.
|
|||
|
|
|
||
|
|
The verifier is a gate script: it must NOT report drift against an empty /
|
||
|
|
unmigrated DB (no enum types → "no migrated target", not drift), and it must
|
||
|
|
distinguish unreachable (skip) from real drift (fail) so the Makefile mask
|
||
|
|
can't swallow drift into a false-green. These tests pin the two pure
|
||
|
|
predicates behind the I/O shell; the asyncpg connect/fetch path is exercised
|
||
|
|
by the live empty-`roboco`-DB smoke (`test_smoke_empty_db_skips`).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import importlib.util
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
SCRIPT = Path(__file__).parents[3] / "scripts" / "verify_postgres_enums.py"
|
||
|
|
|
||
|
|
# Load the script as an isolated module (it's a gate script, not a package).
|
||
|
|
_spec = importlib.util.spec_from_file_location("verify_postgres_enums", SCRIPT)
|
||
|
|
assert _spec is not None and _spec.loader is not None
|
||
|
|
_mod = importlib.util.module_from_spec(_spec)
|
||
|
|
_spec.loader.exec_module(_mod)
|
||
|
|
enum_drift = _mod.enum_drift
|
||
|
|
should_skip_for_unmigrated = _mod.should_skip_for_unmigrated
|
||
|
|
|
||
|
|
_SKIP = 0
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# enum_drift — pure set comparison
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_enum_drift_exact_match_is_no_drift() -> None:
|
||
|
|
roles = {"developer", "qa"}
|
||
|
|
teams = {"backend", "frontend"}
|
||
|
|
has_drift, msgs = enum_drift(roles, teams, roles.copy(), teams.copy())
|
||
|
|
assert has_drift is False
|
||
|
|
assert msgs == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_enum_drift_missing_role() -> None:
|
||
|
|
has_drift, msgs = enum_drift(
|
||
|
|
{"developer"}, {"backend"}, {"developer", "qa"}, {"backend"}
|
||
|
|
)
|
||
|
|
assert has_drift is True
|
||
|
|
assert any("agentrole missing" in m and "qa" in m for m in msgs)
|
||
|
|
|
||
|
|
|
||
|
|
def test_enum_drift_extra_role() -> None:
|
||
|
|
has_drift, msgs = enum_drift(
|
||
|
|
{"developer", "ghost"}, {"backend"}, {"developer"}, {"backend"}
|
||
|
|
)
|
||
|
|
assert has_drift is True
|
||
|
|
assert any("agentrole has extra" in m and "ghost" in m for m in msgs)
|
||
|
|
|
||
|
|
|
||
|
|
def test_enum_drift_missing_team() -> None:
|
||
|
|
has_drift, msgs = enum_drift(
|
||
|
|
{"developer"}, {"backend"}, {"developer"}, {"backend", "ux_ui"}
|
||
|
|
)
|
||
|
|
assert has_drift is True
|
||
|
|
assert any("team missing" in m and "ux_ui" in m for m in msgs)
|
||
|
|
|
||
|
|
|
||
|
|
def test_enum_drift_extra_team() -> None:
|
||
|
|
has_drift, msgs = enum_drift(
|
||
|
|
{"developer"}, {"backend", "phantom"}, {"developer"}, {"backend"}
|
||
|
|
)
|
||
|
|
assert has_drift is True
|
||
|
|
assert any("team has extra" in m and "phantom" in m for m in msgs)
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# should_skip_for_unmigrated — only skip when BOTH enum types are absent
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_skip_when_both_enum_types_absent() -> None:
|
||
|
|
assert should_skip_for_unmigrated(False, False) is True
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_skip_when_only_agentrole_present() -> None:
|
||
|
|
# A partial schema is itself suspicious — do not skip; let drift fire.
|
||
|
|
assert should_skip_for_unmigrated(True, False) is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_skip_when_only_team_present() -> None:
|
||
|
|
assert should_skip_for_unmigrated(False, True) is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_skip_when_both_present() -> None:
|
||
|
|
assert should_skip_for_unmigrated(True, True) is False
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Live smoke — the empty `roboco` DB on this host must skip (exit 0), not
|
||
|
|
# false-positive drift (exit 1). Skipped silently if no DB is reachable.
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_smoke_empty_db_skips() -> None:
|
||
|
|
"""Against an empty/unmigrated DB the verifier skips (exit 0)."""
|
||
|
|
result = subprocess.run(
|
||
|
|
[sys.executable, str(SCRIPT)],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
check=False,
|
||
|
|
)
|
||
|
|
if (
|
||
|
|
result.returncode == _SKIP
|
||
|
|
and "skipped" in (result.stdout + result.stderr).lower()
|
||
|
|
):
|
||
|
|
# A reachable-but-empty DB → skip path. (No DB → also exit 0 skip.)
|
||
|
|
assert "DRIFT" not in result.stdout
|
||
|
|
return
|
||
|
|
# If postgres is unreachable on this host, the smoke is a no-op skip.
|
||
|
|
assert result.returncode == _SKIP, (
|
||
|
|
f"expected skip exit 0, got {result.returncode}; out={result.stdout!r}"
|
||
|
|
)
|