Spaces:
Sleeping
Sleeping
| import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; | |
| import * as fs from 'fs'; | |
| import * as path from 'path'; | |
| import { | |
| createTestDatabase, | |
| seedTestNodes, | |
| seedTestTemplates, | |
| createTestNode, | |
| createTestTemplate, | |
| resetDatabase, | |
| createDatabaseSnapshot, | |
| restoreDatabaseSnapshot, | |
| loadFixtures, | |
| dbHelpers, | |
| createMockDatabaseAdapter, | |
| withTransaction, | |
| measureDatabaseOperation, | |
| TestDatabase | |
| } from '../../utils/database-utils'; | |
| describe('Database Utils', () => { | |
| let testDb: TestDatabase; | |
| afterEach(async () => { | |
| if (testDb) { | |
| await testDb.cleanup(); | |
| } | |
| }); | |
| describe('createTestDatabase', () => { | |
| it('should create an in-memory database by default', async () => { | |
| testDb = await createTestDatabase(); | |
| expect(testDb.adapter).toBeDefined(); | |
| expect(testDb.nodeRepository).toBeDefined(); | |
| expect(testDb.templateRepository).toBeDefined(); | |
| expect(testDb.path).toBe(':memory:'); | |
| }); | |
| it('should create a file-based database when requested', async () => { | |
| const dbPath = path.join(__dirname, '../../temp/test-file.db'); | |
| testDb = await createTestDatabase({ inMemory: false, dbPath }); | |
| expect(testDb.path).toBe(dbPath); | |
| expect(fs.existsSync(dbPath)).toBe(true); | |
| }); | |
| it('should initialize schema when requested', async () => { | |
| testDb = await createTestDatabase({ initSchema: true }); | |
| // Verify tables exist | |
| const tables = testDb.adapter | |
| .prepare("SELECT name FROM sqlite_master WHERE type='table'") | |
| .all() as { name: string }[]; | |
| const tableNames = tables.map(t => t.name); | |
| expect(tableNames).toContain('nodes'); | |
| expect(tableNames).toContain('templates'); | |
| }); | |
| it('should skip schema initialization when requested', async () => { | |
| testDb = await createTestDatabase({ initSchema: false }); | |
| // Verify tables don't exist (SQLite has internal tables, so check for our specific tables) | |
| const tables = testDb.adapter | |
| .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('nodes', 'templates')") | |
| .all() as { name: string }[]; | |
| expect(tables.length).toBe(0); | |
| }); | |
| }); | |
| describe('seedTestNodes', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should seed default test nodes', async () => { | |
| const nodes = await seedTestNodes(testDb.nodeRepository); | |
| expect(nodes).toHaveLength(3); | |
| expect(nodes[0].nodeType).toBe('nodes-base.httpRequest'); | |
| expect(nodes[1].nodeType).toBe('nodes-base.webhook'); | |
| expect(nodes[2].nodeType).toBe('nodes-base.slack'); | |
| }); | |
| it('should seed custom nodes along with defaults', async () => { | |
| const customNodes = [ | |
| { nodeType: 'nodes-base.custom1', displayName: 'Custom 1' }, | |
| { nodeType: 'nodes-base.custom2', displayName: 'Custom 2' } | |
| ]; | |
| const nodes = await seedTestNodes(testDb.nodeRepository, customNodes); | |
| expect(nodes).toHaveLength(5); // 3 default + 2 custom | |
| expect(nodes[3].nodeType).toBe('nodes-base.custom1'); | |
| expect(nodes[4].nodeType).toBe('nodes-base.custom2'); | |
| }); | |
| it('should save nodes to database', async () => { | |
| await seedTestNodes(testDb.nodeRepository); | |
| const count = dbHelpers.countRows(testDb.adapter, 'nodes'); | |
| expect(count).toBe(3); | |
| const httpNode = testDb.nodeRepository.getNode('nodes-base.httpRequest'); | |
| expect(httpNode).toBeDefined(); | |
| expect(httpNode.displayName).toBe('HTTP Request'); | |
| }); | |
| }); | |
| describe('seedTestTemplates', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should seed default test templates', async () => { | |
| const templates = await seedTestTemplates(testDb.templateRepository); | |
| expect(templates).toHaveLength(2); | |
| expect(templates[0].name).toBe('Simple HTTP Workflow'); | |
| expect(templates[1].name).toBe('Webhook to Slack'); | |
| }); | |
| it('should seed custom templates', async () => { | |
| const customTemplates = [ | |
| { id: 100, name: 'Custom Template' } | |
| ]; | |
| const templates = await seedTestTemplates(testDb.templateRepository, customTemplates); | |
| expect(templates).toHaveLength(3); | |
| expect(templates[2].id).toBe(100); | |
| expect(templates[2].name).toBe('Custom Template'); | |
| }); | |
| }); | |
| describe('createTestNode', () => { | |
| it('should create a node with defaults', () => { | |
| const node = createTestNode(); | |
| expect(node.nodeType).toBe('nodes-base.test'); | |
| expect(node.displayName).toBe('Test Node'); | |
| expect(node.style).toBe('programmatic'); | |
| expect(node.isAITool).toBe(false); | |
| }); | |
| it('should override defaults', () => { | |
| const node = createTestNode({ | |
| nodeType: 'nodes-base.custom', | |
| displayName: 'Custom Node', | |
| isAITool: true | |
| }); | |
| expect(node.nodeType).toBe('nodes-base.custom'); | |
| expect(node.displayName).toBe('Custom Node'); | |
| expect(node.isAITool).toBe(true); | |
| }); | |
| }); | |
| describe('resetDatabase', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should clear all data and reinitialize schema', async () => { | |
| // Add some data | |
| await seedTestNodes(testDb.nodeRepository); | |
| await seedTestTemplates(testDb.templateRepository); | |
| // Verify data exists | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(3); | |
| expect(dbHelpers.countRows(testDb.adapter, 'templates')).toBe(2); | |
| // Reset database | |
| await resetDatabase(testDb.adapter); | |
| // Verify data is cleared | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(0); | |
| expect(dbHelpers.countRows(testDb.adapter, 'templates')).toBe(0); | |
| // Verify tables still exist | |
| const tables = testDb.adapter | |
| .prepare("SELECT name FROM sqlite_master WHERE type='table'") | |
| .all() as { name: string }[]; | |
| const tableNames = tables.map(t => t.name); | |
| expect(tableNames).toContain('nodes'); | |
| expect(tableNames).toContain('templates'); | |
| }); | |
| }); | |
| describe('Database Snapshots', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should create and restore database snapshot', async () => { | |
| // Seed initial data | |
| await seedTestNodes(testDb.nodeRepository); | |
| await seedTestTemplates(testDb.templateRepository); | |
| // Create snapshot | |
| const snapshot = await createDatabaseSnapshot(testDb.adapter); | |
| expect(snapshot.metadata.nodeCount).toBe(3); | |
| expect(snapshot.metadata.templateCount).toBe(2); | |
| expect(snapshot.nodes).toHaveLength(3); | |
| expect(snapshot.templates).toHaveLength(2); | |
| // Clear database | |
| await resetDatabase(testDb.adapter); | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(0); | |
| // Restore from snapshot | |
| await restoreDatabaseSnapshot(testDb.adapter, snapshot); | |
| // Verify data is restored | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(3); | |
| expect(dbHelpers.countRows(testDb.adapter, 'templates')).toBe(2); | |
| const httpNode = testDb.nodeRepository.getNode('nodes-base.httpRequest'); | |
| expect(httpNode).toBeDefined(); | |
| expect(httpNode.displayName).toBe('HTTP Request'); | |
| }); | |
| }); | |
| describe('loadFixtures', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should load fixtures from JSON file', async () => { | |
| // Create a temporary fixture file | |
| const fixturePath = path.join(__dirname, '../../temp/test-fixtures.json'); | |
| const fixtures = { | |
| nodes: [ | |
| createTestNode({ nodeType: 'nodes-base.fixture1' }), | |
| createTestNode({ nodeType: 'nodes-base.fixture2' }) | |
| ], | |
| templates: [ | |
| createTestTemplate({ id: 1000, name: 'Fixture Template' }) | |
| ] | |
| }; | |
| // Ensure directory exists | |
| const dir = path.dirname(fixturePath); | |
| if (!fs.existsSync(dir)) { | |
| fs.mkdirSync(dir, { recursive: true }); | |
| } | |
| fs.writeFileSync(fixturePath, JSON.stringify(fixtures, null, 2)); | |
| // Load fixtures | |
| await loadFixtures(testDb.adapter, fixturePath); | |
| // Verify data was loaded | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(2); | |
| expect(dbHelpers.countRows(testDb.adapter, 'templates')).toBe(1); | |
| expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.fixture1')).toBe(true); | |
| expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.fixture2')).toBe(true); | |
| // Cleanup | |
| fs.unlinkSync(fixturePath); | |
| }); | |
| }); | |
| describe('dbHelpers', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| await seedTestNodes(testDb.nodeRepository); | |
| }); | |
| it('should count rows correctly', () => { | |
| const count = dbHelpers.countRows(testDb.adapter, 'nodes'); | |
| expect(count).toBe(3); | |
| }); | |
| it('should check if node exists', () => { | |
| expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.httpRequest')).toBe(true); | |
| expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.nonexistent')).toBe(false); | |
| }); | |
| it('should get all node types', () => { | |
| const nodeTypes = dbHelpers.getAllNodeTypes(testDb.adapter); | |
| expect(nodeTypes).toHaveLength(3); | |
| expect(nodeTypes).toContain('nodes-base.httpRequest'); | |
| expect(nodeTypes).toContain('nodes-base.webhook'); | |
| expect(nodeTypes).toContain('nodes-base.slack'); | |
| }); | |
| it('should clear table', () => { | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(3); | |
| dbHelpers.clearTable(testDb.adapter, 'nodes'); | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(0); | |
| }); | |
| }); | |
| describe('createMockDatabaseAdapter', () => { | |
| it('should create a mock adapter with all required methods', () => { | |
| const mockAdapter = createMockDatabaseAdapter(); | |
| expect(mockAdapter.prepare).toBeDefined(); | |
| expect(mockAdapter.exec).toBeDefined(); | |
| expect(mockAdapter.close).toBeDefined(); | |
| expect(mockAdapter.pragma).toBeDefined(); | |
| expect(mockAdapter.transaction).toBeDefined(); | |
| expect(mockAdapter.checkFTS5Support).toBeDefined(); | |
| // Test that methods are mocked | |
| expect(vi.isMockFunction(mockAdapter.prepare)).toBe(true); | |
| expect(vi.isMockFunction(mockAdapter.exec)).toBe(true); | |
| }); | |
| }); | |
| describe('withTransaction', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should rollback transaction for testing', async () => { | |
| // Insert a node | |
| await seedTestNodes(testDb.nodeRepository, [ | |
| { nodeType: 'nodes-base.transaction-test' } | |
| ]); | |
| const initialCount = dbHelpers.countRows(testDb.adapter, 'nodes'); | |
| // Try to insert in a transaction that will rollback | |
| const result = await withTransaction(testDb.adapter, async () => { | |
| testDb.nodeRepository.saveNode(createTestNode({ | |
| nodeType: 'nodes-base.should-rollback' | |
| })); | |
| // Verify it was inserted within transaction | |
| const midCount = dbHelpers.countRows(testDb.adapter, 'nodes'); | |
| expect(midCount).toBe(initialCount + 1); | |
| return 'test-result'; | |
| }); | |
| // Transaction should have rolled back | |
| expect(result).toBeNull(); | |
| const finalCount = dbHelpers.countRows(testDb.adapter, 'nodes'); | |
| expect(finalCount).toBe(initialCount); | |
| }); | |
| }); | |
| describe('measureDatabaseOperation', () => { | |
| beforeEach(async () => { | |
| testDb = await createTestDatabase(); | |
| }); | |
| it('should measure operation duration', async () => { | |
| const duration = await measureDatabaseOperation('test operation', async () => { | |
| await seedTestNodes(testDb.nodeRepository); | |
| // Add a small delay to ensure measurable time passes | |
| await new Promise(resolve => setTimeout(resolve, 1)); | |
| }); | |
| expect(duration).toBeGreaterThanOrEqual(0); | |
| expect(duration).toBeLessThan(1000); // Should be fast | |
| }); | |
| }); | |
| describe('Integration Tests', () => { | |
| it('should handle complex database operations', async () => { | |
| testDb = await createTestDatabase({ enableFTS5: true }); | |
| // Seed initial data | |
| const nodes = await seedTestNodes(testDb.nodeRepository); | |
| const templates = await seedTestTemplates(testDb.templateRepository); | |
| // Create snapshot | |
| const snapshot = await createDatabaseSnapshot(testDb.adapter); | |
| // Add more data | |
| await seedTestNodes(testDb.nodeRepository, [ | |
| { nodeType: 'nodes-base.extra1' }, | |
| { nodeType: 'nodes-base.extra2' } | |
| ]); | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(5); | |
| // Restore snapshot | |
| await restoreDatabaseSnapshot(testDb.adapter, snapshot); | |
| // Should be back to original state | |
| expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(3); | |
| // Test FTS5 if supported | |
| if (testDb.adapter.checkFTS5Support()) { | |
| // FTS5 operations would go here | |
| expect(true).toBe(true); | |
| } | |
| }); | |
| }); | |
| }); |