Skip to content

Commit c90efef

Browse files
committed
Working on sqlite integration tests
1 parent 5e6c5b5 commit c90efef

30 files changed

+346
-85
lines changed

src/flux/migration/read_migration.py

+18-11
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def read_migrations(*, config: FluxConfig) -> list[Migration]:
1414
"""
1515
migrations = []
1616
for migration_file in os.listdir(config.migration_directory):
17-
if migration_file.endswith("_up.sql"):
18-
migration_id = migration_file[:-7]
17+
if migration_file.endswith(".sql") and not migration_file.endswith(".undo.sql"):
18+
migration_id = migration_file[:-4]
1919
migrations.append(
2020
read_sql_migration(config=config, migration_id=migration_id)
2121
)
@@ -88,8 +88,8 @@ def read_sql_migration(*, config: FluxConfig, migration_id: str) -> Migration:
8888
"""
8989
Read a pair of SQL migration files and return a Migration object
9090
"""
91-
up_file = os.path.join(config.migration_directory, f"{migration_id}_up.sql")
92-
down_file = os.path.join(config.migration_directory, f"{migration_id}_down.sql")
91+
up_file = os.path.join(config.migration_directory, f"{migration_id}.sql")
92+
down_file = os.path.join(config.migration_directory, f"{migration_id}.undo.sql")
9393

9494
try:
9595
with open(up_file) as f:
@@ -128,6 +128,13 @@ def read_repeatable_sql_migration(
128128
except Exception as e:
129129
raise MigrationLoadingError("Error reading up migration") from e
130130

131+
if os.path.exists(
132+
os.path.join(
133+
config.migration_directory, migration_subdir, f"{migration_id}.undo.sql"
134+
)
135+
):
136+
raise MigrationLoadingError("Repeatable migrations cannot have a down")
137+
131138
return Migration(id=migration_id, up=up, down=None)
132139

133140

@@ -139,19 +146,19 @@ def read_python_migration(*, config: FluxConfig, migration_id: str) -> Migration
139146

140147
with temporary_module(migration_file) as module:
141148
try:
142-
up_migration = module.up()
149+
up_migration = module.apply()
143150
except Exception as e:
144151
raise MigrationLoadingError("Error reading up migration") from e
145152
if not isinstance(up_migration, str):
146153
raise MigrationLoadingError("Up migration must return a string")
147-
if hasattr(module, "down"):
154+
if hasattr(module, "undo"):
148155
try:
149-
down_migration = module.down()
156+
down_migration = module.undo()
150157
except Exception as e:
151158
raise MigrationLoadingError("Error reading down migration") from e
152-
if not isinstance(down_migration, str) and down_migration is not None:
159+
if not isinstance(down_migration, str):
153160
raise MigrationLoadingError(
154-
"Down migration must return a string or None"
161+
"Down migration must return a string"
155162
)
156163
else:
157164
down_migration = None
@@ -174,12 +181,12 @@ def read_repeatable_python_migration(
174181

175182
with temporary_module(migration_file) as module:
176183
try:
177-
up_migration = module.up()
184+
up_migration = module.apply()
178185
except Exception as e:
179186
raise MigrationLoadingError("Error reading up migration") from e
180187
if not isinstance(up_migration, str):
181188
raise MigrationLoadingError("Up migration must return a string")
182-
if hasattr(module, "down"):
189+
if hasattr(module, "undo"):
183190
raise MigrationLoadingError("Repeatable migrations cannot have a down")
184191

185192
return Migration(id=migration_id, up=up_migration, down=None)

tests/integration/sqlite/backend.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from contextlib import asynccontextmanager
33
from dataclasses import dataclass, field
44
from typing import Any
5-
65
import aiosqlite
6+
from aiosqlite import Connection
77

88
from flux.backend.applied_migration import AppliedMigration
99
from flux.backend.base import MigrationBackend
@@ -18,7 +18,7 @@ class SQLiteBackend(MigrationBackend):
1818
db_path: str
1919
migrations_table: str
2020

21-
_conn: Any = field(init=False, repr=False)
21+
_conn: Connection = field(init=False, repr=False)
2222

2323
@classmethod
2424
def from_config(cls, config: FluxConfig) -> "SQLiteBackend":
@@ -64,10 +64,11 @@ async def transaction(self):
6464
"""
6565
try:
6666
yield
67-
await self._conn.commit()
6867
except Exception:
6968
await self._conn.rollback()
7069
raise
70+
else:
71+
await self._conn.commit()
7172

7273
@asynccontextmanager
7374
async def migration_lock(self):
@@ -156,3 +157,19 @@ async def get_applied_migrations(self) -> set[AppliedMigration]:
156157
AppliedMigration(id=row[0], hash=row[1], applied_at=row[2])
157158
for row in await cursor.fetchall()
158159
}
160+
161+
# -- Testing methods
162+
163+
async def table_info(self, table_name: str):
164+
"""
165+
Get information about a table in the database
166+
"""
167+
cursor = await self._conn.execute(f"pragma table_info({table_name})")
168+
return await cursor.fetchall()
169+
170+
async def get_all_rows(self, table_name: str):
171+
"""
172+
Get all rows from a table
173+
"""
174+
cursor = await self._conn.execute(f"select * from {table_name}")
175+
return await cursor.fetchall()

tests/integration/sqlite/conftest.py

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import pytest
66

77
from tests.integration.sqlite.backend import SQLiteBackend
8+
from tests.integration.sqlite.constants import MIGRATIONS_1_DIR
9+
import shutil
810

911

1012
@pytest.fixture
@@ -17,3 +19,15 @@ def temp_db_path() -> Generator[str, None, None]:
1719
@pytest.fixture
1820
def sqlite_backend(temp_db_path: str):
1921
yield SQLiteBackend(db_path=temp_db_path, migrations_table="_flux_migrations")
22+
23+
24+
@pytest.fixture
25+
def example_migrations_dir() -> Generator[str, None, None]:
26+
"""
27+
Create a temporary copy of the migrations directory that can be freely
28+
modified by tests
29+
"""
30+
with TemporaryDirectory() as tempdir:
31+
migrations_dir = os.path.join(tempdir, "migrations")
32+
shutil.copytree(MIGRATIONS_1_DIR, migrations_dir)
33+
yield migrations_dir
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
alter table simple_table drop column description

tests/integration/sqlite/data/migrations-1/20200101_001_add_description_to_simple_table_down.sql

-14
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
def up():
1+
def apply():
22
return """
33
alter table another_table add column timestamp text;
44
"""
55

66

7-
def down():
8-
"""
9-
pragma foreign_keys=off;
10-
11-
create table another_table_new (
12-
id integer primary key autoincrement,
13-
value integer
14-
);
15-
16-
insert into another_table_new (id, value)
17-
select id, value from another_table;
18-
19-
drop table another_table;
20-
alter table another_table_new rename to another_table;
21-
22-
pragma foreign_keys=on;
7+
def undo():
8+
return """
9+
alter table another_table drop column timestamp;
2310
"""

tests/integration/sqlite/data/migrations-1/20200102_001_create_new_table.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
def up():
1+
def apply():
22
return """
33
create table if not exists new_table (
44
id integer primary key autoincrement,

tests/integration/sqlite/data/migrations-1/post-apply/20200101_002_recreate_view2.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
def up():
1+
def apply():
22
return """
33
create view if not exists view2 as
44
select id, value from another_table where value > 10;

tests/integration/sqlite/data/migrations-1/pre-apply/20200101_002_ensure_simple_table_exists.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
def up():
1+
def apply():
22
return """
33
create table if not exists simple_table (
44
id integer primary key autoincrement,

0 commit comments

Comments
 (0)