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 { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, createTestDevice, generateTestToken } = require('../setup'); const detectorsRoutes = require('../../routes/detectors'); describe('Detectors Routes', () => { let app, models, sequelize; before(async () => { ({ models, sequelize } = await setupTestEnvironment()); // Setup express app for testing app = express(); app.use(express.json()); // Add mock Socket.io middleware for tests app.use((req, res, next) => { req.io = { emit: () => {}, // Mock emit function that does nothing emitToDashboard: () => {}, emitToDevice: () => {} }; next(); }); 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 }; 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 required fields', async () => { const heartbeatData = { type: 'heartbeat' // Missing required key field }; 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 }; 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; }); }); });