Files
drone-detector/server/tests/models/models.test.js
2025-09-15 15:44:44 +02:00

674 lines
19 KiB
JavaScript

const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
const { expect } = require('chai');
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestTenant } = require('../setup');
describe('Models', () => {
let models, sequelize;
before(async () => {
({ models, sequelize } = await setupTestEnvironment());
});
after(async () => {
await teardownTestEnvironment();
});
beforeEach(async () => {
await cleanDatabase();
});
describe('User Model', () => {
it('should create user with valid data', async () => {
const tenant = await createTestTenant();
const userData = {
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
role: 'admin',
tenant_id: tenant.id
};
const user = await models.User.create(userData);
expect(user.id).to.exist;
expect(user.username).to.equal('testuser');
expect(user.email).to.equal('test@example.com');
expect(user.role).to.equal('admin');
expect(user.tenant_id).to.equal(tenant.id);
});
it('should enforce unique username per tenant', async () => {
const tenant = await createTestTenant();
const userData = {
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
tenant_id: tenant.id
};
await models.User.create(userData);
// Try to create another user with same username in same tenant
try {
await models.User.create({
...userData,
email: 'different@example.com'
});
expect.fail('Should have thrown unique constraint error');
} catch (error) {
expect(error.name).to.include('SequelizeUniqueConstraintError');
}
});
it('should allow same username in different tenants', async () => {
const tenant1 = await createTestTenant({ slug: 'tenant1' });
const tenant2 = await createTestTenant({ slug: 'tenant2' });
const timestamp = Date.now();
const user1 = await models.User.create({
username: 'testuser',
email: `test1-${timestamp}@example.com`,
password_hash: 'hashedpassword',
tenant_id: tenant1.id
});
const user2 = await models.User.create({
username: 'testuser',
email: `test2-${timestamp}@example.com`,
password_hash: 'hashedpassword',
tenant_id: tenant2.id
});
expect(user1.username).to.equal(user2.username);
expect(user1.tenant_id).to.not.equal(user2.tenant_id);
});
it('should validate email format', async () => {
const tenant = await createTestTenant();
try {
await models.User.create({
username: 'testuser',
email: 'invalid-email',
password_hash: 'hashedpassword',
tenant_id: tenant.id
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should validate role values', async () => {
const tenant = await createTestTenant();
try {
await models.User.create({
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
role: 'invalid_role',
tenant_id: tenant.id
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should have default values', async () => {
const tenant = await createTestTenant();
const user = await models.User.create({
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
tenant_id: tenant.id
});
expect(user.role).to.equal('viewer'); // Default role
expect(user.is_active).to.be.true;
expect(user.created_at).to.exist;
expect(user.updated_at).to.exist;
});
it('should associate with tenant', async () => {
const tenant = await createTestTenant();
const user = await models.User.create({
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
tenant_id: tenant.id
});
const userWithTenant = await models.User.findByPk(user.id, {
include: [{ model: models.Tenant, as: 'tenant' }]
});
expect(userWithTenant.tenant).to.exist;
expect(userWithTenant.tenant.slug).to.equal(tenant.slug);
});
});
describe('Tenant Model', () => {
it('should create tenant with valid data', async () => {
const tenantData = {
name: 'Test Tenant',
slug: 'test-tenant',
domain: 'test.example.com'
};
const tenant = await models.Tenant.create(tenantData);
expect(tenant.id).to.exist;
expect(tenant.name).to.equal('Test Tenant');
expect(tenant.slug).to.equal('test-tenant');
expect(tenant.domain).to.equal('test.example.com');
});
it('should enforce unique slug', async () => {
await models.Tenant.create({
name: 'Tenant 1',
slug: 'test-slug',
domain: 'test1.example.com'
});
try {
await models.Tenant.create({
name: 'Tenant 2',
slug: 'test-slug', // Same slug
domain: 'test2.example.com'
});
expect.fail('Should have thrown unique constraint error');
} catch (error) {
expect(error.name).to.include('SequelizeUniqueConstraintError');
}
});
it('should have default values', async () => {
const tenant = await models.Tenant.create({
name: 'Test Tenant',
slug: 'test-tenant',
domain: 'test.example.com'
});
expect(tenant.is_active).to.be.true;
expect(tenant.allow_registration).to.be.false;
expect(tenant.ip_restriction_enabled).to.be.false;
});
it('should validate slug format', async () => {
try {
await models.Tenant.create({
name: 'Test Tenant',
slug: 'invalid slug with spaces',
domain: 'test.example.com'
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
});
describe('Device Model', () => {
it('should create device with valid data', async () => {
const tenant = await createTestTenant();
const deviceData = {
id: 1941875381,
name: 'Test Device',
geo_lat: 59.3293,
geo_lon: 18.0686,
tenant_id: tenant.id
};
const device = await models.Device.create(deviceData);
expect(device.id).to.equal(1941875381);
expect(device.name).to.equal('Test Device');
expect(device.geo_lat).to.equal(59.3293);
expect(device.geo_lon).to.equal(18.0686);
});
it('should validate coordinate ranges', async () => {
const tenant = await createTestTenant();
try {
await models.Device.create({
id: 123,
name: 'Invalid Device',
geo_lat: 91, // Invalid latitude
geo_lon: 18.0686,
tenant_id: tenant.id
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should have default values', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
expect(device.is_active).to.be.true;
expect(device.is_approved).to.be.false; // Requires manual approval
expect(device.heartbeat_interval).to.equal(300); // 5 minutes
});
it('should associate with tenant', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const deviceWithTenant = await models.Device.findByPk(device.id, {
include: [{ model: models.Tenant, as: 'tenant' }]
});
expect(deviceWithTenant.tenant).to.exist;
expect(deviceWithTenant.tenant.id).to.equal(tenant.id);
});
it('should enforce unique device ID per tenant', async () => {
const tenant = await createTestTenant();
const deviceId = 123;
await models.Device.create({
id: deviceId,
name: 'Device 1',
tenant_id: tenant.id
});
try {
await models.Device.create({
id: deviceId,
name: 'Device 2',
tenant_id: tenant.id
});
expect.fail('Should have thrown unique constraint error');
} catch (error) {
expect(error.name).to.include('SequelizeUniqueConstraintError');
}
});
});
describe('DroneDetection Model', () => {
it('should create detection with valid data', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
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 detection = await models.DroneDetection.create(detectionData);
expect(detection.id).to.exist;
expect(detection.device_id).to.equal(device.id);
expect(detection.drone_type).to.equal(2);
expect(detection.rssi).to.equal(-65);
});
it('should auto-set server timestamp', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const detection = await models.DroneDetection.create({
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
});
expect(detection.created_at).to.exist;
expect(detection.created_at).to.be.a('date');
});
it('should validate coordinate ranges', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
try {
await models.DroneDetection.create({
device_id: device.id,
geo_lat: 91, // Invalid latitude
geo_lon: 18.0686,
device_timestamp: Date.now(),
drone_type: 2,
rssi: -65,
freq: 2400,
drone_id: 1001
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should associate with device', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const detection = await models.DroneDetection.create({
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 detectionWithDevice = await models.DroneDetection.findByPk(detection.id, {
include: [{ model: models.Device, as: 'device' }]
});
expect(detectionWithDevice.device).to.exist;
expect(detectionWithDevice.device.id).to.equal(device.id);
});
});
describe('AlertRule Model', () => {
it('should create alert rule with valid data', async () => {
const tenant = await createTestTenant();
const ruleData = {
tenant_id: tenant.id,
name: 'Test Rule',
priority: 'high',
min_detections: 2,
time_window: 300,
is_active: true
};
const rule = await models.AlertRule.create(ruleData);
expect(rule.id).to.exist;
expect(rule.name).to.equal('Test Rule');
expect(rule.priority).to.equal('high');
expect(rule.min_detections).to.equal(2);
});
it('should have default values', async () => {
const tenant = await createTestTenant();
const rule = await models.AlertRule.create({
tenant_id: tenant.id,
name: 'Test Rule'
});
expect(rule.is_active).to.be.true;
expect(rule.priority).to.equal('medium');
});
it('should validate priority values', async () => {
const tenant = await createTestTenant();
try {
await models.AlertRule.create({
tenant_id: tenant.id,
name: 'Test Rule',
priority: 'invalid_priority'
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should associate with tenant', async () => {
const tenant = await createTestTenant();
const rule = await models.AlertRule.create({
tenant_id: tenant.id,
name: 'Test Rule'
});
const ruleWithTenant = await models.AlertRule.findByPk(rule.id, {
include: [{ model: models.Tenant, as: 'tenant' }]
});
expect(ruleWithTenant.tenant).to.exist;
expect(ruleWithTenant.tenant.id).to.equal(tenant.id);
});
});
describe('AlertLog Model', () => {
it('should create alert log with valid data', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const logData = {
device_id: device.id,
alert_type: 'email',
recipient: 'test@example.com',
message: 'Test alert message',
status: 'pending'
};
const alertLog = await models.AlertLog.create(logData);
expect(alertLog.id).to.exist;
expect(alertLog.alert_type).to.equal('email');
expect(alertLog.recipient).to.equal('test@example.com');
expect(alertLog.message).to.equal('Test alert message');
});
it('should auto-set timestamp', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const alertLog = await models.AlertLog.create({
device_id: device.id,
alert_type: 'email',
message: 'Test alert message'
});
expect(alertLog.created_at).to.exist;
expect(alertLog.created_at).to.be.a('date');
});
it('should validate threat level values', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
try {
await models.AlertLog.create({
device_id: device.id,
rule_name: 'Test Alert',
threat_level: 'invalid_level',
message: 'Test message'
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
it('should associate with device', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const alertLog = await models.AlertLog.create({
device_id: device.id,
rule_name: 'Test Alert',
threat_level: 'high',
message: 'Test message'
});
const logWithDevice = await models.AlertLog.findByPk(alertLog.id, {
include: [{ model: models.Device, as: 'device' }]
});
expect(logWithDevice.device).to.exist;
expect(logWithDevice.device.id).to.equal(device.id);
});
});
describe('Heartbeat Model', () => {
it('should create heartbeat with valid data', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const heartbeatData = {
key: 'device_123_key',
device_id: device.id,
signal_strength: -50,
battery_level: 85,
temperature: 22.5
};
console.log('DEBUG Creating heartbeat with data:', heartbeatData);
const heartbeat = await models.Heartbeat.create(heartbeatData);
console.log('DEBUG Heartbeat creation successful');
console.log('DEBUG Heartbeat created:', {
id: heartbeat.id,
key: heartbeat.key,
device_id: heartbeat.device_id,
battery_level: heartbeat.battery_level,
allFields: Object.keys(heartbeat.dataValues)
});
expect(heartbeat.id).to.exist;
expect(heartbeat.key).to.equal('device_123_key');
expect(heartbeat.device_id).to.equal(device.id);
expect(heartbeat.battery_level).to.equal(85);
});
it('should auto-set timestamp', async () => {
const tenant = await createTestTenant();
const device = await models.Device.create({
id: 124,
name: 'Test Device 2',
tenant_id: tenant.id
});
const heartbeat = await models.Heartbeat.create({
key: 'device_124_key',
device_id: device.id
});
expect(heartbeat.created_at).to.exist;
expect(heartbeat.created_at).to.be.a('date');
});
it('should validate battery level range', async () => {
try {
await models.Heartbeat.create({
key: 'device_123_key',
device_id: 123,
battery_level: 150 // Invalid range
});
expect.fail('Should have thrown validation error');
} catch (error) {
expect(error.name).to.include('SequelizeValidationError');
}
});
});
describe('Model Associations', () => {
it('should load all associations correctly', async () => {
const tenant = await createTestTenant();
const user = await models.User.create({
username: 'testuser',
email: 'test@example.com',
password_hash: 'hashedpassword',
tenant_id: tenant.id
});
const device = await models.Device.create({
id: 123,
name: 'Test Device',
tenant_id: tenant.id
});
const detection = await models.DroneDetection.create({
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
});
// Test complex association loading
const tenantWithAll = await models.Tenant.findByPk(tenant.id, {
include: [
{
model: models.User,
as: 'users'
},
{
model: models.Device,
as: 'devices',
include: [
{
model: models.DroneDetection,
as: 'detections'
}
]
}
]
});
expect(tenantWithAll.users).to.have.length(1);
expect(tenantWithAll.devices).to.have.length(1);
expect(tenantWithAll.devices[0].detections).to.have.length(1);
});
});
});