refactor: establish database migration foundation
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from backend.database import Migration, MigrationError, MigrationRunner
|
||||
from database import ReviewDatabase
|
||||
|
||||
|
||||
class DatabaseMigrationTests(unittest.TestCase):
|
||||
def test_fresh_database_records_the_adopted_schema_once(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
path = Path(root) / "review.db"
|
||||
database = ReviewDatabase(path)
|
||||
with database.connect() as connection:
|
||||
rows = connection.execute(
|
||||
"SELECT version, name FROM schema_migrations"
|
||||
).fetchall()
|
||||
self.assertEqual(
|
||||
[(row["version"], row["name"]) for row in rows],
|
||||
[("0001", "adopt_legacy_schema")],
|
||||
)
|
||||
ReviewDatabase(path)
|
||||
with database.connect() as connection:
|
||||
count = connection.execute(
|
||||
"SELECT COUNT(*) AS count FROM schema_migrations"
|
||||
).fetchone()["count"]
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
def test_connection_factory_enables_required_pragmas(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
database = ReviewDatabase(Path(root) / "review.db")
|
||||
with database.connect() as connection:
|
||||
self.assertEqual(connection.execute("PRAGMA foreign_keys").fetchone()[0], 1)
|
||||
self.assertEqual(connection.execute("PRAGMA journal_mode").fetchone()[0], "wal")
|
||||
self.assertEqual(connection.execute("PRAGMA busy_timeout").fetchone()[0], 20000)
|
||||
|
||||
def test_failed_migration_rolls_back_and_is_not_recorded(self) -> None:
|
||||
connection = sqlite3.connect(":memory:")
|
||||
self.addCleanup(connection.close)
|
||||
connection.row_factory = sqlite3.Row
|
||||
|
||||
def fail(conn: sqlite3.Connection) -> None:
|
||||
conn.execute("CREATE TABLE should_rollback (id INTEGER)")
|
||||
raise RuntimeError("stop")
|
||||
|
||||
migration = Migration("9000", "failure", fail, "failure:v1")
|
||||
with self.assertRaises(MigrationError):
|
||||
MigrationRunner().apply(connection, (migration,))
|
||||
tables = {
|
||||
row["name"]
|
||||
for row in connection.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||
)
|
||||
}
|
||||
self.assertNotIn("should_rollback", tables)
|
||||
self.assertEqual(
|
||||
connection.execute("SELECT COUNT(*) FROM schema_migrations").fetchone()[0],
|
||||
0,
|
||||
)
|
||||
|
||||
def test_applied_migration_checksum_is_immutable(self) -> None:
|
||||
connection = sqlite3.connect(":memory:")
|
||||
self.addCleanup(connection.close)
|
||||
connection.row_factory = sqlite3.Row
|
||||
first = Migration("9001", "example", lambda conn: None, "example:v1")
|
||||
changed = Migration("9001", "example", lambda conn: None, "example:v2")
|
||||
runner = MigrationRunner()
|
||||
runner.apply(connection, (first,))
|
||||
with self.assertRaises(MigrationError):
|
||||
runner.apply(connection, (changed,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user