Fix jwt-token

This commit is contained in:
2025-09-14 21:07:43 +02:00
parent d6293dd8ba
commit 019eb8c2b2
20 changed files with 7185 additions and 29 deletions

View File

@@ -0,0 +1,389 @@
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 authRoutes = require('../../routes/auth');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, generateTestToken } = require('../setup');
describe('Auth Routes', () => {
let app, models, sequelize;
before(async () => {
({ models, sequelize } = await setupTestEnvironment());
// Setup express app for testing
app = express();
app.use(express.json());
app.use('/auth', authRoutes);
});
after(async () => {
await teardownTestEnvironment();
});
beforeEach(async () => {
await cleanDatabase();
});
describe('POST /auth/login', () => {
it('should login with valid credentials', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({
username: 'testuser',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
tenant_id: tenant.id
});
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'password'
});
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.data.token).to.exist;
expect(response.body.data.user.username).to.equal('testuser');
});
it('should reject invalid username', async () => {
const response = await request(app)
.post('/auth/login')
.send({
username: 'nonexistent',
password: 'password'
});
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
expect(response.body.message).to.equal('Invalid credentials');
});
it('should reject invalid password', async () => {
const user = await createTestUser({
username: 'testuser',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
});
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'wrongpassword'
});
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
});
it('should reject inactive user', async () => {
const user = await createTestUser({
username: 'inactive',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
is_active: false
});
const response = await request(app)
.post('/auth/login')
.send({
username: 'inactive',
password: 'password'
});
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
expect(response.body.message).to.equal('Account is inactive');
});
it('should include tenant information in JWT token', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({
username: 'testuser',
password: '$2b$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
tenant_id: tenant.id
});
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'password'
});
expect(response.status).to.equal(200);
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(response.body.data.token, process.env.JWT_SECRET || 'test-secret');
expect(decoded.tenantId).to.equal(tenant.slug);
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser'
// missing password
});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should handle database errors gracefully', async () => {
// Mock database error
const originalFindOne = models.User.findOne;
models.User.findOne = sinon.stub().rejects(new Error('Database error'));
const response = await request(app)
.post('/auth/login')
.send({
username: 'testuser',
password: 'password'
});
expect(response.status).to.equal(500);
expect(response.body.success).to.be.false;
// Restore original method
models.User.findOne = originalFindOne;
});
});
describe('POST /auth/register', () => {
it('should register new user when registration allowed', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'newuser',
email: 'new@example.com',
password: 'password123',
firstName: 'New',
lastName: 'User'
});
expect(response.status).to.equal(201);
expect(response.body.success).to.be.true;
expect(response.body.data.user.username).to.equal('newuser');
});
it('should reject registration when not allowed', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: false
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'newuser',
email: 'new@example.com',
password: 'password123'
});
expect(response.status).to.equal(403);
expect(response.body.success).to.be.false;
expect(response.body.message).to.include('Registration not allowed');
});
it('should reject duplicate username', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true
});
await createTestUser({
username: 'existing',
tenant_id: tenant.id
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'existing',
email: 'new@example.com',
password: 'password123'
});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
expect(response.body.message).to.include('already exists');
});
it('should reject duplicate email', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true
});
await createTestUser({
username: 'existing',
email: 'existing@example.com',
tenant_id: tenant.id
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'newuser',
email: 'existing@example.com',
password: 'password123'
});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should validate password strength', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'newuser',
email: 'new@example.com',
password: '123' // weak password
});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
expect(response.body.message).to.include('password');
});
it('should validate email format', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.send({
username: 'newuser',
email: 'invalid-email',
password: 'password123'
});
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
});
it('should enforce IP restrictions during registration', async () => {
const tenant = await createTestTenant({
slug: 'test-tenant',
allow_registration: true,
ip_restrictions_enabled: true,
allowed_ips: '192.168.1.1'
});
const response = await request(app)
.post('/auth/register')
.set('Host', 'test-tenant.example.com')
.set('X-Forwarded-For', '192.168.2.1') // Not allowed IP
.send({
username: 'newuser',
email: 'new@example.com',
password: 'password123'
});
expect(response.status).to.equal(403);
expect(response.body.success).to.be.false;
expect(response.body.message).to.include('IP address not allowed');
});
});
describe('POST /auth/refresh', () => {
it('should refresh valid token', async () => {
const user = await createTestUser();
const token = generateTestToken(user);
const response = await request(app)
.post('/auth/refresh')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.data.token).to.exist;
expect(response.body.data.token).to.not.equal(token); // New token
});
it('should reject refresh without token', async () => {
const response = await request(app)
.post('/auth/refresh');
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
});
it('should reject refresh with invalid token', async () => {
const response = await request(app)
.post('/auth/refresh')
.set('Authorization', 'Bearer invalid.token');
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
});
});
describe('POST /auth/logout', () => {
it('should logout successfully', async () => {
const user = await createTestUser();
const token = generateTestToken(user);
const response = await request(app)
.post('/auth/logout')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.message).to.equal('Logged out successfully');
});
it('should logout without token', async () => {
const response = await request(app)
.post('/auth/logout');
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
});
});
describe('GET /auth/me', () => {
it('should return current user info', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/auth/me')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.data.username).to.equal(user.username);
expect(response.body.data.email).to.equal(user.email);
});
it('should require authentication', async () => {
const response = await request(app)
.get('/auth/me');
expect(response.status).to.equal(401);
expect(response.body.success).to.be.false;
});
});
});

View File

@@ -0,0 +1,393 @@
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 detectionsRoutes = require('../../routes/detections');
const { authenticateToken } = require('../../middleware/auth');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant, createTestDevice, createTestDetection, generateTestToken } = require('../setup');
describe('Detections Routes', () => {
let app, models, sequelize;
before(async () => {
({ models, sequelize } = await setupTestEnvironment());
// Setup express app for testing
app = express();
app.use(express.json());
app.use(authenticateToken);
app.use('/detections', detectionsRoutes);
});
after(async () => {
await teardownTestEnvironment();
});
beforeEach(async () => {
await cleanDatabase();
});
describe('GET /detections', () => {
it('should return detections for user tenant', async () => {
const tenant = await createTestTenant({ slug: 'test-tenant' });
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({ device_id: device.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.data.detections).to.be.an('array');
expect(response.body.data.detections).to.have.length(1);
expect(response.body.data.detections[0].id).to.equal(detection.id);
});
it('should not return detections from other tenants', async () => {
const tenant1 = await createTestTenant({ slug: 'tenant1' });
const tenant2 = await createTestTenant({ slug: 'tenant2' });
const user1 = await createTestUser({ tenant_id: tenant1.id });
const device1 = await createTestDevice({ tenant_id: tenant1.id });
const device2 = await createTestDevice({ tenant_id: tenant2.id });
await createTestDetection({ device_id: device1.id });
await createTestDetection({ device_id: device2.id });
const token = generateTestToken(user1, tenant1);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1);
});
it('should support pagination', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
// Create multiple detections
for (let i = 0; i < 15; i++) {
await createTestDetection({ device_id: device.id });
}
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections?page=1&limit=10')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(10);
expect(response.body.data.pagination.totalPages).to.equal(2);
expect(response.body.data.pagination.hasNextPage).to.be.true;
});
it('should support sorting', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
const detection1 = await createTestDetection({
device_id: device.id,
server_timestamp: new Date('2023-01-01')
});
const detection2 = await createTestDetection({
device_id: device.id,
server_timestamp: new Date('2023-01-02')
});
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections?sort=server_timestamp&order=desc')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections[0].id).to.equal(detection2.id);
});
it('should support filtering by device', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device1 = await createTestDevice({ tenant_id: tenant.id });
const device2 = await createTestDevice({ tenant_id: tenant.id });
await createTestDetection({ device_id: device1.id });
await createTestDetection({ device_id: device2.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get(`/detections?device_id=${device1.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1);
expect(response.body.data.detections[0].device_id).to.equal(device1.id);
});
it('should support filtering by drone type', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
await createTestDetection({ device_id: device.id, drone_type: 2 });
await createTestDetection({ device_id: device.id, drone_type: 3 });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections?drone_type=2')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1);
expect(response.body.data.detections[0].drone_type).to.equal(2);
});
it('should support filtering by date range', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
await createTestDetection({
device_id: device.id,
server_timestamp: new Date('2023-01-01')
});
await createTestDetection({
device_id: device.id,
server_timestamp: new Date('2023-02-01')
});
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections?start_date=2023-01-15&end_date=2023-02-15')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1);
});
it('should exclude drone type 0 by default', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
await createTestDetection({ device_id: device.id, drone_type: 0 });
await createTestDetection({ device_id: device.id, drone_type: 2 });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.detections).to.have.length(1);
expect(response.body.data.detections[0].drone_type).to.equal(2);
});
it('should enhance detections with drone type info', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({
device_id: device.id,
drone_type: 2
});
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
const returnedDetection = response.body.data.detections[0];
expect(returnedDetection.drone_type_info).to.exist;
expect(returnedDetection.drone_type_info.name).to.exist;
expect(returnedDetection.drone_type_info.threat_level).to.exist;
});
it('should include device information', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({
tenant_id: tenant.id,
name: 'Test Device'
});
await createTestDetection({ device_id: device.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
const detection = response.body.data.detections[0];
expect(detection.device).to.exist;
expect(detection.device.name).to.equal('Test Device');
});
it('should reject request without tenant', async () => {
const user = await createTestUser();
const token = generateTestToken(user); // No tenant
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(400);
expect(response.body.success).to.be.false;
expect(response.body.message).to.equal('Unable to determine tenant');
});
it('should reject request for inactive tenant', async () => {
const tenant = await createTestTenant({
slug: 'inactive-tenant',
is_active: false
});
const user = await createTestUser({ tenant_id: tenant.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(404);
expect(response.body.success).to.be.false;
});
it('should handle invalid pagination parameters', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections?page=-1&limit=1000')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
// Should use default values
expect(response.body.data.pagination.page).to.equal(1);
expect(response.body.data.pagination.limit).to.equal(20);
});
});
describe('GET /detections/:id', () => {
it('should return specific detection', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({ device_id: device.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get(`/detections/${detection.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.data.id).to.equal(detection.id);
});
it('should not return detection from other tenant', async () => {
const tenant1 = await createTestTenant({ slug: 'tenant1' });
const tenant2 = await createTestTenant({ slug: 'tenant2' });
const user1 = await createTestUser({ tenant_id: tenant1.id });
const device2 = await createTestDevice({ tenant_id: tenant2.id });
const detection2 = await createTestDetection({ device_id: device2.id });
const token = generateTestToken(user1, tenant1);
const response = await request(app)
.get(`/detections/${detection2.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(404);
expect(response.body.success).to.be.false;
});
it('should return 404 for non-existent detection', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.get('/detections/99999')
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(404);
expect(response.body.success).to.be.false;
});
it('should include enhanced detection data', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({ tenant_id: tenant.id });
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({
device_id: device.id,
drone_type: 2
});
const token = generateTestToken(user, tenant);
const response = await request(app)
.get(`/detections/${detection.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.data.drone_type_info).to.exist;
expect(response.body.data.device).to.exist;
});
});
describe('DELETE /detections/:id', () => {
it('should delete detection with admin role', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({
tenant_id: tenant.id,
role: 'admin'
});
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({ device_id: device.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.delete(`/detections/${detection.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(200);
expect(response.body.success).to.be.true;
expect(response.body.message).to.equal('Detection deleted successfully');
});
it('should reject deletion without proper permissions', async () => {
const tenant = await createTestTenant();
const user = await createTestUser({
tenant_id: tenant.id,
role: 'viewer'
});
const device = await createTestDevice({ tenant_id: tenant.id });
const detection = await createTestDetection({ device_id: device.id });
const token = generateTestToken(user, tenant);
const response = await request(app)
.delete(`/detections/${detection.id}`)
.set('Authorization', `Bearer ${token}`);
expect(response.status).to.equal(403);
expect(response.body.success).to.be.false;
});
});
});

View File

@@ -0,0 +1,414 @@
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;
});
});
});