329 lines
8.2 KiB
JavaScript
329 lines
8.2 KiB
JavaScript
const { Sequelize } = require('sequelize');
|
|
const path = require('path');
|
|
|
|
// Set test environment variables
|
|
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';
|
|
|
|
// 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' });
|
|
|
|
// 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' });
|
|
|
|
// Create models object
|
|
models = {
|
|
sequelize,
|
|
Sequelize,
|
|
Device,
|
|
DroneDetection,
|
|
Heartbeat,
|
|
User,
|
|
AlertRule,
|
|
AlertLog,
|
|
Tenant,
|
|
ManagementUser
|
|
};
|
|
|
|
// Override the main models module with our test models
|
|
// This ensures that when other modules import '../models', they get our test models
|
|
const mainModelsPath = path.resolve(__dirname, '../models/index.js');
|
|
require.cache[mainModelsPath] = {
|
|
exports: models,
|
|
loaded: true,
|
|
id: mainModelsPath
|
|
};
|
|
|
|
// Inject test models into middleware modules
|
|
const authMiddleware = require('../middleware/auth');
|
|
authMiddleware.setModels(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) {
|
|
await sequelize.sync({ force: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
try {
|
|
tenant = await Tenant.create({
|
|
name: 'Test Tenant',
|
|
slug: 'test-tenant',
|
|
domain: 'test.example.com',
|
|
is_active: true
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Default tenant creation failed:', error.message);
|
|
console.error('❌ Validation errors:', error.errors);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Generate a unique username to avoid conflicts
|
|
const uniqueSuffix = Date.now() + '-' + Math.random().toString(36).substr(2, 5);
|
|
|
|
const defaultUserData = {
|
|
username: userData.username || 'testuser-' + uniqueSuffix,
|
|
email: userData.email || 'test-' + uniqueSuffix + '@example.com',
|
|
password_hash: '$2b$10$example.hash.for.testing.purposes.only',
|
|
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 defaultTenantData = {
|
|
name: 'Test Tenant',
|
|
slug: 'test-tenant-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
|
|
domain: 'test.example.com',
|
|
is_active: true,
|
|
...tenantData
|
|
};
|
|
|
|
try {
|
|
return await Tenant.create(defaultTenantData);
|
|
} catch (error) {
|
|
console.error('❌ Tenant creation failed:', error.message);
|
|
console.error('❌ Validation errors:', error.errors);
|
|
console.error('❌ Tenant data:', defaultTenantData);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
};
|