We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/doobidoo/mcp-memory-service'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Unit tests for MigrationRunner.
Tests the SQL migration execution utility for both synchronous and
asynchronous database connections.
"""
import sqlite3
from pathlib import Path
import aiosqlite
import pytest
from mcp_memory_service.storage.migration_runner import MigrationRunner
@pytest.mark.unit
def test_migration_runner_sync_executes_sql_files(tmp_path):
"""Test that migration runner executes SQL files synchronously."""
# Setup: Create test migration file
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
test_migration = migrations_dir / "001_test.sql"
test_migration.write_text(
"CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY);"
)
# Create test database
db_path = tmp_path / "test.db"
# Execute migration using sync connection
conn = sqlite3.connect(db_path)
try:
runner = MigrationRunner(migrations_dir)
success, message = runner.run_migrations_sync(conn, ["001_test.sql"])
# Verify success
assert success
assert "Successfully executed 1 migrations" in message
# Verify table was created
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='test_table'"
)
result = cursor.fetchone()
assert result is not None
assert result[0] == "test_table"
finally:
conn.close()
@pytest.mark.asyncio
async def test_migration_runner_async_executes_sql_files(tmp_path):
"""Test that migration runner executes SQL files asynchronously."""
# Setup: Create test migration file
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
test_migration = migrations_dir / "001_test.sql"
test_migration.write_text(
"CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY);"
)
# Create test database
db_path = tmp_path / "test.db"
# Execute migration
runner = MigrationRunner(migrations_dir)
success, message = await runner.run_migrations_async(str(db_path), ["001_test.sql"])
# Verify success
assert success
assert "Successfully executed 1 migrations" in message
# Verify table was created
async with aiosqlite.connect(db_path) as conn:
cursor = await conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='test_table'"
)
result = await cursor.fetchone()
assert result is not None
assert result[0] == "test_table"
@pytest.mark.unit
def test_migration_runner_sync_multiple_files(tmp_path):
"""Test that migration runner executes multiple files in order."""
# Setup: Create multiple migration files
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create migrations in non-sorted order to test sorting
(migrations_dir / "002_second.sql").write_text(
"CREATE TABLE IF NOT EXISTS second_table (id INTEGER);"
)
(migrations_dir / "001_first.sql").write_text(
"CREATE TABLE IF NOT EXISTS first_table (id INTEGER);"
)
# Create test database
db_path = tmp_path / "test.db"
# Execute migrations (should be sorted automatically)
conn = sqlite3.connect(db_path)
try:
runner = MigrationRunner(migrations_dir)
success, message = runner.run_migrations_sync(
conn, ["002_second.sql", "001_first.sql"]
)
# Verify success
assert success
assert "Successfully executed 2 migrations" in message
# Verify both tables were created
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
)
results = cursor.fetchall()
table_names = [row[0] for row in results]
assert "first_table" in table_names
assert "second_table" in table_names
finally:
conn.close()
@pytest.mark.asyncio
async def test_migration_runner_async_multiple_files(tmp_path):
"""Test that migration runner executes multiple files in order asynchronously."""
# Setup: Create multiple migration files
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
(migrations_dir / "002_second.sql").write_text(
"CREATE TABLE IF NOT EXISTS second_table (id INTEGER);"
)
(migrations_dir / "001_first.sql").write_text(
"CREATE TABLE IF NOT EXISTS first_table (id INTEGER);"
)
# Create test database
db_path = tmp_path / "test.db"
# Execute migrations
runner = MigrationRunner(migrations_dir)
success, message = await runner.run_migrations_async(
str(db_path), ["002_second.sql", "001_first.sql"]
)
# Verify success
assert success
assert "Successfully executed 2 migrations" in message
# Verify both tables were created
async with aiosqlite.connect(db_path) as conn:
cursor = await conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
)
results = await cursor.fetchall()
table_names = [row[0] for row in results]
assert "first_table" in table_names
assert "second_table" in table_names
@pytest.mark.unit
def test_migration_runner_sync_missing_file(tmp_path):
"""Test that migration runner handles missing files gracefully."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create only one migration file
(migrations_dir / "001_exists.sql").write_text(
"CREATE TABLE IF NOT EXISTS exists_table (id INTEGER);"
)
# Try to run with a missing file
db_path = tmp_path / "test.db"
conn = sqlite3.connect(db_path)
try:
runner = MigrationRunner(migrations_dir)
success, message = runner.run_migrations_sync(
conn, ["001_exists.sql", "999_missing.sql"]
)
# Should succeed but skip missing file
assert success
assert "Successfully executed 1 migrations" in message
# Verify existing table was created
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='exists_table'"
)
result = cursor.fetchone()
assert result is not None
finally:
conn.close()
@pytest.mark.asyncio
async def test_migration_runner_async_missing_file(tmp_path):
"""Test that migration runner handles missing files gracefully (async)."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create only one migration file
(migrations_dir / "001_exists.sql").write_text(
"CREATE TABLE IF NOT EXISTS exists_table (id INTEGER);"
)
# Try to run with a missing file
db_path = tmp_path / "test.db"
runner = MigrationRunner(migrations_dir)
success, message = await runner.run_migrations_async(
str(db_path), ["001_exists.sql", "999_missing.sql"]
)
# Should succeed but skip missing file
assert success
assert "Successfully executed 1 migrations" in message
# Verify existing table was created
async with aiosqlite.connect(db_path) as conn:
cursor = await conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='exists_table'"
)
result = await cursor.fetchone()
assert result is not None
@pytest.mark.unit
def test_migration_runner_sync_invalid_sql(tmp_path):
"""Test that migration runner handles invalid SQL gracefully."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create migration with invalid SQL
(migrations_dir / "001_invalid.sql").write_text("INVALID SQL STATEMENT;")
db_path = tmp_path / "test.db"
conn = sqlite3.connect(db_path)
try:
runner = MigrationRunner(migrations_dir)
success, message = runner.run_migrations_sync(conn, ["001_invalid.sql"])
# Should fail
assert not success
assert "Migration error" in message
finally:
conn.close()
@pytest.mark.asyncio
async def test_migration_runner_async_invalid_sql(tmp_path):
"""Test that migration runner handles invalid SQL gracefully (async)."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create migration with invalid SQL
(migrations_dir / "001_invalid.sql").write_text("INVALID SQL STATEMENT;")
db_path = tmp_path / "test.db"
runner = MigrationRunner(migrations_dir)
success, message = await runner.run_migrations_async(str(db_path), ["001_invalid.sql"])
# Should fail
assert not success
assert "Migration error" in message
@pytest.mark.unit
def test_migration_runner_sync_idempotent(tmp_path):
"""Test that migrations are idempotent (can run multiple times)."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create idempotent migration with IF NOT EXISTS
(migrations_dir / "001_idempotent.sql").write_text(
"CREATE TABLE IF NOT EXISTS idempotent_table (id INTEGER PRIMARY KEY);"
)
db_path = tmp_path / "test.db"
conn = sqlite3.connect(db_path)
try:
runner = MigrationRunner(migrations_dir)
# Run migration first time
success1, message1 = runner.run_migrations_sync(conn, ["001_idempotent.sql"])
assert success1
assert "Successfully executed 1 migrations" in message1
# Run migration second time (should not fail)
success2, message2 = runner.run_migrations_sync(conn, ["001_idempotent.sql"])
assert success2
assert "Successfully executed 1 migrations" in message2
# Verify table exists
cursor = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='idempotent_table'"
)
result = cursor.fetchone()
assert result is not None
finally:
conn.close()
@pytest.mark.asyncio
async def test_migration_runner_async_idempotent(tmp_path):
"""Test that migrations are idempotent (can run multiple times) async."""
migrations_dir = tmp_path / "migrations"
migrations_dir.mkdir()
# Create idempotent migration with IF NOT EXISTS
(migrations_dir / "001_idempotent.sql").write_text(
"CREATE TABLE IF NOT EXISTS idempotent_table (id INTEGER PRIMARY KEY);"
)
db_path = tmp_path / "test.db"
runner = MigrationRunner(migrations_dir)
# Run migration first time
success1, message1 = await runner.run_migrations_async(
str(db_path), ["001_idempotent.sql"]
)
assert success1
assert "Successfully executed 1 migrations" in message1
# Run migration second time (should not fail)
success2, message2 = await runner.run_migrations_async(
str(db_path), ["001_idempotent.sql"]
)
assert success2
assert "Successfully executed 1 migrations" in message2
# Verify table exists
async with aiosqlite.connect(db_path) as conn:
cursor = await conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='idempotent_table'"
)
result = await cursor.fetchone()
assert result is not None