File size: 2,996 Bytes
0c85e62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Test that Alembic migrations run cleanly on a file-backed SQLite DB.

This catches problems like unnamed FK constraints that prevent batch-mode
drop_constraint from working on real SQLite files (see MB-2).

Migrations 0001 and 0002 are already shipped, so we only exercise
upgrade/downgrade for 0003+.
"""

import os
import sqlite3

import pytest
from alembic import command
from alembic.config import Config


# Oldest shipped revision — we upgrade to here as a baseline and never
# downgrade past it.
_BASELINE = "0002_merge_to_asset_references"


def _make_config(db_path: str) -> Config:
    root = os.path.join(os.path.dirname(__file__), "../..")
    config_path = os.path.abspath(os.path.join(root, "alembic.ini"))
    scripts_path = os.path.abspath(os.path.join(root, "alembic_db"))

    cfg = Config(config_path)
    cfg.set_main_option("script_location", scripts_path)
    cfg.set_main_option("sqlalchemy.url", f"sqlite:///{db_path}")
    return cfg


def _sqlite_path(cfg: Config) -> str:
    url = cfg.get_main_option("sqlalchemy.url")
    assert url is not None and url.startswith("sqlite:///")
    return url.removeprefix("sqlite:///")


@pytest.fixture
def migration_db(tmp_path):
    """Yield an alembic Config pre-upgraded to the baseline revision."""
    db_path = str(tmp_path / "test_migration.db")
    cfg = _make_config(db_path)
    command.upgrade(cfg, _BASELINE)
    yield cfg


def test_upgrade_to_head(migration_db):
    """Upgrade from baseline to head must succeed on a file-backed DB."""
    command.upgrade(migration_db, "head")


def test_downgrade_to_baseline(migration_db):
    """Upgrade to head then downgrade back to baseline."""
    command.upgrade(migration_db, "head")
    command.downgrade(migration_db, _BASELINE)


def test_upgrade_downgrade_cycle(migration_db):
    """Full cycle: upgrade → downgrade → upgrade again."""
    command.upgrade(migration_db, "head")
    command.downgrade(migration_db, _BASELINE)
    command.upgrade(migration_db, "head")


def test_case_sensitive_tags_downgrade_normalizes_existing_tags(migration_db):
    """Downgrading 0005 folds mixed-case tag vocabulary before restoring CHECK."""
    command.upgrade(migration_db, "0005_allow_case_sensitive_tags")

    db_path = _sqlite_path(migration_db)
    with sqlite3.connect(db_path) as conn:
        conn.execute("INSERT INTO tags(name) VALUES (?)", ("NewTag",))
        conn.execute("INSERT INTO tags(name) VALUES (?)", ("newtag",))
        conn.execute("INSERT INTO tags(name) VALUES (?)", ("model_type:LLM",))

    command.downgrade(migration_db, "0004_drop_tag_type")

    with sqlite3.connect(db_path) as conn:
        tags = {row[0] for row in conn.execute("SELECT name FROM tags")}
        assert "newtag" in tags
        assert "model_type:llm" in tags
        assert "NewTag" not in tags
        assert "model_type:LLM" not in tags

        with pytest.raises(sqlite3.IntegrityError):
            conn.execute("INSERT INTO tags(name) VALUES (?)", ("Upper",))