const database = require('../models/database'); const fs = require('fs'); const path = require('path'); describe('Database Optimization and Performance Tests', () => { let testDbPath; beforeAll(async () => { // Set up test database testDbPath = path.join(__dirname, '..', 'test_optimization.db'); if (fs.existsSync(testDbPath)) { fs.unlinkSync(testDbPath); } database.dbPath = testDbPath; await database.initialize(); }); afterAll(async () => { database.close(); if (fs.existsSync(testDbPath)) { fs.unlinkSync(testDbPath); } }); beforeEach(async () => { // Clean up database before each test const db = database.getDatabase(); db.exec('DELETE FROM inventory_history'); db.exec('DELETE FROM inventory'); db.exec('DELETE FROM products'); db.exec('DELETE FROM import_sessions'); }); describe('Database Indexing and Query Optimization', () => { test('should have proper indexes created', async () => { const db = database.getDatabase(); // Check that indexes exist const indexes = db.prepare(` SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' ORDER BY name `).all(); expect(indexes.length).toBeGreaterThan(10); // Should have many indexes // Check for specific critical indexes const indexNames = indexes.map(idx => idx.name); expect(indexNames).toContain('idx_products_product_code'); expect(indexNames).toContain('idx_inventory_product_id'); expect(indexNames).toContain('idx_inventory_history_product_id'); expect(indexNames).toContain('idx_inventory_low_stock'); }); test('should perform fast queries with large dataset', async () => { const db = database.getDatabase(); // Create large dataset const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category, unit_of_measure) VALUES (?, ?, ?, ?) `); const insertInventory = db.prepare(` INSERT INTO inventory (product_id, current_level, minimum_level, maximum_level) VALUES (?, ?, ?, ?) `); console.log('Creating large dataset for query optimization tests...'); const transaction = db.transaction(() => { for (let i = 1; i <= 1000; i++) { const result = insertProduct.run( `OPT${i.toString().padStart(6, '0')}`, `Optimization Test Product ${i}`, `Category${i % 20}`, 'pcs' ); insertInventory.run( result.lastInsertRowid, Math.floor(Math.random() * 1000) + 1, 10, 500 ); } }); transaction(); // Test query performance const queries = [ { name: 'Product lookup by code', query: 'SELECT * FROM products WHERE product_code = ?', params: ['OPT000500'] }, { name: 'Category filter', query: 'SELECT * FROM products WHERE category = ?', params: ['Category5'] }, { name: 'Low stock query', query: ` SELECT p.*, i.current_level, i.minimum_level FROM products p INNER JOIN inventory i ON p.id = i.product_id WHERE i.current_level <= i.minimum_level `, params: [] }, { name: 'Inventory summary', query: ` SELECT p.product_code, p.description, i.current_level FROM products p INNER JOIN inventory i ON p.id = i.product_id ORDER BY p.product_code LIMIT 100 `, params: [] } ]; for (const queryTest of queries) { const startTime = Date.now(); const stmt = db.prepare(queryTest.query); const results = queryTest.params.length > 0 ? stmt.all(...queryTest.params) : stmt.all(); const duration = Date.now() - startTime; console.log(`${queryTest.name}: ${duration}ms (${results.length} results)`); expect(duration).toBeLessThan(100); // Should be very fast with proper indexes } }); test('should handle concurrent reads efficiently', async () => { const db = database.getDatabase(); // Create test data const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category) VALUES (?, ?, ?) `); for (let i = 1; i <= 100; i++) { insertProduct.run(`CONCURRENT${i}`, `Product ${i}`, 'Test'); } // Perform concurrent reads const concurrentReads = 50; const promises = []; const startTime = Date.now(); for (let i = 0; i < concurrentReads; i++) { const promise = new Promise((resolve) => { const stmt = db.prepare('SELECT * FROM products WHERE category = ?'); const results = stmt.all('Test'); resolve(results.length); }); promises.push(promise); } const results = await Promise.all(promises); const duration = Date.now() - startTime; console.log(`${concurrentReads} concurrent reads completed in ${duration}ms`); expect(duration).toBeLessThan(1000); // Should complete quickly expect(results.every(count => count === 100)).toBe(true); // All should return same count }); }); describe('Database Statistics and Analysis', () => { test('should provide database statistics', () => { const stats = database.getStats(); expect(stats.isInitialized).toBe(true); expect(stats.dbPath).toBe(testDbPath); expect(stats.pragmas).toBeDefined(); expect(stats.tables).toBeDefined(); expect(stats.indexes).toBeDefined(); // Check pragma settings expect(stats.pragmas.journalMode).toBe('wal'); expect(stats.pragmas.synchronous).toBe(1); // NORMAL expect(stats.pragmas.cacheSize).toBeGreaterThanOrEqual(1000); }); test('should analyze performance and provide recommendations', () => { const analysis = database.analyzePerformance(); expect(analysis.timestamp).toBeDefined(); expect(Array.isArray(analysis.recommendations)).toBe(true); console.log('Performance analysis:', analysis); }); test('should get table statistics', () => { const stats = database.getTableStats(); expect(stats.products).toBeDefined(); expect(stats.inventory).toBeDefined(); expect(stats.inventory_history).toBeDefined(); expect(stats.import_sessions).toBeDefined(); // All tables should have row count Object.values(stats).forEach(tableStat => { if (!tableStat.error) { expect(typeof tableStat.rowCount).toBe('number'); } }); }); test('should get index statistics', () => { const indexes = database.getIndexStats(); expect(Array.isArray(indexes)).toBe(true); expect(indexes.length).toBeGreaterThan(0); indexes.forEach(index => { expect(index.name).toBeDefined(); expect(index.table).toBeDefined(); expect(index.definition).toBeDefined(); }); }); }); describe('Database Optimization Operations', () => { test('should optimize database successfully', async () => { // Add some data first const db = database.getDatabase(); const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category) VALUES (?, ?, ?) `); for (let i = 1; i <= 100; i++) { insertProduct.run(`OPTIMIZE${i}`, `Product ${i}`, 'Test'); } const result = await database.optimize(); expect(result.success).toBe(true); expect(result.duration).toBeDefined(); expect(result.results).toBeDefined(); expect(result.results.vacuum).toBe(true); expect(result.results.analyze).toBe(true); expect(result.results.reindex).toBe(true); console.log('Database optimization completed:', result); }); test('should prepare optimized statements', () => { const statements = database.prepareOptimizedStatements(); expect(statements).toBeDefined(); expect(statements.findProductByCode).toBeDefined(); expect(statements.getInventorySummary).toBeDefined(); expect(statements.getLowStockItems).toBeDefined(); expect(statements.updateInventoryLevel).toBeDefined(); // Test using a prepared statement const db = database.getDatabase(); const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category) VALUES (?, ?, ?) `); insertProduct.run('TEST001', 'Test Product', 'Test'); const result = statements.findProductByCode.get('TEST001'); expect(result).toBeDefined(); expect(result.product_code).toBe('TEST001'); }); test('should get prepared statement by name', () => { const statement = database.getPreparedStatement('findProductByCode'); expect(statement).toBeDefined(); // Test error for non-existent statement expect(() => { database.getPreparedStatement('nonExistentStatement'); }).toThrow('Prepared statement \'nonExistentStatement\' not found'); }); }); describe('Performance Benchmarks', () => { test('should handle large batch inserts efficiently', async () => { const db = database.getDatabase(); const batchSize = 1000; const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category, unit_of_measure) VALUES (?, ?, ?, ?) `); const startTime = Date.now(); const transaction = db.transaction(() => { for (let i = 1; i <= batchSize; i++) { insertProduct.run( `BATCH${i.toString().padStart(6, '0')}`, `Batch Product ${i}`, `Category${i % 10}`, 'pcs' ); } }); transaction(); const duration = Date.now() - startTime; console.log(`Batch insert of ${batchSize} products: ${duration}ms`); expect(duration).toBeLessThan(5000); // Should complete within 5 seconds expect(duration / batchSize).toBeLessThan(5); // Less than 5ms per insert }); test('should handle complex queries efficiently', async () => { const db = database.getDatabase(); // Create test data with relationships const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category, unit_of_measure) VALUES (?, ?, ?, ?) `); const insertInventory = db.prepare(` INSERT INTO inventory (product_id, current_level, minimum_level, maximum_level) VALUES (?, ?, ?, ?) `); const insertHistory = db.prepare(` INSERT INTO inventory_history (product_id, old_level, new_level, change_reason, updated_by) VALUES (?, ?, ?, ?, ?) `); // Create complex dataset const transaction = db.transaction(() => { for (let i = 1; i <= 500; i++) { const result = insertProduct.run( `COMPLEX${i.toString().padStart(6, '0')}`, `Complex Product ${i}`, `Category${i % 15}`, 'pcs' ); insertInventory.run( result.lastInsertRowid, Math.floor(Math.random() * 100) + 1, 10, 200 ); // Add some history records for (let j = 0; j < 3; j++) { insertHistory.run( result.lastInsertRowid, Math.floor(Math.random() * 50), Math.floor(Math.random() * 100) + 1, `Test update ${j}`, 'test-user' ); } } }); transaction(); // Test complex queries const complexQueries = [ { name: 'Multi-table join with aggregation', query: ` SELECT p.category, COUNT(*) as product_count, AVG(i.current_level) as avg_level, SUM(i.current_level) as total_inventory FROM products p INNER JOIN inventory i ON p.id = i.product_id GROUP BY p.category ORDER BY total_inventory DESC ` }, { name: 'History analysis with window functions', query: ` SELECT p.product_code, ih.new_level, ih.updated_at, ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY ih.updated_at DESC) as rn FROM products p INNER JOIN inventory_history ih ON p.id = ih.product_id WHERE p.category = 'Category5' ORDER BY ih.updated_at DESC LIMIT 50 ` } ]; for (const queryTest of complexQueries) { const startTime = Date.now(); const stmt = db.prepare(queryTest.query); const results = stmt.all(); const duration = Date.now() - startTime; console.log(`${queryTest.name}: ${duration}ms (${results.length} results)`); expect(duration).toBeLessThan(500); // Complex queries should still be fast } }); }); describe('Concurrent Access and Locking', () => { test('should handle concurrent writes with proper locking', async () => { const db = database.getDatabase(); // Create a test product const insertProduct = db.prepare(` INSERT INTO products (product_code, description, category) VALUES (?, ?, ?) `); const result = insertProduct.run('LOCK_TEST', 'Lock Test Product', 'Test'); const productId = result.lastInsertRowid; // Create inventory record const insertInventory = db.prepare(` INSERT INTO inventory (product_id, current_level, minimum_level, maximum_level, version) VALUES (?, ?, ?, ?, ?) `); insertInventory.run(productId, 100, 10, 200, 1); // Simulate concurrent updates const concurrentUpdates = 10; const promises = []; for (let i = 0; i < concurrentUpdates; i++) { const promise = new Promise((resolve, reject) => { try { // Simulate optimistic locking const selectStmt = db.prepare('SELECT version FROM inventory WHERE product_id = ?'); const currentVersion = selectStmt.get(productId)?.version || 1; const updateStmt = db.prepare(` UPDATE inventory SET current_level = ?, version = version + 1 WHERE product_id = ? AND version = ? `); const updateResult = updateStmt.run(100 + i, productId, currentVersion); resolve(updateResult.changes > 0); } catch (error) { reject(error); } }); promises.push(promise); } const results = await Promise.allSettled(promises); const successful = results.filter(r => r.status === 'fulfilled' && r.value === true).length; const failed = results.filter(r => r.status === 'fulfilled' && r.value === false).length; console.log(`Concurrent updates: ${successful} successful, ${failed} failed`); // At least one should succeed, others should fail due to version conflicts expect(successful).toBeGreaterThan(0); expect(successful + failed).toBe(concurrentUpdates); // Verify final state is consistent const finalState = db.prepare('SELECT current_level, version FROM inventory WHERE product_id = ?').get(productId); expect(finalState.version).toBeGreaterThan(1); // Should be incremented expect(finalState.current_level).toBeGreaterThanOrEqual(100); }); }); });