Files
ai-exchange/tests/unit/test_migration_runner.py
T

86 lines
3.4 KiB
Python

import contextlib
import io
import json
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from sqlalchemy.sql.elements import TextClause
from sqlalchemy.exc import OperationalError
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
from scripts import run_migrations as run_migrations_module
from scripts.run_migrations import apply_migration_file, run_migrations as run_migration_files, split_sql_statements
class MigrationRunnerTests(unittest.TestCase):
def test_split_sql_statements_keeps_semicolon_inside_quotes(self) -> None:
sql = """
-- comment with ; should be ignored
create table sample (id bigint);
insert into sample (note) values ('semi;colon');
"""
statements = split_sql_statements(sql)
self.assertEqual(len(statements), 2)
self.assertIn("create table sample", statements[0])
self.assertIn("'semi;colon'", statements[1])
def test_apply_migration_file_executes_each_statement(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "001_sample.sql"
path.write_text("create table a (id bigint); create table b (id bigint);", encoding="utf-8")
connection = FakeConnection()
count = apply_migration_file(connection, path)
self.assertEqual(count, 2)
self.assertEqual(len(connection.statements), 2)
self.assertTrue(all(isinstance(statement, TextClause) for statement in connection.statements))
def test_run_migrations_reports_each_file(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
first = Path(tmpdir) / "001_sample.sql"
second = Path(tmpdir) / "002_sample.sql"
first.write_text("create table a (id bigint);", encoding="utf-8")
second.write_text("create table b (id bigint); create table c (id bigint);", encoding="utf-8")
connection = FakeConnection()
results = run_migration_files(connection=connection, files=[first, second])
self.assertEqual(results, [{"file": "001_sample.sql", "statements": 1}, {"file": "002_sample.sql", "statements": 2}])
def test_main_reports_database_unavailable(self) -> None:
output = io.StringIO()
with patch("scripts.run_migrations.engine.begin", side_effect=OperationalError("stmt", {}, Exception())), contextlib.redirect_stdout(output):
exit_code = run_migrations_module.main()
self.assertEqual(exit_code, 2)
self.assertIn("Database unavailable for migrations: OperationalError", output.getvalue())
def test_main_can_emit_json_failure(self) -> None:
output = io.StringIO()
with patch("scripts.run_migrations.engine.begin", side_effect=OperationalError("stmt", {}, Exception())), contextlib.redirect_stdout(output):
exit_code = run_migrations_module.main(env={"MIGRATIONS_OUTPUT_JSON": "1"})
payload = json.loads(output.getvalue())
self.assertEqual(exit_code, 2)
self.assertFalse(payload["passed"])
self.assertEqual(payload["error_type"], "OperationalError")
class FakeConnection:
def __init__(self) -> None:
self.statements = []
def execute(self, statement):
self.statements.append(statement)
if __name__ == "__main__":
unittest.main()