672 lines
19 KiB
JavaScript
672 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
|
|
};
|
|
|
|
const heartbeat = await models.Heartbeat.create(heartbeatData);
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|