← back to Handbag Auth Nextjs
auction-viewer/tests/performance/load.test.js
380 lines
/**
* Performance Tests - Load Testing and Benchmarks
*/
const request = require('supertest');
const express = require('express');
const Database = require('better-sqlite3');
const { TestDatabase } = require('../fixtures/test-database');
const { generateLargeDataset } = require('../fixtures/test-data');
describe('Performance Tests', () => {
let app;
let testDb;
let testDbPath;
beforeAll(() => {
// Create large test database with unique path for performance tests
const path = require('path');
const uniquePath = path.join(__dirname, '../fixtures', `perf-test-${Date.now()}.db`);
testDb = new TestDatabase(uniquePath);
testDb.initialize();
testDb.seed(generateLargeDataset(2000));
testDbPath = testDb.getPath();
// Create Express app
app = express();
app.use(express.json());
app.get('/api/auctions', (req, res) => {
try {
const db = new Database(testDbPath, { readonly: true });
const auctions = db.prepare('SELECT * FROM auctions ORDER BY created_at DESC LIMIT 1000').all();
db.close();
res.json({ success: true, count: auctions.length, data: auctions });
} catch (error) {
res.json({ success: false, error: error.message, data: [] });
}
});
app.get('/api/best-values', (req, res) => {
try {
const db = new Database(testDbPath, { readonly: true });
const sortBy = req.query.sort || 'percent';
const orderBy = sortBy === 'dollar' ? 'savings_amount' : 'savings_percent';
const auctions = db.prepare(`
SELECT *,
CASE
WHEN estimate_low > 0 AND current_price > 0
THEN ((estimate_low - current_price) / estimate_low * 100)
ELSE 0
END as savings_percent,
CASE
WHEN estimate_low > 0 AND current_price > 0
THEN (estimate_low - current_price)
ELSE 0
END as savings_amount
FROM auctions
WHERE current_price > 0 AND estimate_low > 0
ORDER BY ${orderBy} DESC
LIMIT 100
`).all();
db.close();
res.json({ success: true, count: auctions.length, data: auctions, sortedBy: sortBy });
} catch (error) {
res.json({ success: false, error: error.message, data: [] });
}
});
app.get('/api/stats', (req, res) => {
try {
const db = new Database(testDbPath, { readonly: true });
const total = db.prepare('SELECT COUNT(*) as count FROM auctions').get();
const byBrand = db.prepare(`
SELECT search_term, COUNT(*) as count
FROM auctions
GROUP BY search_term
ORDER BY count DESC
`).all();
const byAuctionHouse = db.prepare(`
SELECT auction_house, COUNT(*) as count
FROM auctions
GROUP BY auction_house
ORDER BY count DESC
`).all();
const avgPrice = db.prepare(`
SELECT AVG(current_price) as avg_price
FROM auctions
WHERE current_price > 0
`).get();
db.close();
res.json({
success: true,
stats: {
total: total.count,
byBrand,
byAuctionHouse,
avgPrice: avgPrice.avg_price || 0
}
});
} catch (error) {
res.json({ success: false, error: error.message });
}
});
});
afterAll(() => {
testDb.cleanup();
});
describe('Response Time Benchmarks', () => {
test('GET /api/auctions should respond within 200ms', async () => {
const start = Date.now();
const response = await request(app).get('/api/auctions');
const duration = Date.now() - start;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(200);
}, 10000);
test('GET /api/best-values should respond within 300ms', async () => {
const start = Date.now();
const response = await request(app).get('/api/best-values');
const duration = Date.now() - start;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(300);
}, 10000);
test('GET /api/stats should respond within 250ms', async () => {
const start = Date.now();
const response = await request(app).get('/api/stats');
const duration = Date.now() - start;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(250);
}, 10000);
});
describe('Concurrent Request Handling', () => {
test('should handle 10 concurrent requests', async () => {
const requests = Array.from({ length: 10 }, () =>
request(app).get('/api/auctions')
);
const start = Date.now();
const responses = await Promise.all(requests);
const duration = Date.now() - start;
responses.forEach(response => {
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
});
// Should complete within 2 seconds for 10 concurrent requests
expect(duration).toBeLessThan(2000);
}, 15000);
test('should handle 50 concurrent requests', async () => {
const requests = Array.from({ length: 50 }, () =>
request(app).get('/api/best-values')
);
const start = Date.now();
const responses = await Promise.all(requests);
const duration = Date.now() - start;
responses.forEach(response => {
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
});
// Should complete within 5 seconds for 50 concurrent requests
expect(duration).toBeLessThan(5000);
}, 20000);
test('should handle mixed endpoint concurrent requests', async () => {
const requests = [
...Array.from({ length: 10 }, () => request(app).get('/api/auctions')),
...Array.from({ length: 10 }, () => request(app).get('/api/best-values')),
...Array.from({ length: 10 }, () => request(app).get('/api/stats'))
];
const start = Date.now();
const responses = await Promise.all(requests);
const duration = Date.now() - start;
responses.forEach(response => {
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
});
expect(duration).toBeLessThan(3000);
}, 15000);
});
describe('Large Dataset Performance', () => {
test('should handle queries on 2000+ records efficiently', async () => {
const response = await request(app).get('/api/auctions');
expect(response.status).toBe(200);
expect(response.body.data.length).toBeGreaterThan(0);
expect(response.body.data.length).toBeLessThanOrEqual(1000); // Respects limit
});
test('should perform complex sorting on large dataset', async () => {
const start = Date.now();
const response = await request(app).get('/api/best-values?sort=percent');
const duration = Date.now() - start;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(400);
// Verify sorting
const data = response.body.data;
for (let i = 0; i < data.length - 1; i++) {
expect(data[i].savings_percent).toBeGreaterThanOrEqual(data[i + 1].savings_percent);
}
});
test('should perform aggregations on large dataset', async () => {
const start = Date.now();
const response = await request(app).get('/api/stats');
const duration = Date.now() - start;
expect(response.status).toBe(200);
expect(duration).toBeLessThan(300);
expect(response.body.stats.total).toBeGreaterThanOrEqual(2000);
});
});
describe('Database Query Performance', () => {
test('should execute SELECT queries efficiently', () => {
const db = new Database(testDbPath, { readonly: true });
const iterations = 100;
const start = Date.now();
for (let i = 0; i < iterations; i++) {
db.prepare('SELECT * FROM auctions LIMIT 100').all();
}
const duration = Date.now() - start;
db.close();
// 100 queries should complete in less than 1 second
expect(duration).toBeLessThan(1000);
const avgTime = duration / iterations;
expect(avgTime).toBeLessThan(10); // Average < 10ms per query
});
test('should execute complex queries with calculations efficiently', () => {
const db = new Database(testDbPath, { readonly: true });
const start = Date.now();
const result = db.prepare(`
SELECT *,
((estimate_low - current_price) / estimate_low * 100) as savings_percent,
(estimate_low - current_price) as savings_amount
FROM auctions
WHERE current_price > 0 AND estimate_low > 0
ORDER BY savings_percent DESC
LIMIT 100
`).all();
const duration = Date.now() - start;
db.close();
expect(result.length).toBeGreaterThan(0);
expect(duration).toBeLessThan(100); // Should be very fast
});
test('should execute aggregation queries efficiently', () => {
const db = new Database(testDbPath, { readonly: true });
const start = Date.now();
const stats = {
total: db.prepare('SELECT COUNT(*) as count FROM auctions').get(),
byBrand: db.prepare('SELECT search_term, COUNT(*) as count FROM auctions GROUP BY search_term').all(),
avgPrice: db.prepare('SELECT AVG(current_price) as avg FROM auctions WHERE current_price > 0').get()
};
const duration = Date.now() - start;
db.close();
expect(stats.total.count).toBeGreaterThan(0);
expect(stats.byBrand.length).toBeGreaterThan(0);
expect(duration).toBeLessThan(150);
});
});
describe('Memory Usage', () => {
test('should not leak memory on repeated requests', async () => {
const initialMemory = process.memoryUsage().heapUsed;
// Make 100 requests
for (let i = 0; i < 100; i++) {
await request(app).get('/api/auctions');
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
const finalMemory = process.memoryUsage().heapUsed;
const memoryIncrease = finalMemory - initialMemory;
// Memory increase should be reasonable (< 50MB for 100 requests)
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024);
}, 30000);
test('should handle large response payloads', async () => {
const response = await request(app).get('/api/auctions');
expect(response.body.data.length).toBeGreaterThan(0);
// Calculate payload size
const payloadSize = JSON.stringify(response.body).length;
// Payload should be reasonable (< 5MB)
expect(payloadSize).toBeLessThan(5 * 1024 * 1024);
});
});
describe('Throughput', () => {
test('should maintain throughput under load', async () => {
const requestCount = 100;
const start = Date.now();
const requests = Array.from({ length: requestCount }, () =>
request(app).get('/api/auctions')
);
await Promise.all(requests);
const duration = Date.now() - start;
const throughput = (requestCount / duration) * 1000; // requests per second
// Should handle at least 20 requests per second
expect(throughput).toBeGreaterThan(20);
}, 30000);
});
describe('Database Connection Management', () => {
test('should open and close connections properly', async () => {
// Make multiple requests
for (let i = 0; i < 10; i++) {
const response = await request(app).get('/api/auctions');
expect(response.status).toBe(200);
}
// No connection leaks - all connections should be closed
// This is implicit in the test passing without hanging
});
test('should handle rapid connection open/close', async () => {
const start = Date.now();
for (let i = 0; i < 50; i++) {
const db = new Database(testDbPath, { readonly: true });
db.prepare('SELECT COUNT(*) FROM auctions').get();
db.close();
}
const duration = Date.now() - start;
// Should handle 50 connection cycles quickly
expect(duration).toBeLessThan(1000);
});
});
});