Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions tests/test_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,104 @@ def test_dynamic_inserted_before_hybrid_no_multiple_heads(
assert _walk_chain(full) == ["aaaa", "bbbb", "dddd", "cccc"]


def test_mergify_engine_architecture(tmp_path: pathlib.Path) -> None:
"""Reproduce the mergify-engine migration layout and verify the chain file.

mergify-engine has three tiers of migrations:

* A long static chain (394 migrations with hardcoded down_revision).
* A dynamic chain (22 migrations using get_down_revision()).
* One hybrid migration (static down_revision pointing to a dynamic rev).

This test recreates that architecture at a smaller scale and verifies
that generate_chain_file produces a JSON file that, combined with the
static down_revisions, yields one linear chain from root to head.
"""
versions_dir = tmp_path / "versions"
versions_dir.mkdir()

# -- Static chain: s1 → s2 → s3 (like the 394 static migrations) --
(versions_dir / "aa01_static_root.py").write_text(
'revision = "aa01"\ndown_revision = None\n',
)
(versions_dir / "aa02_static_mid.py").write_text(
'revision = "aa02"\ndown_revision = "aa01"\n',
)
(versions_dir / "aa03_static_head.py").write_text(
'revision = "aa03"\ndown_revision = "aa02"\n',
)

# -- Dynamic chain: d1 → d2 → d3 → d4 → d5 (like the 22 dynamic) --
for i in range(1, 6):
(versions_dir / f"bb0{i}_dynamic_{i}.py").write_text(
"from alembic_git_revisions import get_down_revision\n"
f'revision = "bb0{i}"\n'
"down_revision = get_down_revision(revision)\n",
)

# -- Hybrid: static migration pointing to dynamic d4 (like 34c2e9a4b043) --
# In production this was added on a separate branch and merged after d5.
(versions_dir / "cc01_hybrid.py").write_text(
'revision = "cc01"\ndown_revision = "bb04"\n',
)

# Git order: statics first, then dynamics, then hybrid last (as in prod).
# d5 was merged before the hybrid, so it appears earlier in git order.
git_order = [
"aa01_static_root.py",
"aa02_static_mid.py",
"aa03_static_head.py",
"bb01_dynamic_1.py",
"bb02_dynamic_2.py",
"bb03_dynamic_3.py",
"bb04_dynamic_4.py",
"bb05_dynamic_5.py", # branch A merged first
"cc01_hybrid.py", # branch B merged second
]

# Generate the chain file (the JSON produced for Docker/CI builds).
_chain.build_chain.cache_clear()
with mock.patch.object(
_chain,
"_get_git_commit_order",
return_value=git_order,
):
_chain.generate_chain_file(versions_dir)

# Read the generated JSON.
chain_file = tmp_path / "revision_chain.json"
assert chain_file.exists()
chain = json.loads(chain_file.read_text())

# The chain file must contain exactly the dynamic migrations.
# The hybrid (cc01) is NOT in the file — it has a hardcoded down_revision.
assert chain == {
"bb01": "aa03", # first dynamic chains after static head
"bb02": "bb01",
"bb03": "bb02",
"bb04": "bb03",
# bb05 must chain after the hybrid, not after bb04 (the bug)
"bb05": "cc01",
}

# Reconstruct the full chain (static + dynamic + hybrid) and walk it.
files = _chain._parse_migration_files(versions_dir, git_order)
full = {f.revision: f.static_down_revision for f in files if not f.is_dynamic}
full.update(chain)

assert _walk_chain(full) == [
"aa01", # static root
"aa02",
"aa03", # static head
"bb01", # dynamic chain starts
"bb02",
"bb03",
"bb04", # hybrid's target
"cc01", # hybrid (hardcoded down_revision="bb04")
"bb05", # last dynamic, chains after hybrid
]


def test_auto_discover_versions_dir(tmp_path: pathlib.Path) -> None:
"""get_down_revision auto-discovers versions_dir from caller's location."""
versions_dir = tmp_path / "versions"
Expand Down