Skip to main content
Glama
migration-process.test.ts14.5 kB
/** * Migration Process Integration Tests * * Tests the complete migration lifecycle including: * - Fresh database setup * - Migration from empty to current schema * - Rollback scenarios * - Migration with existing data */ import type { PoolClient } from "pg"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { DatabaseConnectionManager } from "../../database/connection-manager"; import { SchemaMigrationSystem } from "../../database/schema-migration"; describe("Migration Process Integration", () => { let adminDbManager: DatabaseConnectionManager; let testDbManager: DatabaseConnectionManager; const testDbName = `test_migration_process_${Date.now()}`; beforeAll(async () => { // Connect to default postgres database to create test database adminDbManager = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: "postgres", user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", }); await adminDbManager.connect(); const adminClient = await adminDbManager.getConnection(); try { // Create fresh test database await adminClient.query(`DROP DATABASE IF EXISTS ${testDbName}`); await adminClient.query(`CREATE DATABASE ${testDbName}`); } finally { adminDbManager.releaseConnection(adminClient); } // Connect to test database testDbManager = new DatabaseConnectionManager({ host: process.env.DB_HOST || "localhost", port: parseInt(process.env.DB_PORT || "5432"), database: testDbName, user: process.env.DB_USER || "postgres", password: process.env.DB_PASSWORD || "postgres", }); await testDbManager.connect(); }); afterAll(async () => { // Disconnect from test database if (testDbManager) { await testDbManager.disconnect(); } // Drop test database if (adminDbManager) { const adminClient = await adminDbManager.getConnection(); try { await adminClient.query(`DROP DATABASE IF EXISTS ${testDbName}`); } finally { adminDbManager.releaseConnection(adminClient); await adminDbManager.disconnect(); } } }, 60000); // 60 second timeout for cleanup describe("Fresh Database Setup", () => { it("should start with version 0 on empty database", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(0); }); it("should create schema_migrations table on first access", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT table_name FROM information_schema.tables WHERE table_name = 'schema_migrations' `); expect(result.rows.length).toBe(1); } finally { testDbManager.releaseConnection(client); } }); }); describe("Migration from Empty to Current Schema", () => { it("should apply all migrations successfully", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Run all migrations await migrationSystem.runMigrations(); // Verify we're at latest version (4) const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(4); }); it("should create all required tables", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('memories', 'memory_embeddings', 'memory_links', 'memory_metadata', 'memory_emotions') ORDER BY table_name `); expect(result.rows.length).toBe(5); expect(result.rows.map((r) => r.table_name)).toEqual([ "memories", "memory_embeddings", "memory_emotions", "memory_links", "memory_metadata", ]); } finally { testDbManager.releaseConnection(client); } }); it("should enable pgvector extension", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT * FROM pg_extension WHERE extname = 'vector' `); expect(result.rows.length).toBe(1); } finally { testDbManager.releaseConnection(client); } }); it("should create all required indexes", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT indexname FROM pg_indexes WHERE schemaname = 'public' AND indexname LIKE 'idx_%' ORDER BY indexname `); // Should have indexes for memories, embeddings, links, metadata expect(result.rows.length).toBeGreaterThan(10); const indexNames = result.rows.map((r) => r.indexname); expect(indexNames).toContain("idx_memories_user"); expect(indexNames).toContain("idx_memories_created"); expect(indexNames).toContain("idx_embeddings_vector"); expect(indexNames).toContain("idx_metadata_keywords"); } finally { testDbManager.releaseConnection(client); } }); it("should track all applied migrations", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT version, name FROM schema_migrations ORDER BY version `); expect(result.rows.length).toBe(4); expect(result.rows[0].version).toBe(1); expect(result.rows[0].name).toBe("initial_schema"); expect(result.rows[3].version).toBe(4); expect(result.rows[3].name).toBe("full_text_search"); } finally { testDbManager.releaseConnection(client); } }); }); describe("Rollback Scenarios", () => { it("should rollback to version 2 successfully", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Rollback to version 2 await migrationSystem.rollbackMigration(2); // Verify version const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(2); }); it("should remove rolled back migrations from tracking", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT version FROM schema_migrations ORDER BY version `); expect(result.rows.length).toBe(2); expect(result.rows.map((r) => r.version)).toEqual([1, 2]); } finally { testDbManager.releaseConnection(client); } }); it("should remove full-text search column after rollback", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT column_name FROM information_schema.columns WHERE table_name = 'memories' AND column_name = 'search_vector' `); // search_vector should be removed after rolling back migration 4 expect(result.rows.length).toBe(0); } finally { testDbManager.releaseConnection(client); } }); it("should re-apply migrations after rollback", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Re-apply all migrations await migrationSystem.runMigrations(); // Verify we're back at latest version const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(4); }); it("should rollback to version 0 (empty schema)", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Rollback all migrations await migrationSystem.rollbackMigration(0); // Verify version const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(0); }); it("should remove all tables after full rollback", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('memories', 'memory_embeddings', 'memory_links', 'memory_metadata', 'memory_emotions') `); expect(result.rows.length).toBe(0); } finally { testDbManager.releaseConnection(client); } }); }); describe("Migration with Existing Data", () => { let client: PoolClient; beforeAll(async () => { // Re-apply migrations first const migrationSystem = new SchemaMigrationSystem(testDbManager); await migrationSystem.runMigrations(); }); it("should preserve data during migration", async () => { client = await testDbManager.getConnection(); try { // Insert test data await client.query(` INSERT INTO memories (id, content, user_id, primary_sector) VALUES ('test-memory-1', 'Test content for migration', 'user-1', 'semantic') `); // Verify data exists const result = await client.query(`SELECT * FROM memories WHERE id = 'test-memory-1'`); expect(result.rows.length).toBe(1); expect(result.rows[0].content).toBe("Test content for migration"); } finally { testDbManager.releaseConnection(client); } }); it("should maintain data integrity after rollback and re-migration", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Rollback to version 3 (before full-text search) await migrationSystem.rollbackMigration(3); // Re-apply migration 4 await migrationSystem.runMigrations(); // Verify data still exists client = await testDbManager.getConnection(); try { const result = await client.query(`SELECT * FROM memories WHERE id = 'test-memory-1'`); expect(result.rows.length).toBe(1); expect(result.rows[0].content).toBe("Test content for migration"); } finally { testDbManager.releaseConnection(client); } }); it("should update search_vector for existing data after migration", async () => { client = await testDbManager.getConnection(); try { // Migration 4 should have updated search_vector for existing rows const result = await client.query(` SELECT search_vector FROM memories WHERE id = 'test-memory-1' `); expect(result.rows.length).toBe(1); expect(result.rows[0].search_vector).not.toBeNull(); } finally { testDbManager.releaseConnection(client); } }); it("should handle cascade deletes correctly", async () => { client = await testDbManager.getConnection(); try { // Insert related data await client.query(` INSERT INTO memory_metadata (memory_id, keywords, tags, importance) VALUES ('test-memory-1', ARRAY['test', 'migration'], ARRAY['integration'], 0.8) `); // Delete parent memory await client.query(`DELETE FROM memories WHERE id = 'test-memory-1'`); // Verify cascade delete worked const metadataResult = await client.query(` SELECT * FROM memory_metadata WHERE memory_id = 'test-memory-1' `); expect(metadataResult.rows.length).toBe(0); } finally { testDbManager.releaseConnection(client); } }); }); describe("Idempotency", () => { it("should be safe to run migrations multiple times", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Run migrations multiple times await migrationSystem.runMigrations(); await migrationSystem.runMigrations(); await migrationSystem.runMigrations(); // Should still be at version 4 const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(4); }); it("should not duplicate migration records", async () => { const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT version, COUNT(*) as count FROM schema_migrations GROUP BY version HAVING COUNT(*) > 1 `); // No duplicates should exist expect(result.rows.length).toBe(0); } finally { testDbManager.releaseConnection(client); } }); }); describe("Error Handling", () => { it("should rollback transaction on migration failure", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager, "/nonexistent/path"); // Rollback to version 0 first await new SchemaMigrationSystem(testDbManager).rollbackMigration(0); // Try to run migrations with invalid path (should fail) await expect(migrationSystem.runMigrations()).rejects.toThrow(); // Verify database is still at version 0 (transaction was rolled back) const versionAfter = await new SchemaMigrationSystem(testDbManager).getCurrentVersion(); expect(versionAfter).toBe(0); // Restore migrations for other tests await new SchemaMigrationSystem(testDbManager).runMigrations(); }); }); describe("Database Reset", () => { it("should reset database to clean state", async () => { const migrationSystem = new SchemaMigrationSystem(testDbManager); // Reset database await migrationSystem.resetDatabase(); // Verify we're at latest version const version = await migrationSystem.getCurrentVersion(); expect(version).toBe(4); // Verify all tables exist const client = await testDbManager.getConnection(); try { const result = await client.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('memories', 'memory_embeddings', 'memory_links', 'memory_metadata', 'memory_emotions') `); expect(result.rows.length).toBe(5); } finally { testDbManager.releaseConnection(client); } }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server