const { describe, it, beforeEach, afterEach, before, after } = require('mocha'); const { expect } = require('chai'); const sinon = require('sinon'); const DroneTrackingService = require('../../services/droneTrackingService'); const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestDevice, createTestDetection } = require('../setup'); describe('DroneTrackingService', () => { let models, sequelize, trackingService; before(async () => { ({ models, sequelize } = await setupTestEnvironment()); trackingService = new DroneTrackingService(); }); after(async () => { await teardownTestEnvironment(); }); beforeEach(async () => { await cleanDatabase(); trackingService.clear(); trackingService.removeAllListeners(); }); describe('trackDetection', () => { it('should track new drone detection', async () => { const device = await createTestDevice(); const detection = await createTestDetection({ device_id: device.id, drone_id: 12345, rssi: -60, geo_lat: 59.3293, geo_lon: 18.0686 }); trackingService.trackDetection(detection); expect(trackingService.activeDrones.has(12345)).to.be.true; const droneData = trackingService.activeDrones.get(12345); expect(droneData.currentPosition.lat).to.equal(59.3293); expect(droneData.currentPosition.lon).to.equal(18.0686); }); it('should update existing drone tracking', async () => { const device = await createTestDevice(); const detection1 = await createTestDetection({ device_id: device.id, drone_id: 12345, rssi: -60, geo_lat: 59.3293, geo_lon: 18.0686 }); const detection2 = await createTestDetection({ device_id: device.id, drone_id: 12345, rssi: -55, geo_lat: 59.3300, // Moved slightly geo_lon: 18.0690 }); trackingService.trackDetection(detection1); trackingService.trackDetection(detection2); const droneData = trackingService.activeDrones.get(12345); expect(droneData.detectionHistory).to.have.length(2); expect(droneData.currentPosition.lat).to.equal(59.3300); expect(droneData.averageRSSI).to.be.closeTo(-57.5, 0.1); }); it('should calculate movement between detections', async () => { const device = await createTestDevice(); const detection1 = await createTestDetection({ device_id: device.id, drone_id: 12345, geo_lat: 59.3293, geo_lon: 18.0686 }); const detection2 = await createTestDetection({ device_id: device.id, drone_id: 12345, geo_lat: 59.3393, // ~1km north geo_lon: 18.0686 }); trackingService.trackDetection(detection1); trackingService.trackDetection(detection2); const droneData = trackingService.activeDrones.get(12345); expect(droneData.movementPattern.totalDistance).to.be.greaterThan(0); expect(droneData.movementPattern.direction).to.exist; }); it('should emit movement alert for significant movement', (done) => { const device = createTestDevice(); trackingService.on('movement_alert', (alertData) => { expect(alertData).to.exist; expect(alertData.droneId).to.equal(12345); expect(alertData.analysis).to.exist; done(); }); // Create detections showing rapid movement const detection1 = { drone_id: 12345, device_id: 1, geo_lat: 59.3293, geo_lon: 18.0686, rssi: -60, server_timestamp: new Date() }; const detection2 = { drone_id: 12345, device_id: 1, geo_lat: 59.3393, // 1km movement geo_lon: 18.0686, rssi: -55, server_timestamp: new Date(Date.now() + 30000) // 30 seconds later }; trackingService.trackDetection(detection1); setTimeout(() => { trackingService.trackDetection(detection2); }, 100); }); it('should detect approach patterns', async () => { const device = await createTestDevice(); const droneId = 12345; // Simulate drone approaching (RSSI getting stronger) const detections = [ { rssi: -80, geo_lat: 59.3200, geo_lon: 18.0600 }, { rssi: -70, geo_lat: 59.3220, geo_lon: 18.0620 }, { rssi: -60, geo_lat: 59.3240, geo_lon: 18.0640 }, { rssi: -50, geo_lat: 59.3260, geo_lon: 18.0660 } ]; for (const detection of detections) { await trackingService.trackDetection({ drone_id: droneId, device_id: device.id, ...detection, server_timestamp: new Date() }); } const droneData = trackingService.activeDrones.get(droneId); expect(droneData.movementPattern.isApproaching).to.be.true; }); it('should detect retreat patterns', async () => { const device = await createTestDevice(); const droneId = 12345; // Simulate drone retreating (RSSI getting weaker) const detections = [ { rssi: -50, geo_lat: 59.3260, geo_lon: 18.0660 }, { rssi: -60, geo_lat: 59.3240, geo_lon: 18.0640 }, { rssi: -70, geo_lat: 59.3220, geo_lon: 18.0620 }, { rssi: -80, geo_lat: 59.3200, geo_lon: 18.0600 } ]; for (const detection of detections) { await trackingService.trackDetection({ drone_id: droneId, device_id: device.id, ...detection, server_timestamp: new Date() }); } const droneData = trackingService.activeDrones.get(droneId); expect(droneData.movementPattern.isRetreating).to.be.true; }); }); describe('analyzeMovement', () => { it('should analyze movement patterns correctly', () => { const positions = [ { lat: 59.3293, lon: 18.0686, timestamp: new Date('2023-01-01T10:00:00Z') }, { lat: 59.3300, lon: 18.0690, timestamp: new Date('2023-01-01T10:01:00Z') }, { lat: 59.3310, lon: 18.0695, timestamp: new Date('2023-01-01T10:02:00Z') } ]; const analysis = trackingService.analyzeMovement(positions); expect(analysis.totalDistance).to.be.greaterThan(0); expect(analysis.averageSpeed).to.be.greaterThan(0); expect(analysis.direction).to.exist; expect(analysis.isLinear).to.be.a('boolean'); }); it('should detect circular patterns', () => { // Create positions in a rough circle const positions = []; const centerLat = 59.3293; const centerLon = 18.0686; const radius = 0.001; // Small radius for (let i = 0; i < 8; i++) { const angle = (i / 8) * 2 * Math.PI; positions.push({ lat: centerLat + radius * Math.cos(angle), lon: centerLon + radius * Math.sin(angle), timestamp: new Date(Date.now() + i * 60000) }); } const analysis = trackingService.analyzeMovement(positions); expect(analysis.isCircular).to.be.true; }); it('should calculate correct speeds', () => { const positions = [ { lat: 59.3293, lon: 18.0686, timestamp: new Date('2023-01-01T10:00:00Z') }, { lat: 59.3303, lon: 18.0686, timestamp: new Date('2023-01-01T10:01:00Z') } // ~1km in 1 minute ]; const analysis = trackingService.analyzeMovement(positions); // Should detect high speed (60 km/h) expect(analysis.averageSpeed).to.be.greaterThan(50); expect(analysis.maxSpeed).to.be.greaterThan(50); }); }); describe('cleanupOldTracks', () => { it('should remove old inactive drone tracks', async () => { const droneId = 12345; // Add drone track trackingService.activeDrones.set(droneId, { droneId, firstSeen: new Date(Date.now() - 3600000), // 1 hour ago lastSeen: new Date(Date.now() - 1800000), // 30 minutes ago detectionHistory: [] }); trackingService.cleanupOldTracks(); expect(trackingService.activeDrones.has(droneId)).to.be.false; }); it('should keep recent drone tracks', async () => { const droneId = 12345; // Add recent drone track trackingService.activeDrones.set(droneId, { droneId, firstSeen: new Date(Date.now() - 300000), // 5 minutes ago lastSeen: new Date(Date.now() - 60000), // 1 minute ago detectionHistory: [] }); trackingService.cleanupOldTracks(); expect(trackingService.activeDrones.has(droneId)).to.be.true; }); }); describe('getActiveTracking', () => { it('should return all active drone tracks', async () => { const device = await createTestDevice(); // Add multiple drones const detection1 = await createTestDetection({ device_id: device.id, drone_id: 12345 }); const detection2 = await createTestDetection({ device_id: device.id, drone_id: 67890 }); trackingService.trackDetection(detection1); trackingService.trackDetection(detection2); const activeTracks = trackingService.getActiveTracking(); expect(activeTracks).to.be.an('array'); expect(activeTracks).to.have.length(2); expect(activeTracks.map(t => t.droneId)).to.include.members([12345, 67890]); }); it('should include movement analysis in active tracks', async () => { const device = await createTestDevice(); const detection = await createTestDetection({ device_id: device.id, drone_id: 12345 }); trackingService.trackDetection(detection); const activeTracks = trackingService.getActiveTracking(); expect(activeTracks[0].movementPattern).to.exist; expect(activeTracks[0].currentPosition).to.exist; expect(activeTracks[0].detectionCount).to.be.a('number'); }); }); describe('getDroneHistory', () => { it('should return detection history for specific drone', async () => { const device = await createTestDevice(); const droneId = 12345; // Add multiple detections const detection1 = await createTestDetection({ device_id: device.id, drone_id: droneId }); const detection2 = await createTestDetection({ device_id: device.id, drone_id: droneId }); trackingService.trackDetection(detection1); trackingService.trackDetection(detection2); const history = trackingService.getDroneHistory(droneId); expect(history).to.exist; expect(history.detectionHistory).to.have.length(2); expect(history.droneId).to.equal(droneId); }); it('should return null for unknown drone', () => { const history = trackingService.getDroneHistory(99999); expect(history).to.be.null; }); }); describe('Distance and Speed Calculations', () => { it('should calculate distance between coordinates correctly', () => { const lat1 = 59.3293; const lon1 = 18.0686; const lat2 = 59.3393; // ~1.1km north const lon2 = 18.0686; const distance = trackingService.calculateDistance(lat1, lon1, lat2, lon2); expect(distance).to.be.closeTo(1110, 50); // ~1.1km in meters }); it('should calculate bearing correctly', () => { const lat1 = 59.3293; const lon1 = 18.0686; const lat2 = 59.3393; // North const lon2 = 18.0686; const bearing = trackingService.calculateBearing(lat1, lon1, lat2, lon2); expect(bearing).to.be.closeTo(0, 5); // Should be close to 0 degrees (north) }); it('should handle speed calculations with time differences', () => { const pos1 = { lat: 59.3293, lon: 18.0686, timestamp: new Date('2023-01-01T10:00:00Z') }; const pos2 = { lat: 59.3393, lon: 18.0686, timestamp: new Date('2023-01-01T10:01:00Z') // 1 minute later }; const speed = trackingService.calculateSpeed(pos1, pos2); expect(speed).to.be.greaterThan(60); // Should be ~66 km/h (1.1km in 1 min) }); }); describe('Threat Level Assessment', () => { it('should assess higher threat for approaching drones', async () => { const device = await createTestDevice(); const droneId = 12345; // Simulate approaching drone const detections = [ { rssi: -80, server_timestamp: new Date(Date.now() - 180000) }, { rssi: -70, server_timestamp: new Date(Date.now() - 120000) }, { rssi: -60, server_timestamp: new Date(Date.now() - 60000) }, { rssi: -50, server_timestamp: new Date() } ]; for (const detection of detections) { trackingService.trackDetection({ drone_id: droneId, device_id: device.id, geo_lat: 59.3293, geo_lon: 18.0686, ...detection }); } const droneData = trackingService.activeDrones.get(droneId); expect(droneData.threatAssessment.level).to.be.oneOf(['high', 'critical']); }); it('should assess lower threat for retreating drones', async () => { const device = await createTestDevice(); const droneId = 12345; // Simulate retreating drone const detections = [ { rssi: -50, server_timestamp: new Date(Date.now() - 180000) }, { rssi: -60, server_timestamp: new Date(Date.now() - 120000) }, { rssi: -70, server_timestamp: new Date(Date.now() - 60000) }, { rssi: -80, server_timestamp: new Date() } ]; for (const detection of detections) { trackingService.trackDetection({ drone_id: droneId, device_id: device.id, geo_lat: 59.3293, geo_lon: 18.0686, ...detection }); } const droneData = trackingService.activeDrones.get(droneId); expect(droneData.threatAssessment.level).to.be.oneOf(['low', 'medium']); }); }); });