// IMPORTANT: Set environment variables FIRST, before any other imports process.env.NODE_ENV = 'test'; process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-only'; process.env.DATABASE_URL = ':memory:'; process.env.DB_DIALECT = 'sqlite'; process.env.DB_STORAGE = ':memory:'; process.env.DB_LOGGING = 'false'; const { Sequelize } = require('sequelize'); const path = require('path'); const sinon = require('sinon'); // Global Sinon setup - restore all spies after each test afterEach(() => { sinon.restore(); }); // Set additional test environment variables // Test database configuration const testDatabase = { dialect: 'sqlite', storage: ':memory:', // In-memory database for fast tests logging: false, // Disable SQL logging in tests sync: { force: true } // Always recreate tables for tests }; let sequelize; let models; /** * Setup test environment before all tests */ async function setupTestEnvironment() { // Create test database connection sequelize = new Sequelize(testDatabase); // Import and initialize models with test sequelize instance const Device = require('../models/Device')(sequelize); const DroneDetection = require('../models/DroneDetection')(sequelize); const Heartbeat = require('../models/Heartbeat')(sequelize); const User = require('../models/User')(sequelize); const AlertRule = require('../models/AlertRule')(sequelize); const AlertLog = require('../models/AlertLog')(sequelize); const Tenant = require('../models/Tenant')(sequelize); const ManagementUser = require('../models/ManagementUser')(sequelize); // Define associations Device.hasMany(DroneDetection, { foreignKey: 'device_id', as: 'detections' }); DroneDetection.belongsTo(Device, { foreignKey: 'device_id', as: 'device' }); Device.hasMany(Heartbeat, { foreignKey: 'device_id', as: 'heartbeats' }); Heartbeat.belongsTo(Device, { foreignKey: 'device_id', as: 'device' }); User.hasMany(AlertRule, { foreignKey: 'user_id', as: 'alertRules' }); AlertRule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); AlertRule.hasMany(AlertLog, { foreignKey: 'alert_rule_id', as: 'logs' }); AlertLog.belongsTo(AlertRule, { foreignKey: 'alert_rule_id', as: 'rule' }); DroneDetection.hasMany(AlertLog, { foreignKey: 'detection_id', as: 'alerts' }); AlertLog.belongsTo(DroneDetection, { foreignKey: 'detection_id', as: 'detection' }); Device.hasMany(AlertLog, { foreignKey: 'device_id', as: 'alerts' }); AlertLog.belongsTo(Device, { foreignKey: 'device_id', as: 'device' }); // Tenant associations Tenant.hasMany(User, { foreignKey: 'tenant_id', as: 'users' }); User.belongsTo(Tenant, { foreignKey: 'tenant_id', as: 'tenant' }); Tenant.hasMany(Device, { foreignKey: 'tenant_id', as: 'devices' }); Device.belongsTo(Tenant, { foreignKey: 'tenant_id', as: 'tenant' }); Tenant.hasMany(AlertRule, { foreignKey: 'tenant_id', as: 'alertRules' }); AlertRule.belongsTo(Tenant, { foreignKey: 'tenant_id', as: 'tenant' }); // Create models object models = { sequelize, Sequelize, Device, DroneDetection, Heartbeat, User, AlertRule, AlertLog, Tenant, ManagementUser }; // Set global models for routes to use in test mode global.__TEST_MODELS__ = models; // Sync database await sequelize.sync({ force: true }); // Return test context return { sequelize, models }; } /** * Cleanup test environment after all tests */ async function teardownTestEnvironment() { if (sequelize) { await sequelize.close(); } } /** * Clean database between tests */ async function cleanDatabase() { if (sequelize) { try { // Drop all tables and recreate them await sequelize.drop(); await sequelize.sync({ force: true }); // Verify tables are clean const tableNames = Object.keys(sequelize.models); for (const tableName of tableNames) { const count = await sequelize.models[tableName].count(); if (count > 0) { console.warn(`Warning: ${tableName} table not properly cleaned, has ${count} records`); } } } catch (error) { console.error('Error cleaning database:', error); throw error; } } } /** * Create test user with specified role and tenant */ async function createTestUser(userData = {}) { const { User, Tenant } = models; // Create default tenant if not exists let tenant = await Tenant.findOne({ where: { slug: 'test-tenant' } }); if (!tenant) { tenant = await Tenant.create({ name: 'Test Tenant', slug: 'test-tenant', domain: 'test.example.com', is_active: true }); } const defaultUserData = { username: 'testuser', email: 'test@example.com', password_hash: '$2b$10$dummyHashForTestingOnly', role: 'admin', tenant_id: tenant.id, is_active: true, ...userData }; return await User.create(defaultUserData); } /** * Create test device with specified tenant */ async function createTestDevice(deviceData = {}) { const { Device, Tenant } = models; // Create default tenant if not exists let tenant = await Tenant.findOne({ where: { slug: 'test-tenant' } }); if (!tenant) { tenant = await Tenant.create({ name: 'Test Tenant', slug: 'test-tenant', domain: 'test.example.com', is_active: true }); } const defaultDeviceData = { id: Math.floor(Math.random() * 1000000000), name: 'Test Device', geo_lat: 59.3293, geo_lon: 18.0686, location_description: 'Test Location', tenant_id: tenant.id, is_active: true, is_approved: true, ...deviceData }; return await Device.create(defaultDeviceData); } /** * Create test detection data */ async function createTestDetection(detectionData = {}) { const { DroneDetection, Device } = models; // Create device if not provided let device; if (detectionData.device_id) { device = await Device.findByPk(detectionData.device_id); } if (!device) { device = await createTestDevice(); } const defaultDetectionData = { device_id: device.id, geo_lat: device.geo_lat, geo_lon: device.geo_lon, device_timestamp: Date.now(), server_timestamp: new Date(), drone_type: 2, rssi: -65, freq: 2400, drone_id: Math.floor(Math.random() * 10000), ...detectionData }; return await DroneDetection.create(defaultDetectionData); } /** * Create test tenant */ async function createTestTenant(tenantData = {}) { const { Tenant } = models; const timestamp = Date.now(); const defaultTenantData = { name: 'Test Tenant', slug: 'test-tenant-' + timestamp, domain: 'test-' + timestamp + '.example.com', is_active: true, ...tenantData }; return await Tenant.create(defaultTenantData); } /** * Generate JWT token for test user */ function generateTestToken(user, tenant = null) { const jwt = require('jsonwebtoken'); const payload = { userId: user.id, username: user.username, role: user.role, email: user.email }; if (tenant) { payload.tenantId = tenant.slug; } return jwt.sign(payload, process.env.JWT_SECRET || 'test-secret', { expiresIn: '1h' }); } /** * Mock Express request object */ function mockRequest(overrides = {}) { return { body: {}, params: {}, query: {}, headers: {}, get: function(header) { return this.headers[header.toLowerCase()]; }, ...overrides }; } /** * Mock Express response object */ function mockResponse() { const res = { statusCode: 200, data: null, status: function(code) { this.statusCode = code; return this; }, json: function(data) { this.data = data; return this; }, send: function(data) { this.data = data; return this; }, setHeader: function(name, value) { this.headers = this.headers || {}; this.headers[name] = value; return this; } }; return res; } /** * Mock Express next function */ function mockNext() { const errors = []; const next = function(error) { if (error) errors.push(error); }; next.errors = errors; return next; } module.exports = { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestDevice, createTestDetection, createTestTenant, generateTestToken, mockRequest, mockResponse, mockNext };