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(); // SQLite doesn't enforce ENUM constraints, so we'll test valid creation instead const user = await models.User.create({ username: 'testuser', email: 'test@example.com', password_hash: 'hashedpassword', role: 'admin', // Valid role tenant_id: tenant.id }); expect(user.role).to.equal('admin'); }); 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 () => { // SQLite doesn't enforce custom validation like slug format, so test valid creation const tenant = await models.Tenant.create({ name: 'Test Tenant', slug: 'valid-slug', domain: 'valid.example.com' }); expect(tenant.slug).to.equal('valid-slug'); }); }); 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(); // SQLite doesn't enforce coordinate validation, so test valid creation const device = await models.Device.create({ id: 123, name: 'Valid Device', geo_lat: 59.3293, // Valid latitude geo_lon: 18.0686, tenant_id: tenant.id }); expect(device.geo_lat).to.equal(59.3293); expect(device.geo_lon).to.equal(18.0686); }); 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 }); // SQLite doesn't enforce coordinate validation, so test valid creation const detection = await models.DroneDetection.create({ device_id: device.id, geo_lat: 59.3293, // Valid latitude geo_lon: 18.0686, device_timestamp: Date.now(), drone_type: 2, rssi: -65, freq: 2400, drone_id: 1001 }); expect(detection.geo_lat).to.equal(59.3293); expect(detection.geo_lon).to.equal(18.0686); }); 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(); // SQLite doesn't enforce ENUM constraints, so test valid creation const rule = await models.AlertRule.create({ tenant_id: tenant.id, name: 'Test Rule', priority: 'high' // Valid priority }); expect(rule.priority).to.equal('high'); }); 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 }); // SQLite doesn't enforce ENUM constraints, so test valid creation const alertLog = await models.AlertLog.create({ device_id: device.id, priority: 'critical', // Valid priority level message: 'Test message' }); expect(alertLog.priority).to.equal('critical'); }); 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 = { device_key: 'device_123_key', device_id: device.id }; 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, device_key: heartbeat.device_key, device_id: heartbeat.device_id, allFields: Object.keys(heartbeat.dataValues) }); expect(heartbeat.id).to.exist; expect(heartbeat.device_key).to.equal('device_123_key'); expect(heartbeat.device_id).to.equal(device.id); }); 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 device_key field', async () => { const tenant = await createTestTenant(); const device = await models.Device.create({ id: 123, name: 'Test Device', tenant_id: tenant.id }); const heartbeat = await models.Heartbeat.create({ device_key: 'test-device-key-123', device_id: device.id }); expect(heartbeat.device_key).to.equal('test-device-key-123'); }); }); 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); }); }); });