File size: 5,006 Bytes
a006afd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import pytest
from importlib_resources import files
from typing import Generator, List, Callable
import chromadb.db.migrations as migrations
from chromadb.db.impl.sqlite import SqliteDB
from chromadb.config import System, Settings
from pytest import FixtureRequest
import copy


def sqlite() -> Generator[migrations.MigratableDB, None, None]:
    """Fixture generator for sqlite DB"""
    db = SqliteDB(
        System(
            Settings(
                migrations="none",
                allow_reset=True,
            )
        )
    )
    db.start()
    yield db


def db_fixtures() -> List[Callable[[], Generator[migrations.MigratableDB, None, None]]]:
    return [sqlite]


@pytest.fixture(scope="module", params=db_fixtures())
def db(request: FixtureRequest) -> Generator[migrations.MigratableDB, None, None]:
    yield next(request.param())


# Some Database impls improperly swallow exceptions, test that the wrapper works
def test_exception_propagation(db: migrations.MigratableDB) -> None:
    with pytest.raises(Exception):
        with db.tx():
            raise (Exception("test exception"))


def test_setup_migrations(db: migrations.MigratableDB) -> None:
    db.reset_state()
    db.setup_migrations()
    db.setup_migrations()  # idempotent

    with db.tx() as cursor:
        rows = cursor.execute("SELECT * FROM migrations").fetchall()
        assert len(rows) == 0


def test_migrations(db: migrations.MigratableDB) -> None:
    db.initialize_migrations()

    dir = files("chromadb.test.db.migrations")
    db_migrations = db.db_migrations(dir)
    source_migrations = migrations.find_migrations(dir, db.migration_scope())

    unapplied_migrations = migrations.verify_migration_sequence(
        db_migrations, source_migrations
    )

    assert unapplied_migrations == source_migrations

    with db.tx() as cur:
        rows = cur.execute("SELECT * FROM migrations").fetchall()
        assert len(rows) == 0

    with db.tx() as cur:
        for m in unapplied_migrations[:-1]:
            db.apply_migration(cur, m)

    db_migrations = db.db_migrations(dir)
    unapplied_migrations = migrations.verify_migration_sequence(
        db_migrations, source_migrations
    )

    assert len(unapplied_migrations) == 1
    assert unapplied_migrations[0]["version"] == 3

    with db.tx() as cur:
        assert len(cur.execute("SELECT * FROM migrations").fetchall()) == 2
        assert len(cur.execute("SELECT * FROM table1").fetchall()) == 0
        assert len(cur.execute("SELECT * FROM table2").fetchall()) == 0
        with pytest.raises(Exception):
            cur.execute("SELECT * FROM table3").fetchall()

    with db.tx() as cur:
        for m in unapplied_migrations:
            db.apply_migration(cur, m)

    db_migrations = db.db_migrations(dir)
    unapplied_migrations = migrations.verify_migration_sequence(
        db_migrations, source_migrations
    )

    assert len(unapplied_migrations) == 0

    with db.tx() as cur:
        assert len(cur.execute("SELECT * FROM migrations").fetchall()) == 3
        assert len(cur.execute("SELECT * FROM table3").fetchall()) == 0


def test_tampered_migration(db: migrations.MigratableDB) -> None:
    db.reset_state()

    db.setup_migrations()

    dir = files("chromadb.test.db.migrations")
    source_migrations = migrations.find_migrations(dir, db.migration_scope())

    db_migrations = db.db_migrations(dir)

    unapplied_migrations = migrations.verify_migration_sequence(
        db_migrations, source_migrations
    )

    with db.tx() as cur:
        for m in unapplied_migrations:
            db.apply_migration(cur, m)

    db_migrations = db.db_migrations(dir)
    unapplied_migrations = migrations.verify_migration_sequence(
        db_migrations, source_migrations
    )
    assert len(unapplied_migrations) == 0

    inconsistent_version_migrations = copy.deepcopy(source_migrations)
    inconsistent_version_migrations[0]["version"] = 2

    with pytest.raises(migrations.InconsistentVersionError):
        migrations.verify_migration_sequence(
            db_migrations, inconsistent_version_migrations
        )

    inconsistent_hash_migrations = copy.deepcopy(source_migrations)
    inconsistent_hash_migrations[0]["hash"] = "badhash"

    with pytest.raises(migrations.InconsistentHashError):
        migrations.verify_migration_sequence(
            db_migrations, inconsistent_hash_migrations
        )


def test_initialization(
    monkeypatch: pytest.MonkeyPatch, db: migrations.MigratableDB
) -> None:
    db.reset_state()
    dir = files("chromadb.test.db.migrations")
    monkeypatch.setattr(db, "migration_dirs", lambda: [dir])

    assert not db.migrations_initialized()

    with pytest.raises(migrations.UninitializedMigrationsError):
        db.validate_migrations()

    db.setup_migrations()

    assert db.migrations_initialized()

    with pytest.raises(migrations.UnappliedMigrationsError):
        db.validate_migrations()

    db.apply_migrations()
    db.validate_migrations()