Files
drone-detector/server/tests/setup.js
2025-09-16 06:46:40 +02:00

358 lines
9.1 KiB
JavaScript

// 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:', // Use 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() {
// Clear any existing global models
delete global.__TEST_MODELS__;
// 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
};
// Store models globally for routes to access
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();
}
// Clear global models
delete global.__TEST_MODELS__;
}
/**
* 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: userData.username || `testuser${Date.now()}${Math.floor(Math.random() * 1000)}`,
email: userData.email || `test${Date.now()}@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 = {
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
};
// If a specific ID is provided, use upsert to ensure it's respected
if (deviceData.id) {
const [device, created] = await Device.upsert(defaultDeviceData, {
returning: true
});
return device;
} else {
// Auto-generate ID when none provided
defaultDeviceData.id = Math.floor(Math.random() * 1000000000);
return await Device.create(defaultDeviceData);
}
}
/**
* Create test detection data
*/
async function createTestDetection(detectionData = {}) {
const { DroneDetection, Device } = models;
let device;
// If device_id is provided, try to find the existing device
if (detectionData.device_id) {
device = await Device.findByPk(detectionData.device_id);
}
// If no device found or no device_id provided, create a new device
if (!device) {
const deviceData = {};
if (detectionData.tenant_id) {
deviceData.tenant_id = detectionData.tenant_id;
}
device = await createTestDevice(deviceData);
}
// Remove device_id from detectionData to avoid overriding
const { device_id, ...restDetectionData } = detectionData;
const defaultDetectionData = {
device_id: device.id, // Always use the actual 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),
...restDetectionData
};
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
};