479 lines
16 KiB
JavaScript
479 lines
16 KiB
JavaScript
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
|
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, createTestDevice, generateTestToken } = require('../setup');
|
|
|
|
describe('Performance Tests', () => {
|
|
let models, sequelize;
|
|
|
|
before(async () => {
|
|
({ models, sequelize } = await setupTestEnvironment());
|
|
});
|
|
|
|
after(async () => {
|
|
await teardownTestEnvironment();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await cleanDatabase();
|
|
});
|
|
|
|
describe('Database Performance', () => {
|
|
it('should handle large volume of detections efficiently', async function() {
|
|
this.timeout(30000); // 30 second timeout for performance test
|
|
|
|
const tenant = await createTestTenant();
|
|
const device = await createTestDevice({
|
|
tenant_id: tenant.id,
|
|
is_approved: true
|
|
});
|
|
|
|
const batchSize = 1000;
|
|
const detections = [];
|
|
|
|
// Prepare batch of detections
|
|
for (let i = 0; i < batchSize; i++) {
|
|
detections.push({
|
|
device_id: device.id,
|
|
tenant_id: tenant.id,
|
|
geo_lat: 59.3293 + (Math.random() * 0.01),
|
|
geo_lon: 18.0686 + (Math.random() * 0.01),
|
|
device_timestamp: new Date(Date.now() + (i * 1000)),
|
|
drone_type: Math.floor(Math.random() * 19),
|
|
rssi: -50 - Math.floor(Math.random() * 50),
|
|
freq: 2400 + Math.floor(Math.random() * 100),
|
|
drone_id: Math.floor(Math.random() * 10000),
|
|
threat_level: ['low', 'medium', 'high', 'critical'][Math.floor(Math.random() * 4)],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
}
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Bulk insert detections
|
|
await models.DroneDetection.bulkCreate(detections);
|
|
|
|
const insertTime = Date.now() - startTime;
|
|
|
|
// Test query performance
|
|
const queryStartTime = Date.now();
|
|
|
|
const recentDetections = await models.DroneDetection.findAll({
|
|
where: {
|
|
tenant_id: tenant.id,
|
|
device_timestamp: {
|
|
[models.Sequelize.Op.gte]: new Date(Date.now() - 3600000) // Last hour
|
|
}
|
|
},
|
|
order: [['device_timestamp', 'DESC']],
|
|
limit: 100
|
|
});
|
|
|
|
const queryTime = Date.now() - queryStartTime;
|
|
|
|
// Performance assertions
|
|
expect(insertTime).to.be.lessThan(5000); // 5 seconds max for 1000 inserts
|
|
expect(queryTime).to.be.lessThan(100); // 100ms max for query
|
|
expect(recentDetections).to.have.length(100);
|
|
|
|
console.log(`✅ Performance: ${batchSize} inserts in ${insertTime}ms, query in ${queryTime}ms`);
|
|
});
|
|
|
|
it('should efficiently handle tenant-scoped queries with large datasets', async function() {
|
|
this.timeout(30000);
|
|
|
|
// Create multiple tenants with data
|
|
const tenants = [];
|
|
const devices = [];
|
|
|
|
for (let t = 0; t < 5; t++) {
|
|
const tenant = await createTestTenant({ slug: `perf-tenant-${t}` });
|
|
tenants.push(tenant);
|
|
|
|
// Create devices for each tenant
|
|
for (let d = 0; d < 10; d++) {
|
|
const device = await createTestDevice({
|
|
id: (t * 100) + d,
|
|
tenant_id: tenant.id,
|
|
is_approved: true
|
|
});
|
|
devices.push(device);
|
|
}
|
|
}
|
|
|
|
// Create large dataset across all tenants
|
|
const allDetections = [];
|
|
devices.forEach(device => {
|
|
for (let i = 0; i < 200; i++) {
|
|
allDetections.push({
|
|
device_id: device.id,
|
|
tenant_id: device.tenant_id,
|
|
geo_lat: 59.3293 + (Math.random() * 0.1),
|
|
geo_lon: 18.0686 + (Math.random() * 0.1),
|
|
device_timestamp: new Date(Date.now() - (Math.random() * 86400000)), // Random within 24h
|
|
drone_type: Math.floor(Math.random() * 19),
|
|
rssi: -30 - Math.floor(Math.random() * 70),
|
|
freq: 2400,
|
|
drone_id: Math.floor(Math.random() * 50000),
|
|
threat_level: ['low', 'medium', 'high', 'critical'][Math.floor(Math.random() * 4)],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
}
|
|
});
|
|
|
|
await models.DroneDetection.bulkCreate(allDetections);
|
|
|
|
// Test tenant isolation performance
|
|
const testTenant = tenants[0];
|
|
const startTime = Date.now();
|
|
|
|
const tenantDetections = await models.DroneDetection.findAll({
|
|
where: { tenant_id: testTenant.id },
|
|
include: [{
|
|
model: models.Device,
|
|
where: { tenant_id: testTenant.id }
|
|
}],
|
|
order: [['device_timestamp', 'DESC']],
|
|
limit: 50
|
|
});
|
|
|
|
const queryTime = Date.now() - startTime;
|
|
|
|
expect(queryTime).to.be.lessThan(200); // 200ms max with joins
|
|
expect(tenantDetections).to.have.length(50);
|
|
|
|
// Verify all results belong to correct tenant
|
|
tenantDetections.forEach(detection => {
|
|
expect(detection.tenant_id).to.equal(testTenant.id);
|
|
expect(detection.Device.tenant_id).to.equal(testTenant.id);
|
|
});
|
|
|
|
console.log(`✅ Tenant isolation query: ${queryTime}ms with ${allDetections.length} total records`);
|
|
});
|
|
|
|
it('should handle concurrent user sessions efficiently', async function() {
|
|
this.timeout(20000);
|
|
|
|
const tenant = await createTestTenant();
|
|
const users = [];
|
|
|
|
// Create multiple users
|
|
for (let i = 0; i < 10; i++) {
|
|
const user = await createTestUser({
|
|
username: `user${i}`,
|
|
email: `user${i}@example.com`,
|
|
tenant_id: tenant.id
|
|
});
|
|
users.push(user);
|
|
}
|
|
|
|
// Simulate concurrent operations
|
|
const concurrentOperations = users.map(async (user, index) => {
|
|
const startTime = Date.now();
|
|
|
|
// Each user performs multiple operations
|
|
const operations = [
|
|
// Create device
|
|
models.Device.create({
|
|
id: 9000 + index,
|
|
name: `Device ${index}`,
|
|
tenant_id: tenant.id,
|
|
geo_lat: 59.3293,
|
|
geo_lon: 18.0686,
|
|
is_approved: true
|
|
}),
|
|
|
|
// Query existing data
|
|
models.DroneDetection.findAll({
|
|
where: { tenant_id: tenant.id },
|
|
limit: 10
|
|
}),
|
|
|
|
// Create alert rule
|
|
models.AlertRule.create({
|
|
tenant_id: tenant.id,
|
|
name: `Rule ${index}`,
|
|
is_active: true
|
|
})
|
|
];
|
|
|
|
await Promise.all(operations);
|
|
return Date.now() - startTime;
|
|
});
|
|
|
|
const operationTimes = await Promise.all(concurrentOperations);
|
|
const averageTime = operationTimes.reduce((a, b) => a + b, 0) / operationTimes.length;
|
|
|
|
expect(averageTime).to.be.lessThan(1000); // 1 second average
|
|
console.log(`✅ Concurrent operations: ${operationTimes.length} users, ${averageTime.toFixed(2)}ms average`);
|
|
});
|
|
});
|
|
|
|
describe('Memory Performance', () => {
|
|
it('should efficiently manage memory with large tracking datasets', async () => {
|
|
const DroneTrackingService = require('../../services/droneTrackingService');
|
|
const trackingService = new DroneTrackingService();
|
|
|
|
const initialMemory = process.memoryUsage().heapUsed;
|
|
|
|
// Simulate tracking 1000 different drones
|
|
for (let i = 0; i < 1000; i++) {
|
|
const detection = {
|
|
drone_id: i,
|
|
geo_lat: 59.3293 + (Math.random() * 0.1),
|
|
geo_lon: 18.0686 + (Math.random() * 0.1),
|
|
device_timestamp: new Date(),
|
|
rssi: -60,
|
|
threat_level: 'medium'
|
|
};
|
|
|
|
trackingService.trackDetection(detection);
|
|
}
|
|
|
|
const afterTrackingMemory = process.memoryUsage().heapUsed;
|
|
const memoryIncrease = afterTrackingMemory - initialMemory;
|
|
|
|
// Memory increase should be reasonable (less than 50MB for 1000 drones)
|
|
expect(memoryIncrease).to.be.lessThan(50 * 1024 * 1024);
|
|
|
|
// Test cleanup efficiency
|
|
trackingService.cleanup(Date.now() + 1000); // Cleanup all
|
|
|
|
const afterCleanupMemory = process.memoryUsage().heapUsed;
|
|
const activeTracking = trackingService.getActiveTracking();
|
|
|
|
expect(activeTracking).to.have.length(0);
|
|
console.log(`✅ Memory: +${(memoryIncrease / 1024 / 1024).toFixed(2)}MB for 1000 tracked drones`);
|
|
});
|
|
|
|
it('should handle alert service memory efficiently', async () => {
|
|
const AlertService = require('../../services/alertService');
|
|
const alertService = new AlertService();
|
|
|
|
const initialMemory = process.memoryUsage().heapUsed;
|
|
|
|
// Generate many active alerts
|
|
for (let i = 0; i < 500; i++) {
|
|
const alertKey = `device_${i % 50}_drone_${i}`;
|
|
alertService.activeAlerts.set(alertKey, {
|
|
deviceId: i % 50,
|
|
droneId: i,
|
|
firstDetection: Date.now(),
|
|
lastDetection: Date.now(),
|
|
detectionCount: 1,
|
|
threatLevel: 'high'
|
|
});
|
|
}
|
|
|
|
const afterAlertsMemory = process.memoryUsage().heapUsed;
|
|
const memoryIncrease = afterAlertsMemory - initialMemory;
|
|
|
|
// Memory should be reasonable
|
|
expect(memoryIncrease).to.be.lessThan(20 * 1024 * 1024); // 20MB max
|
|
|
|
// Test cleanup
|
|
alertService.cleanupOldAlerts(Date.now() + 1000);
|
|
expect(alertService.activeAlerts.size).to.equal(0);
|
|
|
|
console.log(`✅ Alert memory: +${(memoryIncrease / 1024 / 1024).toFixed(2)}MB for 500 alerts`);
|
|
});
|
|
});
|
|
|
|
describe('API Response Time Performance', () => {
|
|
it('should maintain fast response times under load', async () => {
|
|
const tenant = await createTestTenant();
|
|
const device = await createTestDevice({
|
|
tenant_id: tenant.id,
|
|
is_approved: true
|
|
});
|
|
|
|
// Create substantial detection history
|
|
const detections = [];
|
|
for (let i = 0; i < 2000; i++) {
|
|
detections.push({
|
|
device_id: device.id,
|
|
tenant_id: tenant.id,
|
|
geo_lat: 59.3293 + (Math.random() * 0.01),
|
|
geo_lon: 18.0686 + (Math.random() * 0.01),
|
|
device_timestamp: new Date(Date.now() - (i * 60000)), // 1 minute intervals
|
|
drone_type: Math.floor(Math.random() * 19),
|
|
rssi: -50 - Math.floor(Math.random() * 50),
|
|
freq: 2400,
|
|
drone_id: Math.floor(Math.random() * 1000),
|
|
threat_level: ['low', 'medium', 'high', 'critical'][Math.floor(Math.random() * 4)],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
}
|
|
|
|
await models.DroneDetection.bulkCreate(detections);
|
|
|
|
// Test various query patterns for response time
|
|
const queries = [
|
|
{
|
|
name: 'Recent detections',
|
|
query: () => models.DroneDetection.findAll({
|
|
where: {
|
|
tenant_id: tenant.id,
|
|
device_timestamp: {
|
|
[models.Sequelize.Op.gte]: new Date(Date.now() - 3600000)
|
|
}
|
|
},
|
|
order: [['device_timestamp', 'DESC']],
|
|
limit: 50
|
|
})
|
|
},
|
|
{
|
|
name: 'Device statistics',
|
|
query: () => models.DroneDetection.findAll({
|
|
where: { tenant_id: tenant.id },
|
|
attributes: [
|
|
'device_id',
|
|
[models.sequelize.fn('COUNT', '*'), 'count'],
|
|
[models.sequelize.fn('AVG', models.sequelize.col('rssi')), 'avg_rssi']
|
|
],
|
|
group: ['device_id']
|
|
})
|
|
},
|
|
{
|
|
name: 'Threat analysis',
|
|
query: () => models.DroneDetection.findAll({
|
|
where: {
|
|
tenant_id: tenant.id,
|
|
threat_level: ['high', 'critical']
|
|
},
|
|
order: [['device_timestamp', 'DESC']],
|
|
limit: 100
|
|
})
|
|
}
|
|
];
|
|
|
|
for (const queryTest of queries) {
|
|
const startTime = Date.now();
|
|
const result = await queryTest.query();
|
|
const queryTime = Date.now() - startTime;
|
|
|
|
expect(queryTime).to.be.lessThan(500); // 500ms max
|
|
expect(result).to.be.an('array');
|
|
|
|
console.log(`✅ ${queryTest.name}: ${queryTime}ms (${result.length} results)`);
|
|
}
|
|
});
|
|
|
|
it('should handle pagination efficiently', async () => {
|
|
const tenant = await createTestTenant();
|
|
const device = await createTestDevice({ tenant_id: tenant.id });
|
|
|
|
// Create large dataset
|
|
const detections = [];
|
|
for (let i = 0; i < 5000; i++) {
|
|
detections.push({
|
|
device_id: device.id,
|
|
tenant_id: tenant.id,
|
|
geo_lat: 59.3293,
|
|
geo_lon: 18.0686,
|
|
device_timestamp: new Date(Date.now() - (i * 1000)),
|
|
drone_type: 2,
|
|
rssi: -60,
|
|
freq: 2400,
|
|
drone_id: i,
|
|
threat_level: 'medium',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
}
|
|
|
|
await models.DroneDetection.bulkCreate(detections);
|
|
|
|
// Test pagination performance at different offsets
|
|
const pageSize = 50;
|
|
const testPages = [0, 1000, 2000, 4000]; // Different offset positions
|
|
|
|
for (const offset of testPages) {
|
|
const startTime = Date.now();
|
|
|
|
const pageResults = await models.DroneDetection.findAndCountAll({
|
|
where: { tenant_id: tenant.id },
|
|
order: [['device_timestamp', 'DESC']],
|
|
limit: pageSize,
|
|
offset: offset
|
|
});
|
|
|
|
const queryTime = Date.now() - startTime;
|
|
|
|
expect(queryTime).to.be.lessThan(200); // 200ms max even for large offsets
|
|
expect(pageResults.rows).to.have.length(pageSize);
|
|
|
|
console.log(`✅ Page at offset ${offset}: ${queryTime}ms`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Scalability Tests', () => {
|
|
it('should scale with increasing number of tenants', async function() {
|
|
this.timeout(30000);
|
|
|
|
const tenantCount = 20;
|
|
const devicesPerTenant = 5;
|
|
const detectionsPerDevice = 100;
|
|
|
|
// Create multi-tenant environment
|
|
for (let t = 0; t < tenantCount; t++) {
|
|
const tenant = await createTestTenant({ slug: `scale-tenant-${t}` });
|
|
|
|
for (let d = 0; d < devicesPerTenant; d++) {
|
|
const device = await createTestDevice({
|
|
id: (t * 1000) + d,
|
|
tenant_id: tenant.id,
|
|
is_approved: true
|
|
});
|
|
|
|
// Create detections for this device
|
|
const detections = [];
|
|
for (let i = 0; i < detectionsPerDevice; i++) {
|
|
detections.push({
|
|
device_id: device.id,
|
|
tenant_id: tenant.id,
|
|
geo_lat: 59.3293 + (Math.random() * 0.01),
|
|
geo_lon: 18.0686 + (Math.random() * 0.01),
|
|
device_timestamp: new Date(Date.now() - (Math.random() * 86400000)),
|
|
drone_type: Math.floor(Math.random() * 19),
|
|
rssi: -60 + Math.floor(Math.random() * 40),
|
|
freq: 2400,
|
|
drone_id: (t * 10000) + (d * 1000) + i,
|
|
threat_level: ['low', 'medium', 'high'][Math.floor(Math.random() * 3)],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
}
|
|
|
|
await models.DroneDetection.bulkCreate(detections);
|
|
}
|
|
}
|
|
|
|
// Test query performance across all tenants
|
|
const startTime = Date.now();
|
|
|
|
const allTenants = await models.Tenant.findAll({
|
|
include: [{
|
|
model: models.Device,
|
|
include: [{
|
|
model: models.DroneDetection,
|
|
limit: 10,
|
|
order: [['device_timestamp', 'DESC']]
|
|
}]
|
|
}]
|
|
});
|
|
|
|
const queryTime = Date.now() - startTime;
|
|
|
|
expect(allTenants).to.have.length(tenantCount);
|
|
expect(queryTime).to.be.lessThan(2000); // 2 seconds max for complex query
|
|
|
|
console.log(`✅ Scalability: ${tenantCount} tenants, ${tenantCount * devicesPerTenant} devices, ${tenantCount * devicesPerTenant * detectionsPerDevice} detections in ${queryTime}ms`);
|
|
});
|
|
});
|
|
});
|