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'); // Import all the routes and middleware for integration testing const authRoutes = require('../../routes/auth'); const detectorsRoutes = require('../../routes/detectors'); const detectionsRoutes = require('../../routes/detections'); const deviceRoutes = require('../../routes/device'); const { authenticateToken } = require('../../middleware/auth'); const { checkIPRestriction } = require('../../middleware/ip-restriction'); const AlertService = require('../../services/alertService'); const DroneTrackingService = require('../../services/droneTrackingService'); describe('Integration Tests', () => { let app, models, sequelize, alertService, trackingService; before(async () => { ({ models, sequelize } = await setupTestEnvironment()); // Initialize services alertService = new AlertService(); trackingService = new DroneTrackingService(); // Setup complete express app for integration testing app = express(); app.use(express.json()); // Add global middleware app.use(checkIPRestriction); // Add routes app.use('/auth', authRoutes); app.use('/detectors', detectorsRoutes); app.use(authenticateToken); app.use('/detections', detectionsRoutes); app.use('/devices', deviceRoutes); }); after(async () => { await teardownTestEnvironment(); }); beforeEach(async () => { await cleanDatabase(); alertService.activeAlerts.clear(); trackingService.clear(); }); describe('Complete User Registration and Login Flow', () => { it('should complete full user registration workflow', async () => { // 1. Create tenant with registration enabled const tenant = await createTestTenant({ slug: 'test-tenant', domain: 'test-tenant.example.com', allow_registration: true, auth_provider: 'local' }); // 2. Register new user const registrationResponse = await request(app) .post('/auth/register') .set('Host', 'test-tenant.example.com') .send({ username: 'newuser', email: 'newuser@example.com', password: 'SecurePassword123!', firstName: 'New', lastName: 'User' }); expect(registrationResponse.status).to.equal(201); expect(registrationResponse.body.success).to.be.true; expect(registrationResponse.body.data.user.username).to.equal('newuser'); // 3. Login with new user const loginResponse = await request(app) .post('/auth/login') .send({ username: 'newuser', password: 'SecurePassword123!' }); expect(loginResponse.status).to.equal(200); expect(loginResponse.body.success).to.be.true; expect(loginResponse.body.data.token).to.exist; const token = loginResponse.body.data.token; // 4. Access protected endpoint const protectedResponse = await request(app) .get('/auth/me') .set('Authorization', `Bearer ${token}`); expect(protectedResponse.status).to.equal(200); expect(protectedResponse.body.data.username).to.equal('newuser'); // 5. Verify user exists in database with correct tenant const user = await models.User.findOne({ where: { username: 'newuser' }, include: [models.Tenant] }); expect(user).to.exist; expect(user.Tenant.slug).to.equal('test-tenant'); expect(user.is_active).to.be.true; }); it('should prevent registration when disabled', async () => { const tenant = await createTestTenant({ slug: 'no-reg-tenant', allow_registration: false }); const response = await request(app) .post('/auth/register') .set('Host', 'no-reg-tenant.example.com') .send({ username: 'blocked', email: 'blocked@example.com', password: 'SecurePassword123!' }); expect(response.status).to.equal(403); expect(response.body.success).to.be.false; expect(response.body.message).to.include('Registration not allowed'); }); }); describe('Complete Device Registration and Detection Flow', () => { it('should complete full device lifecycle workflow', async () => { const tenant = await createTestTenant(); const user = await createTestUser({ tenant_id: tenant.id, role: 'admin' }); const token = generateTestToken(user, tenant); // 1. Register new device const deviceData = { id: 1941875381, name: 'Integration Test Device', geo_lat: 59.3293, geo_lon: 18.0686, location_description: 'Test Security Facility' }; const registrationResponse = await request(app) .post('/devices') .set('Authorization', `Bearer ${token}`) .send(deviceData); expect(registrationResponse.status).to.equal(201); expect(registrationResponse.body.success).to.be.true; // 2. Device is initially unapproved - detection should be rejected const unapprovedDetection = { device_id: deviceData.id, geo_lat: 59.3293, geo_lon: 18.0686, device_timestamp: Date.now(), drone_type: 2, rssi: -65, freq: 2400, drone_id: 1001 }; const rejectedResponse = await request(app) .post('/detectors') .send(unapprovedDetection); expect(rejectedResponse.status).to.equal(403); expect(rejectedResponse.body.approval_required).to.be.true; // 3. Approve device const approvalResponse = await request(app) .put(`/devices/${deviceData.id}`) .set('Authorization', `Bearer ${token}`) .send({ is_approved: true }); expect(approvalResponse.status).to.equal(200); // 4. Send detection from approved device const detectionResponse = await request(app) .post('/detectors') .send(unapprovedDetection); expect(detectionResponse.status).to.equal(201); expect(detectionResponse.body.success).to.be.true; // 5. Verify detection is stored and visible via API const detectionsResponse = await request(app) .get('/detections') .set('Authorization', `Bearer ${token}`); expect(detectionsResponse.status).to.equal(200); expect(detectionsResponse.body.data.detections).to.have.length(1); expect(detectionsResponse.body.data.detections[0].device_id).to.equal(deviceData.id); // 6. Verify device status is updated const deviceStatusResponse = await request(app) .get('/devices') .set('Authorization', `Bearer ${token}`); expect(deviceStatusResponse.status).to.equal(200); const devices = deviceStatusResponse.body.data; const testDevice = devices.find(d => d.id === deviceData.id); expect(testDevice).to.exist; expect(testDevice.recent_detections_count).to.be.greaterThan(0); }); it('should enforce tenant isolation in device operations', async () => { // Setup two tenants const tenant1 = await createTestTenant({ slug: 'tenant1' }); const tenant2 = await createTestTenant({ slug: 'tenant2' }); const user1 = await createTestUser({ tenant_id: tenant1.id, role: 'admin' }); const user2 = await createTestUser({ tenant_id: tenant2.id, role: 'admin' }); const token1 = generateTestToken(user1, tenant1); const token2 = generateTestToken(user2, tenant2); // Create device for tenant1 const device1 = await createTestDevice({ id: 111, tenant_id: tenant1.id, is_approved: true }); // Create device for tenant2 const device2 = await createTestDevice({ id: 222, tenant_id: tenant2.id, is_approved: true }); // User1 should only see tenant1 devices const tenant1Response = await request(app) .get('/devices') .set('Authorization', `Bearer ${token1}`); expect(tenant1Response.status).to.equal(200); const tenant1Devices = tenant1Response.body.data; expect(tenant1Devices).to.have.length(1); expect(tenant1Devices[0].id).to.equal(111); // User2 should only see tenant2 devices const tenant2Response = await request(app) .get('/devices') .set('Authorization', `Bearer ${token2}`); expect(tenant2Response.status).to.equal(200); const tenant2Devices = tenant2Response.body.data; expect(tenant2Devices).to.have.length(1); expect(tenant2Devices[0].id).to.equal(222); }); }); describe('Complete Alert and Tracking Workflow', () => { it('should trigger alerts and track drone movement', async () => { const tenant = await createTestTenant(); const user = await createTestUser({ tenant_id: tenant.id }); const device = await createTestDevice({ tenant_id: tenant.id, is_approved: true }); const token = generateTestToken(user, tenant); // Create alert rule const alertRule = await models.AlertRule.create({ tenant_id: tenant.id, name: 'Critical Proximity Alert', drone_type: 2, min_rssi: -60, is_active: true }); // Mock socket.io for real-time notifications const mockIo = { emit: sinon.stub(), emitToDashboard: sinon.stub(), emitToDevice: sinon.stub(), to: (room) => ({ emit: sinon.stub() }) }; // Simulate drone approach with multiple detections const droneId = 12345; const detections = [ { rssi: -80, geo_lat: 59.3200, distance: 5000 }, { rssi: -70, geo_lat: 59.3220, distance: 2000 }, { rssi: -55, geo_lat: 59.3240, distance: 800 }, { rssi: -45, geo_lat: 59.3260, distance: 200 } ]; for (let i = 0; i < detections.length; i++) { const detection = detections[i]; // Send detection const response = await request(app) .post('/detectors') .send({ device_id: device.id, geo_lat: detection.geo_lat, geo_lon: 18.0686, device_timestamp: Date.now() + (i * 30000), // 30 seconds apart drone_type: 2, rssi: detection.rssi, freq: 2400, drone_id: droneId }); expect(response.status).to.equal(201); // Process alert for this detection const detectionRecord = await models.DroneDetection.findOne({ where: { drone_id: droneId }, order: [['id', 'DESC']] }); await alertService.processDetectionAlert(detectionRecord, mockIo); trackingService.trackDetection(detectionRecord); } // Verify alerts were triggered const alertLogs = await models.AlertLog.findAll({ where: { device_id: device.id } }); expect(alertLogs.length).to.be.greaterThan(0); // Verify critical alerts for close proximity const criticalAlerts = alertLogs.filter(alert => alert.threat_level === 'critical'); expect(criticalAlerts.length).to.be.greaterThan(0); // Verify drone tracking const activeTracks = trackingService.getActiveTracking(); expect(activeTracks).to.have.length(1); expect(activeTracks[0].droneId).to.equal(droneId); expect(activeTracks[0].movementPattern.isApproaching).to.be.true; // Verify detections are visible via API const detectionsResponse = await request(app) .get('/detections') .set('Authorization', `Bearer ${token}`); expect(detectionsResponse.status).to.equal(200); expect(detectionsResponse.body.data.detections).to.have.length(4); // Verify real-time notifications were sent expect(mockIo.emitToDashboard.called).to.be.true; expect(mockIo.emitToDevice.called).to.be.true; }); it('should handle high-frequency detection stream', async () => { const device = await createTestDevice({ is_approved: true }); const droneId = 99999; // Simulate rapid detection stream (realistic for active tracking) const detectionPromises = []; for (let i = 0; i < 10; i++) { const detectionPromise = request(app) .post('/detectors') .send({ device_id: device.id, geo_lat: 59.3293 + (i * 0.001), // Slight movement geo_lon: 18.0686, device_timestamp: Date.now() + (i * 1000), // 1 second apart drone_type: 2, rssi: -60 + i, // Varying signal strength freq: 2400, drone_id: droneId }); detectionPromises.push(detectionPromise); } const responses = await Promise.all(detectionPromises); // All detections should be accepted responses.forEach(response => { expect(response.status).to.equal(201); }); // Verify all detections are stored const storedDetections = await models.DroneDetection.findAll({ where: { drone_id: droneId } }); expect(storedDetections).to.have.length(10); // Verify tracking service handles rapid updates storedDetections.forEach(detection => { trackingService.trackDetection(detection); }); const droneTrack = trackingService.getDroneHistory(droneId); expect(droneTrack.detectionHistory).to.have.length(10); expect(droneTrack.movementPattern.totalDistance).to.be.greaterThan(0); }); }); describe('Multi-Tenant Data Isolation', () => { it('should completely isolate tenant data across all endpoints', async () => { // Create two complete tenant environments const tenant1 = await createTestTenant({ slug: 'isolation-test-1' }); const tenant2 = await createTestTenant({ slug: 'isolation-test-2' }); const user1 = await createTestUser({ tenant_id: tenant1.id, role: 'admin' }); const user2 = await createTestUser({ tenant_id: tenant2.id, role: 'admin' }); const device1 = await createTestDevice({ tenant_id: tenant1.id, is_approved: true }); const device2 = await createTestDevice({ tenant_id: tenant2.id, is_approved: true }); const token1 = generateTestToken(user1, tenant1); const token2 = generateTestToken(user2, tenant2); // Create alert rules for each tenant await models.AlertRule.create({ tenant_id: tenant1.id, name: 'Tenant 1 Rule', is_active: true }); await models.AlertRule.create({ tenant_id: tenant2.id, name: 'Tenant 2 Rule', is_active: true }); // Send detections from each device await request(app).post('/detectors').send({ device_id: device1.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({ device_id: device2.id, geo_lat: 60.1699, geo_lon: 24.9384, device_timestamp: Date.now(), drone_type: 3, rssi: -70, freq: 2400, drone_id: 2001 }); // Test device isolation const devices1 = await request(app) .get('/devices') .set('Authorization', `Bearer ${token1}`); const devices2 = await request(app) .get('/devices') .set('Authorization', `Bearer ${token2}`); expect(devices1.body.data).to.have.length(1); expect(devices2.body.data).to.have.length(1); expect(devices1.body.data[0].id).to.equal(device1.id); expect(devices2.body.data[0].id).to.equal(device2.id); // Test detection isolation const detections1 = await request(app) .get('/detections') .set('Authorization', `Bearer ${token1}`); const detections2 = await request(app) .get('/detections') .set('Authorization', `Bearer ${token2}`); expect(detections1.body.data.detections).to.have.length(1); expect(detections2.body.data.detections).to.have.length(1); expect(detections1.body.data.detections[0].drone_id).to.equal(1001); expect(detections2.body.data.detections[0].drone_id).to.equal(2001); // Test cross-tenant access denial const detection1Id = detections1.body.data.detections[0].id; const crossTenantAccess = await request(app) .get(`/detections/${detection1Id}`) .set('Authorization', `Bearer ${token2}`); // Wrong tenant token expect(crossTenantAccess.status).to.equal(404); }); }); describe('Error Recovery and Edge Cases', () => { it('should handle database connection failures gracefully', async () => { // Mock database connection failure const originalFindOne = models.Device.findOne; models.Device.findOne = sinon.stub().rejects(new Error('Database connection lost')); const response = await request(app) .post('/detectors') .send({ 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 }); expect(response.status).to.equal(500); expect(response.body.success).to.be.false; // Restore original method models.Device.findOne = originalFindOne; }); it('should handle malformed detection data', async () => { const malformedPayloads = [ {}, // Empty object { device_id: 'invalid' }, // Invalid device_id type { device_id: 123, geo_lat: 'invalid' }, // Invalid coordinate { device_id: 123, geo_lat: 91 }, // Out of range coordinate null, // Null payload 'invalid json string' // Invalid JSON ]; for (const payload of malformedPayloads) { const response = await request(app) .post('/detectors') .send(payload); expect(response.status).to.be.oneOf([400, 500]); expect(response.body.success).to.be.false; } }); it('should handle concurrent user operations', async () => { const tenant = await createTestTenant(); const user = await createTestUser({ tenant_id: tenant.id, role: 'admin' }); const token = generateTestToken(user, tenant); // Simulate concurrent device registrations const devicePromises = []; for (let i = 0; i < 5; i++) { const devicePromise = request(app) .post('/devices') .set('Authorization', `Bearer ${token}`) .send({ id: 1000 + i, name: `Concurrent Device ${i}`, geo_lat: 59.3293, geo_lon: 18.0686 }); devicePromises.push(devicePromise); } const responses = await Promise.all(devicePromises); // All should succeed responses.forEach(response => { expect(response.status).to.equal(201); }); // Verify all devices were created const devices = await models.Device.findAll({ where: { tenant_id: tenant.id } }); expect(devices).to.have.length(5); }); }); describe('Performance Under Load', () => { it('should handle burst of detections efficiently', async () => { const device = await createTestDevice({ is_approved: true }); const startTime = Date.now(); // Send 50 detections rapidly const detectionPromises = []; for (let i = 0; i < 50; i++) { const promise = request(app) .post('/detectors') .send({ device_id: device.id, geo_lat: 59.3293, geo_lon: 18.0686, device_timestamp: Date.now() + i, drone_type: 2, rssi: -65, freq: 2400, drone_id: 1000 + (i % 10) // 10 different drones }); detectionPromises.push(promise); } const responses = await Promise.all(detectionPromises); const endTime = Date.now(); // All should complete successfully const successCount = responses.filter(r => r.status === 201).length; expect(successCount).to.equal(50); // Should complete within reasonable time (adjust threshold as needed) const duration = endTime - startTime; expect(duration).to.be.lessThan(10000); // 10 seconds max console.log(`✅ Processed ${successCount} detections in ${duration}ms`); }); }); });