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('Drone Detection Advanced Processing', () => { let models, sequelize; before(async () => { ({ models, sequelize } = await setupTestEnvironment()); }); after(async () => { await teardownTestEnvironment(); }); beforeEach(async () => { await cleanDatabase(); }); describe('Detection Pattern Analysis', () => { it('should analyze drone movement patterns', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id, is_approved: true, geo_lat: 59.3293, geo_lon: 18.0686 }); const droneId = 12345; const detections = []; // Create a pattern of detections showing drone movement const baseTime = Date.now(); const movementPattern = [ { lat: 59.3293, lon: 18.0686, rssi: -80, distance: 1000 }, { lat: 59.3295, lon: 18.0688, rssi: -75, distance: 800 }, { lat: 59.3297, lon: 18.0690, rssi: -70, distance: 600 }, { lat: 59.3299, lon: 18.0692, rssi: -65, distance: 400 }, { lat: 59.3301, lon: 18.0694, rssi: -60, distance: 200 } ]; for (let i = 0; i < movementPattern.length; i++) { const point = movementPattern[i]; const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: point.lat, geo_lon: point.lon, device_timestamp: new Date(baseTime + i * 30000), // 30 seconds apart drone_type: 2, rssi: point.rssi, freq: 2400, drone_id: droneId, threat_level: 'medium' }); detections.push(detection); } // Analyze movement pattern const analyzeMovementPattern = (detections) => { if (detections.length < 2) return null; const sortedDetections = detections.sort((a, b) => new Date(a.device_timestamp) - new Date(b.device_timestamp) ); let totalDistance = 0; let isApproaching = true; let averageSpeed = 0; for (let i = 1; i < sortedDetections.length; i++) { const prev = sortedDetections[i - 1]; const curr = sortedDetections[i]; // Calculate distance between points const distance = calculateDistance( prev.geo_lat, prev.geo_lon, curr.geo_lat, curr.geo_lon ); totalDistance += distance; // Check if approaching (RSSI getting stronger) if (curr.rssi <= prev.rssi) { isApproaching = false; } // Calculate speed const timeDiff = new Date(curr.device_timestamp) - new Date(prev.device_timestamp); const speed = distance / (timeDiff / 1000); // meters per second averageSpeed += speed; } averageSpeed = averageSpeed / (sortedDetections.length - 1); return { totalDistance, isApproaching, averageSpeed, duration: new Date(sortedDetections[sortedDetections.length - 1].device_timestamp) - new Date(sortedDetections[0].device_timestamp), detectionCount: sortedDetections.length }; }; const calculateDistance = (lat1, lon1, lat2, lon2) => { const R = 6371000; // Earth's radius in meters const φ1 = lat1 * Math.PI / 180; const φ2 = lat2 * Math.PI / 180; const Δφ = (lat2 - lat1) * Math.PI / 180; const Δλ = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }; const pattern = analyzeMovementPattern(detections); expect(pattern).to.exist; expect(pattern.totalDistance).to.be.greaterThan(0); expect(pattern.isApproaching).to.be.true; // RSSI strengthening expect(pattern.averageSpeed).to.be.greaterThan(0); expect(pattern.detectionCount).to.equal(5); }); it('should detect stationary drone patterns', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); const droneId = 67890; // Create detections from same location (stationary drone) const stationaryDetections = []; for (let i = 0; i < 10; i++) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.3293, // Same coordinates geo_lon: 18.0686, device_timestamp: new Date(Date.now() + i * 60000), // 1 minute apart drone_type: 3, rssi: -65 + Math.random() * 10 - 5, // Small RSSI variation freq: 2400, drone_id: droneId }); stationaryDetections.push(detection); } const analyzeStationaryPattern = (detections) => { const coordinates = detections.map(d => ({ lat: d.geo_lat, lon: d.geo_lon })); // Calculate variance in coordinates const latVariance = calculateVariance(coordinates.map(c => c.lat)); const lonVariance = calculateVariance(coordinates.map(c => c.lon)); const isStationary = latVariance < 0.0001 && lonVariance < 0.0001; // Very small variance return { isStationary, latVariance, lonVariance, detectionCount: detections.length, duration: new Date(detections[detections.length - 1].device_timestamp) - new Date(detections[0].device_timestamp) }; }; const calculateVariance = (values) => { const mean = values.reduce((sum, val) => sum + val, 0) / values.length; const squaredDiffs = values.map(val => Math.pow(val - mean, 2)); return squaredDiffs.reduce((sum, diff) => sum + diff, 0) / values.length; }; const pattern = analyzeStationaryPattern(stationaryDetections); expect(pattern.isStationary).to.be.true; expect(pattern.latVariance).to.be.lessThan(0.0001); expect(pattern.lonVariance).to.be.lessThan(0.0001); expect(pattern.detectionCount).to.equal(10); }); it('should identify swarm drone patterns', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); // Create multiple drones detected simultaneously (swarm pattern) const swarmDetections = []; const baseTime = Date.now(); const swarmCenter = { lat: 59.3293, lon: 18.0686 }; for (let droneIndex = 0; droneIndex < 5; droneIndex++) { for (let timeIndex = 0; timeIndex < 3; timeIndex++) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: swarmCenter.lat + (Math.random() - 0.5) * 0.01, // Within 1km radius geo_lon: swarmCenter.lon + (Math.random() - 0.5) * 0.01, device_timestamp: new Date(baseTime + timeIndex * 10000), // 10 seconds apart drone_type: 2, rssi: -60 - Math.random() * 20, freq: 2400, drone_id: 1000 + droneIndex }); swarmDetections.push(detection); } } const detectSwarmPattern = (detections, timeWindow = 60000) => { // Group detections by time windows const timeGroups = {}; detections.forEach(detection => { const timeKey = Math.floor(new Date(detection.device_timestamp).getTime() / timeWindow); if (!timeGroups[timeKey]) { timeGroups[timeKey] = []; } timeGroups[timeKey].push(detection); }); // Find groups with multiple unique drones const swarmGroups = Object.values(timeGroups).filter(group => { const uniqueDrones = new Set(group.map(d => d.drone_id)); return uniqueDrones.size >= 3; // 3 or more drones detected simultaneously }); const maxConcurrentDrones = Math.max(...Object.values(timeGroups).map(group => new Set(group.map(d => d.drone_id)).size )); return { isSwarm: swarmGroups.length > 0, maxConcurrentDrones, swarmGroupCount: swarmGroups.length, totalUniqueDrones: new Set(detections.map(d => d.drone_id)).size }; }; const swarmAnalysis = detectSwarmPattern(swarmDetections); expect(swarmAnalysis.isSwarm).to.be.true; expect(swarmAnalysis.maxConcurrentDrones).to.be.greaterThan(2); expect(swarmAnalysis.totalUniqueDrones).to.equal(5); }); }); describe('Advanced Threat Assessment', () => { it('should assess threat level based on proximity to sensitive areas', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id, geo_lat: 59.3293, // Stockholm coordinates geo_lon: 18.0686, location_description: 'Airport Security Zone' }); // Define sensitive areas const sensitiveAreas = [ { name: 'Airport Runway', lat: 59.3293, lon: 18.0686, radius: 1000, threat_multiplier: 3.0 }, { name: 'Government Building', lat: 59.3300, lon: 18.0700, radius: 500, threat_multiplier: 2.5 }, { name: 'Power Plant', lat: 59.3280, lon: 18.0650, radius: 2000, threat_multiplier: 2.0 } ]; const assessThreatLevel = (detection, sensitiveAreas) => { let baseThreatLevel = 1.0; let maxThreatMultiplier = 1.0; let proximityFactors = []; sensitiveAreas.forEach(area => { const distance = calculateDistance( detection.geo_lat, detection.geo_lon, area.lat, area.lon ); if (distance <= area.radius) { const proximityFactor = 1 - (distance / area.radius); const threatIncrease = area.threat_multiplier * proximityFactor; proximityFactors.push({ area: area.name, distance, proximityFactor, threatIncrease }); maxThreatMultiplier = Math.max(maxThreatMultiplier, threatIncrease); } }); const finalThreatLevel = baseThreatLevel * maxThreatMultiplier; let threatCategory; if (finalThreatLevel >= 2.5) threatCategory = 'critical'; else if (finalThreatLevel >= 2.0) threatCategory = 'high'; else if (finalThreatLevel >= 1.5) threatCategory = 'medium'; else threatCategory = 'low'; return { threatLevel: finalThreatLevel, threatCategory, proximityFactors }; }; const calculateDistance = (lat1, lon1, lat2, lon2) => { const R = 6371000; const φ1 = lat1 * Math.PI / 180; const φ2 = lat2 * Math.PI / 180; const Δφ = (lat2 - lat1) * Math.PI / 180; const Δλ = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }; // Test detection near airport (high threat) const airportDetection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.3295, // Very close to airport geo_lon: 18.0688, device_timestamp: new Date(), drone_type: 2, rssi: -45, freq: 2400, drone_id: 1001 }); const airportThreat = assessThreatLevel(airportDetection, sensitiveAreas); expect(airportThreat.threatCategory).to.be.oneOf(['high', 'critical']); expect(airportThreat.proximityFactors).to.have.length.greaterThan(0); // Test detection far from sensitive areas (low threat) const remoteDetection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.4000, // Far from sensitive areas geo_lon: 18.1000, device_timestamp: new Date(), drone_type: 2, rssi: -80, freq: 2400, drone_id: 1002 }); const remoteThreat = assessThreatLevel(remoteDetection, sensitiveAreas); expect(remoteThreat.threatCategory).to.equal('low'); expect(remoteThreat.proximityFactors).to.have.length(0); }); it('should factor drone type into threat assessment', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); const droneTypes = require('../../utils/droneTypes'); const assessDroneThreat = (droneType, additionalFactors = {}) => { const droneInfo = droneTypes.getDroneTypeInfo(droneType); let threatScore = 0; // Base threat from drone type switch (droneInfo.threat_level) { case 'critical': threatScore += 4; break; case 'high': threatScore += 3; break; case 'medium': threatScore += 2; break; case 'low': threatScore += 1; break; } // Additional factors if (additionalFactors.strongSignal) threatScore += 1; // Close proximity if (additionalFactors.militaryFreq) threatScore += 2; // Military frequency if (additionalFactors.jamming) threatScore += 3; // Electronic warfare if (additionalFactors.nightTime) threatScore += 1; // Suspicious timing return { baseScore: threatScore, droneInfo, factors: additionalFactors, finalThreatLevel: threatScore >= 6 ? 'critical' : threatScore >= 4 ? 'high' : threatScore >= 2 ? 'medium' : 'low' }; }; // Test military drone (high threat) const militaryDetection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.3293, geo_lon: 18.0686, device_timestamp: new Date(), drone_type: 2, // Orlan (military) rssi: -45, // Strong signal freq: 2400, drone_id: 1003 }); const militaryThreat = assessDroneThreat(2, { strongSignal: true, militaryFreq: true }); expect(militaryThreat.finalThreatLevel).to.be.oneOf(['high', 'critical']); // Test commercial drone (lower threat) const commercialDetection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.3293, geo_lon: 18.0686, device_timestamp: new Date(), drone_type: 8, // DJI (commercial) rssi: -75, // Weak signal freq: 2400, drone_id: 1004 }); const commercialThreat = assessDroneThreat(8, { strongSignal: false }); expect(commercialThreat.finalThreatLevel).to.be.oneOf(['low', 'medium']); }); it('should assess threat escalation over time', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); const droneId = 12345; // Create escalating threat scenario const escalationDetections = []; const baseTime = Date.now(); const scenarios = [ { time: 0, rssi: -80, lat: 59.3200, threat: 'low' }, // Distant { time: 300000, rssi: -70, lat: 59.3220, threat: 'low' }, // Approaching { time: 600000, rssi: -60, lat: 59.3240, threat: 'medium' }, // Closer { time: 900000, rssi: -50, lat: 59.3260, threat: 'medium' }, // Getting close { time: 1200000, rssi: -40, lat: 59.3280, threat: 'high' } // Very close ]; for (const scenario of scenarios) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: scenario.lat, geo_lon: 18.0686, device_timestamp: new Date(baseTime + scenario.time), drone_type: 2, rssi: scenario.rssi, freq: 2400, drone_id: droneId }); escalationDetections.push(detection); } const analyzeEscalation = (detections) => { const sortedDetections = detections.sort((a, b) => new Date(a.device_timestamp) - new Date(b.device_timestamp) ); const escalationEvents = []; let previousThreatLevel = 0; sortedDetections.forEach((detection, index) => { // Simple threat scoring based on RSSI let currentThreatLevel; if (detection.rssi > -50) currentThreatLevel = 3; // High else if (detection.rssi > -65) currentThreatLevel = 2; // Medium else currentThreatLevel = 1; // Low if (currentThreatLevel > previousThreatLevel) { escalationEvents.push({ timestamp: detection.device_timestamp, fromLevel: previousThreatLevel, toLevel: currentThreatLevel, rssi: detection.rssi, position: { lat: detection.geo_lat, lon: detection.geo_lon } }); } previousThreatLevel = currentThreatLevel; }); return { escalationCount: escalationEvents.length, maxThreatLevel: Math.max(...sortedDetections.map(d => d.rssi > -50 ? 3 : d.rssi > -65 ? 2 : 1)), escalationEvents, totalDuration: new Date(sortedDetections[sortedDetections.length - 1].device_timestamp) - new Date(sortedDetections[0].device_timestamp) }; }; const escalationAnalysis = analyzeEscalation(escalationDetections); expect(escalationAnalysis.escalationCount).to.be.greaterThan(0); expect(escalationAnalysis.maxThreatLevel).to.equal(3); expect(escalationAnalysis.escalationEvents).to.have.length.greaterThan(2); }); }); describe('Detection Correlation and Clustering', () => { it('should correlate detections from multiple devices', async () => { const tenant = await createTestTenant(); const device1 = await createTestDevice({ id: 101, tenant_id: tenant.id, geo_lat: 59.3293, geo_lon: 18.0686 }); const device2 = await createTestDevice({ id: 102, tenant_id: tenant.id, geo_lat: 59.3300, geo_lon: 18.0700 }); const droneId = 99999; const detectionTime = new Date(); // Same drone detected by multiple devices const detection1 = await models.DroneDetection.create({ device_id: device1.id, tenant_id: tenant.id, geo_lat: 59.3295, geo_lon: 18.0690, device_timestamp: detectionTime, drone_type: 2, rssi: -60, drone_id: droneId }); const detection2 = await models.DroneDetection.create({ device_id: device2.id, tenant_id: tenant.id, geo_lat: 59.3297, geo_lon: 18.0692, device_timestamp: new Date(detectionTime.getTime() + 5000), // 5 seconds later drone_type: 2, rssi: -65, drone_id: droneId }); const correlateDetections = (detections, timeWindow = 30000, maxDistance = 5000) => { const correlationGroups = []; detections.forEach(detection => { let foundGroup = false; correlationGroups.forEach(group => { const timeMatch = group.detections.some(d => Math.abs(new Date(d.device_timestamp) - new Date(detection.device_timestamp)) <= timeWindow ); const droneMatch = group.droneId === detection.drone_id; if (timeMatch && droneMatch) { group.detections.push(detection); group.devices.add(detection.device_id); foundGroup = true; } }); if (!foundGroup) { correlationGroups.push({ droneId: detection.drone_id, detections: [detection], devices: new Set([detection.device_id]) }); } }); return correlationGroups.map(group => ({ droneId: group.droneId, detectionCount: group.detections.length, deviceCount: group.devices.size, isCorrelated: group.devices.size > 1, timeSpan: group.detections.length > 1 ? Math.max(...group.detections.map(d => new Date(d.device_timestamp))) - Math.min(...group.detections.map(d => new Date(d.device_timestamp))) : 0, detections: group.detections })); }; const allDetections = [detection1, detection2]; const correlations = correlateDetections(allDetections); expect(correlations).to.have.length(1); expect(correlations[0].isCorrelated).to.be.true; expect(correlations[0].deviceCount).to.equal(2); expect(correlations[0].detectionCount).to.equal(2); }); it('should identify detection clusters in geographic areas', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); // Create clustered detections (multiple drones in same area) const clusterCenter = { lat: 59.3293, lon: 18.0686 }; const clusterDetections = []; for (let i = 0; i < 10; i++) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: clusterCenter.lat + (Math.random() - 0.5) * 0.005, // Within ~500m geo_lon: clusterCenter.lon + (Math.random() - 0.5) * 0.005, device_timestamp: new Date(Date.now() + i * 60000), drone_type: Math.floor(Math.random() * 5) + 1, rssi: -60 - Math.random() * 20, drone_id: 2000 + i }); clusterDetections.push(detection); } // Create isolated detection (not in cluster) const isolatedDetection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.4000, // Far from cluster geo_lon: 18.1000, device_timestamp: new Date(), drone_type: 2, rssi: -70, drone_id: 3000 }); const clusterDetectionsArray = [...clusterDetections, isolatedDetection]; const identifyClusters = (detections, maxClusterDistance = 1000, minClusterSize = 3) => { const clusters = []; const processed = new Set(); detections.forEach((detection, index) => { if (processed.has(index)) return; const cluster = [detection]; processed.add(index); detections.forEach((otherDetection, otherIndex) => { if (otherIndex === index || processed.has(otherIndex)) return; const distance = calculateDistance( detection.geo_lat, detection.geo_lon, otherDetection.geo_lat, otherDetection.geo_lon ); if (distance <= maxClusterDistance) { cluster.push(otherDetection); processed.add(otherIndex); } }); if (cluster.length >= minClusterSize) { clusters.push({ center: { lat: cluster.reduce((sum, d) => sum + d.geo_lat, 0) / cluster.length, lon: cluster.reduce((sum, d) => sum + d.geo_lon, 0) / cluster.length }, detections: cluster, size: cluster.length, uniqueDrones: new Set(cluster.map(d => d.drone_id)).size, timeSpan: Math.max(...cluster.map(d => new Date(d.device_timestamp))) - Math.min(...cluster.map(d => new Date(d.device_timestamp))) }); } }); return clusters; }; const calculateDistance = (lat1, lon1, lat2, lon2) => { const R = 6371000; const φ1 = lat1 * Math.PI / 180; const φ2 = lat2 * Math.PI / 180; const Δφ = (lat2 - lat1) * Math.PI / 180; const Δλ = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }; const clusters = identifyClusters(clusterDetectionsArray); expect(clusters).to.have.length(1); // One cluster found expect(clusters[0].size).to.be.greaterThan(3); expect(clusters[0].uniqueDrones).to.be.greaterThan(3); expect(Math.abs(clusters[0].center.lat - clusterCenter.lat)).to.be.lessThan(0.01); }); }); describe('Real-time Detection Processing', () => { it('should handle rapid detection streams efficiently', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id, is_approved: true }); const processDetectionStream = async (detections) => { const results = []; const startTime = Date.now(); for (const detectionData of detections) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, ...detectionData }); // Simulate real-time processing const processingResult = { id: detection.id, processed_at: new Date(), threat_assessment: detectionData.rssi > -50 ? 'high' : 'medium', processing_time: Date.now() - startTime }; results.push(processingResult); } return { processed_count: results.length, total_time: Date.now() - startTime, average_processing_time: results.reduce((sum, r) => sum + r.processing_time, 0) / results.length, results }; }; // Generate rapid detection stream const detectionStream = []; for (let i = 0; i < 20; i++) { detectionStream.push({ geo_lat: 59.3293 + (Math.random() - 0.5) * 0.01, geo_lon: 18.0686 + (Math.random() - 0.5) * 0.01, device_timestamp: new Date(Date.now() + i * 1000), drone_type: 2, rssi: -60 - Math.random() * 30, drone_id: 4000 + Math.floor(i / 4) // Multiple detections per drone }); } const streamResults = await processDetectionStream(detectionStream); expect(streamResults.processed_count).to.equal(20); expect(streamResults.total_time).to.be.lessThan(5000); // Should process quickly expect(streamResults.average_processing_time).to.be.a('number'); }); it('should maintain detection accuracy under load', async () => { const tenant = await createTestTenant(); const device = await createTestDevice({ tenant_id: tenant.id }); // Test detection accuracy with varying signal strengths const testCases = [ { rssi: -40, expected_accuracy: 'high', expected_range: '<100m' }, { rssi: -60, expected_accuracy: 'medium', expected_range: '100-500m' }, { rssi: -80, expected_accuracy: 'low', expected_range: '>500m' } ]; const assessDetectionAccuracy = (rssi) => { let accuracy, estimatedRange; if (rssi > -50) { accuracy = 'high'; estimatedRange = '<100m'; } else if (rssi > -70) { accuracy = 'medium'; estimatedRange = '100-500m'; } else { accuracy = 'low'; estimatedRange = '>500m'; } return { accuracy, estimatedRange, confidence: Math.max(0, 100 + rssi) }; }; for (const testCase of testCases) { const detection = await models.DroneDetection.create({ device_id: device.id, tenant_id: tenant.id, geo_lat: 59.3293, geo_lon: 18.0686, device_timestamp: new Date(), drone_type: 2, rssi: testCase.rssi, drone_id: Math.floor(Math.random() * 10000) }); const accuracy = assessDetectionAccuracy(detection.rssi); expect(accuracy.accuracy).to.equal(testCase.expected_accuracy); expect(accuracy.estimatedRange).to.equal(testCase.expected_range); expect(accuracy.confidence).to.be.a('number'); } }); }); });