Files
drone-detector/server/tests/routes/detectors.test.js
2025-09-14 21:07:43 +02:00

415 lines
11 KiB
JavaScript

const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
const { expect } = require('chai');
const sinon = require('sinon');
const request = require('supertest');
const express = require('express');
const detectorsRoutes = require('../../routes/detectors');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, createTestDevice, generateTestToken } = require('../setup');
describe('Detectors Routes', () => {
let app, models, sequelize;
before(async () => {
({ models, sequelize } = await setupTestEnvironment());
// Setup express app for testing
app = express();
app.use(express.json());
app.use('/detectors', detectorsRoutes);
});
after(async () => {
await teardownTestEnvironment();
});
beforeEach(async () => {
await cleanDatabase();
});
describe('POST /detectors - Detection Data', () => {
it('should accept valid detection from approved device', async () => {
const tenant = await createTestTenant();
const device = await createTestDevice({
id: 1941875381,
tenant_id: tenant.id,
is_approved: true,
is_active: true
});
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(201);
expect(response.body.success).to.be.true;
expect(response.body.message).to.equal('Detection processed successfully');
});
it('should reject detection from unknown device', async () => {
const detectionData = {
device_id: 999999999, // Non-existent device
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(404);
expect(response.body.success).to.be.false;
expect(response.body.error).to.equal('Device not registered');
expect(response.body.registration_required).to.be.true;
});
it('should reject detection from unapproved device', async () => {
const device = await createTestDevice({
id: 1941875381,
is_approved: false,
is_active: true
});
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(403);
expect(response.body.success).to.be.false;
expect(response.body.error).to.equal('Device not approved');
expect(response.body.approval_required).to.be.true;
});
it('should handle drone type 0 (None) detections when debug enabled', async () => {
process.env.STORE_DRONE_TYPE0 = 'true';
const device = await createTestDevice({
is_approved: true,
is_active: true
});
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 0, // None type
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(201);
expect(response.body.success).to.be.true;
delete process.env.STORE_DRONE_TYPE0;
});
it('should skip drone type 0 detections when debug disabled', async () => {
process.env.STORE_DRONE_TYPE0 = 'false';
const device = await createTestDevice({
is_approved: true,
is_active: true
});
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 0,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.message).to.include('debug mode');
delete process.env.STORE_DRONE_TYPE0;
});
it('should validate required detection fields', async () => {
const device = await createTestDevice({ is_approved: true });
const invalidData = {
device_id: device.id,
// missing required fields
geo_lat: 59.3293
};
const response = await request(app)
.post('/detectors')
.send(invalidData);
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should validate coordinate ranges', async () => {
const device = await createTestDevice({ is_approved: true });
const invalidData = {
device_id: device.id,
geo_lat: 91, // Invalid latitude
geo_lon: 181, // Invalid longitude
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(invalidData);
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should trigger alerts for critical detections', async () => {
const device = await createTestDevice({ is_approved: true });
const criticalDetection = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2, // Orlan - military drone
rssi: -35, // Strong signal = close proximity
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(criticalDetection);
expect(response.status).to.equal(201);
expect(response.body.success).to.be.true;
});
it('should handle detection with optional fields', async () => {
const device = await createTestDevice({ is_approved: true });
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001,
confidence_level: 0.85,
signal_duration: 2500
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(201);
expect(response.body.success).to.be.true;
});
});
describe('POST /detectors - Heartbeat Data', () => {
it('should accept valid heartbeat', async () => {
const heartbeatData = {
type: 'heartbeat',
key: 'device_123_key',
device_id: 123,
geo_lat: 59.3293,
geo_lon: 18.0686,
signal_strength: -50,
battery_level: 85,
temperature: 22.5
};
const response = await request(app)
.post('/detectors')
.send(heartbeatData);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.message).to.equal('Heartbeat received');
});
it('should require key field for heartbeat', async () => {
const heartbeatData = {
type: 'heartbeat',
// missing key
device_id: 123
};
const response = await request(app)
.post('/detectors')
.send(heartbeatData);
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should handle heartbeat with minimal data', async () => {
const heartbeatData = {
type: 'heartbeat',
key: 'device_123_key'
};
const response = await request(app)
.post('/detectors')
.send(heartbeatData);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
});
it('should validate battery level range', async () => {
const heartbeatData = {
type: 'heartbeat',
key: 'device_123_key',
battery_level: 150 // Invalid range
};
const response = await request(app)
.post('/detectors')
.send(heartbeatData);
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should store heartbeat when debug enabled', async () => {
process.env.STORE_HEARTBEATS = 'true';
const heartbeatData = {
type: 'heartbeat',
key: 'device_123_key',
device_id: 123,
battery_level: 85
};
const response = await request(app)
.post('/detectors')
.send(heartbeatData);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
delete process.env.STORE_HEARTBEATS;
});
});
describe('Error Handling', () => {
it('should handle invalid JSON payload', async () => {
const response = await request(app)
.post('/detectors')
.set('Content-Type', 'application/json')
.send('invalid json{');
expect(response.status).to.equal(400);
});
it('should handle database connection errors', async () => {
// Mock database error
const originalFindOne = models.Device.findOne;
models.Device.findOne = sinon.stub().rejects(new Error('Database connection failed'));
const detectionData = {
device_id: 123,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
const response = await request(app)
.post('/detectors')
.send(detectionData);
expect(response.status).to.equal(500);
expect(response.body.success).to.be.false;
// Restore original method
models.Device.findOne = originalFindOne;
});
it('should handle missing required fields gracefully', async () => {
const response = await request(app)
.post('/detectors')
.send({});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.include('Invalid payload');
});
it('should log detection data for debugging', async () => {
process.env.LOG_ALL_DETECTIONS = 'true';
const consoleSpy = sinon.spy(console, 'log');
const device = await createTestDevice({ is_approved: true });
const detectionData = {
device_id: device.id,
geo_lat: 59.3293,
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
};
await request(app)
.post('/detectors')
.send(detectionData);
expect(consoleSpy.called).to.be.true;
consoleSpy.restore();
delete process.env.LOG_ALL_DETECTIONS;
});
});
});