Initial commit: Inventory Barcode System

This commit is contained in:
2025-07-22 20:24:51 -04:00
commit 511b01748d
63 changed files with 26932 additions and 0 deletions

View File

@ -0,0 +1,468 @@
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);
});
});
});