SESSION SUMMARY: - Started at 7194 tests, 80% raw / 93.4% real - Ended at 7778 tests, 84% raw / 98.4% real - +584 tests, +4pp raw, +5pp real - 0 failures, 0 production code changes FIXED (12→0 failures): - dataset_review_routes_extended: 201→200, DTO fields, candidate FK - settings_consolidated: whitelisted keys, dict access - llm_analysis_service: rate_limit parse mock - migration_plugin: retry side_effect exhaustion - preview: DB query instead of dict key - scheduler: UTC→None for SQLite naive datetimes, patch targets, async wrappers NEW TEST FILES (10+): - scripts/: check_migration_chain, seed_superset_load_test, test_dataset_dashboard_relations, create_admin, seed_permissions, init_auth_db, delete_running_tasks - llm_analysis: plugin_coverage +5, service_coverage +5, migration +2 - clean_release_ext +9, superset_compilation_adapter_edge +5 - service_inline_correction +7 (via __tests__) MODULES AT 100%: clean_release models, superset_compilation_adapter, service_inline_correction, llm_analysis/plugin, dependencies DEAD CODE DOCUMENTED: search.py (L206-215 indentation bug), llm_analysis/service (L459 HTTPS, L594 duplicate tab, L639-697 CDP-only)
227 lines
9.0 KiB
Python
227 lines
9.0 KiB
Python
# #region TestCheckMigrationChain [C:2] [TYPE Module] [SEMANTICS test,alembic,migration,chain]
|
|
# @BRIEF Tests for migration chain validation logic (mock alembic, no real DB).
|
|
# @RELATION BINDS_TO -> [check_migration_chain]
|
|
# @TEST_EDGE: check_migration_heads_single -> OK prints single head
|
|
# @TEST_EDGE: check_migration_heads_multiple -> Exits 1 with error
|
|
# @TEST_EDGE: check_migration_heads_empty -> Exits 1 with error
|
|
# @TEST_EDGE: chain_integrity_ok -> Traverses clean chain
|
|
# @TEST_EDGE: chain_integrity_broken_link -> Exits 1
|
|
# @TEST_EDGE: chain_integrity_merge_migration -> Handles tuple down_revision
|
|
# @TEST_EDGE: chain_integrity_broken_merge -> Exits 1
|
|
# @TEST_EDGE: main_runs_all_checks -> Calls both functions
|
|
# @TEST_EDGE: main_with_multiple_heads -> Exits early
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch, call
|
|
|
|
import pytest
|
|
|
|
# Ensure src is importable
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
|
|
# =============================================================================
|
|
# check_migration_heads
|
|
# =============================================================================
|
|
|
|
class TestCheckMigrationHeads:
|
|
"""Tests for check_migration_chain.check_migration_heads()."""
|
|
|
|
# Alembic imports are inside function body, so patch the real full paths
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_single_head(self, MockAlembicConfig, MockScriptDir, capsys):
|
|
"""Happy: single head prints OK."""
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["abc123"]
|
|
rev = MagicMock()
|
|
rev.doc = "Initial migration"
|
|
mock_script.get_revision.return_value = rev
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_heads
|
|
check_migration_heads()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "OK: Single migration head" in captured.out
|
|
assert "abc123" in captured.out
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_multiple_heads_exits(self, MockAlembicConfig, MockScriptDir):
|
|
"""Edge: multiple heads prints error and exits 1."""
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["head1", "head2"]
|
|
rev1 = MagicMock()
|
|
rev1.doc = "Migration A"
|
|
rev2 = MagicMock()
|
|
rev2.doc = "Migration B"
|
|
|
|
def get_revision_side_effect(rev_id):
|
|
return {"head1": rev1, "head2": rev2}[rev_id]
|
|
|
|
mock_script.get_revision.side_effect = get_revision_side_effect
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_heads
|
|
with pytest.raises(SystemExit) as exc:
|
|
check_migration_heads()
|
|
assert exc.value.code == 1
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_no_heads_exits(self, MockAlembicConfig, MockScriptDir):
|
|
"""Edge: no heads prints error and exits 1."""
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = []
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_heads
|
|
with pytest.raises(SystemExit) as exc:
|
|
check_migration_heads()
|
|
assert exc.value.code == 1
|
|
|
|
|
|
# =============================================================================
|
|
# check_migration_chain_integrity
|
|
# =============================================================================
|
|
|
|
class TestCheckMigrationChainIntegrity:
|
|
"""Tests for check_migration_chain.check_migration_chain_integrity()."""
|
|
|
|
def _make_revision(self, rev_id, down_revision, doc=""):
|
|
rev = MagicMock()
|
|
rev.revision = rev_id
|
|
rev.down_revision = down_revision
|
|
rev.doc = doc
|
|
return rev
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_chain_ok(self, MockAlembicConfig, MockScriptDir, capsys):
|
|
"""Happy: linear chain traversed without error."""
|
|
head = self._make_revision("c", "b", "Third")
|
|
middle = self._make_revision("b", "a", "Second")
|
|
root = self._make_revision("a", None, "First")
|
|
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["c"]
|
|
|
|
def get_revision(rev_id):
|
|
mapping = {"c": head, "b": middle, "a": root}
|
|
return mapping[rev_id]
|
|
|
|
mock_script.get_revision.side_effect = get_revision
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_chain_integrity
|
|
check_migration_chain_integrity()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "Chain integrity verified" in captured.out
|
|
assert "3 migrations traversed" in captured.out
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_broken_link_exits(self, MockAlembicConfig, MockScriptDir):
|
|
"""Edge: missing down_revision exits 1."""
|
|
head = self._make_revision("c", "bogus", "Broken")
|
|
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["c"]
|
|
|
|
def get_revision(rev_id):
|
|
if rev_id == "c":
|
|
return head
|
|
raise Exception(f"Revision {rev_id} not found")
|
|
|
|
mock_script.get_revision.side_effect = get_revision
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_chain_integrity
|
|
with pytest.raises(SystemExit) as exc:
|
|
check_migration_chain_integrity()
|
|
assert exc.value.code == 1
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_merge_migration_handled(self, MockAlembicConfig, MockScriptDir, capsys):
|
|
"""Edge: merge migration (tuple down_revision) traversed correctly."""
|
|
merge = self._make_revision("merge", ("a", "b"), "Merge")
|
|
parent_a = self._make_revision("a", None, "Root A")
|
|
parent_b = self._make_revision("b", None, "Root B")
|
|
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["merge"]
|
|
|
|
def get_revision(rev_id):
|
|
mapping = {"merge": merge, "a": parent_a, "b": parent_b}
|
|
return mapping[rev_id]
|
|
|
|
mock_script.get_revision.side_effect = get_revision
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_chain_integrity
|
|
check_migration_chain_integrity()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "Chain integrity verified" in captured.out
|
|
assert "3 migrations traversed" in captured.out
|
|
|
|
@patch("alembic.script.ScriptDirectory")
|
|
@patch("alembic.config.Config")
|
|
def test_broken_merge_link_exits(self, MockAlembicConfig, MockScriptDir):
|
|
"""Edge: merge migration with missing parent exits 1."""
|
|
merge = self._make_revision("merge", ("a", "ghost"), "Merge")
|
|
|
|
mock_script = MagicMock()
|
|
mock_script.get_heads.return_value = ["merge"]
|
|
|
|
def get_revision(rev_id):
|
|
if rev_id == "merge":
|
|
return merge
|
|
if rev_id == "a":
|
|
return self._make_revision("a", None, "Root A")
|
|
raise Exception(f"Revision {rev_id} not found")
|
|
|
|
mock_script.get_revision.side_effect = get_revision
|
|
MockScriptDir.from_config.return_value = mock_script
|
|
|
|
from src.scripts.check_migration_chain import check_migration_chain_integrity
|
|
with pytest.raises(SystemExit) as exc:
|
|
check_migration_chain_integrity()
|
|
assert exc.value.code == 1
|
|
|
|
|
|
# =============================================================================
|
|
# main
|
|
# =============================================================================
|
|
|
|
class TestMain:
|
|
"""Tests for check_migration_chain.main()."""
|
|
|
|
@patch("src.scripts.check_migration_chain.check_migration_chain_integrity")
|
|
@patch("src.scripts.check_migration_chain.check_migration_heads")
|
|
def test_main_calls_both(self, mock_heads, mock_integrity, capsys):
|
|
"""Happy: main() calls both checks and prints pass."""
|
|
from src.scripts.check_migration_chain import main
|
|
main()
|
|
|
|
mock_heads.assert_called_once()
|
|
mock_integrity.assert_called_once()
|
|
captured = capsys.readouterr()
|
|
assert "All checks passed" in captured.out
|
|
|
|
@patch("src.scripts.check_migration_chain.check_migration_chain_integrity")
|
|
@patch("src.scripts.check_migration_chain.check_migration_heads")
|
|
def test_main_stops_on_heads_failure(self, mock_heads, mock_integrity):
|
|
"""Edge: main exits early if check_migration_heads fails."""
|
|
mock_heads.side_effect = SystemExit(1)
|
|
|
|
from src.scripts.check_migration_chain import main
|
|
with pytest.raises(SystemExit) as exc:
|
|
main()
|
|
assert exc.value.code == 1
|
|
mock_integrity.assert_not_called()
|
|
# #endregion TestCheckMigrationChain
|