Files
drone-detector/server/tests/services/droneTrackingService.test.js
2025-09-15 14:49:37 +02:00

434 lines
14 KiB
JavaScript

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']);
});
});
});