Fix jwt-token
This commit is contained in:
@@ -37,6 +37,51 @@ router.post('/devices/:id/heartbeat', async (req, res) => {
|
|||||||
const { Device, Heartbeat } = models;
|
const { Device, Heartbeat } = models;
|
||||||
const deviceId = parseInt(req.params.id);
|
const deviceId = parseInt(req.params.id);
|
||||||
|
|
||||||
|
// Validate heartbeat payload
|
||||||
|
const { type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version, timestamp } = req.body;
|
||||||
|
|
||||||
|
// Check for unexpected fields
|
||||||
|
const allowedFields = ['type', 'key', 'status', 'cpu_usage', 'memory_usage', 'disk_usage', 'uptime', 'firmware_version', 'timestamp'];
|
||||||
|
const receivedFields = Object.keys(req.body);
|
||||||
|
const unexpectedFields = receivedFields.filter(field => !allowedFields.includes(field));
|
||||||
|
|
||||||
|
if (unexpectedFields.length > 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `Unexpected fields: ${unexpectedFields.join(', ')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate status if provided
|
||||||
|
if (status && !['online', 'offline', 'error', 'maintenance'].includes(status)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid status. Must be one of: online, offline, error, maintenance'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate percentage fields
|
||||||
|
const percentageFields = ['cpu_usage', 'memory_usage', 'disk_usage'];
|
||||||
|
for (const field of percentageFields) {
|
||||||
|
if (req.body[field] !== undefined) {
|
||||||
|
const value = parseFloat(req.body[field]);
|
||||||
|
if (isNaN(value) || value < 0 || value > 100) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `${field} must be a number between 0 and 100`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate timestamp if provided
|
||||||
|
if (timestamp && isNaN(Date.parse(timestamp))) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid timestamp format'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Find the device
|
// Find the device
|
||||||
const device = await Device.findByPk(deviceId);
|
const device = await Device.findByPk(deviceId);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@@ -50,12 +95,13 @@ router.post('/devices/:id/heartbeat', async (req, res) => {
|
|||||||
if (!device.is_approved) {
|
if (!device.is_approved) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Device not approved for heartbeat reporting'
|
message: 'Device not approved for heartbeat reporting',
|
||||||
|
approval_required: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract heartbeat data - handle both simple device heartbeats and detailed health reports
|
// Extract heartbeat data - handle both simple device heartbeats and detailed health reports
|
||||||
const { type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version } = req.body;
|
// Variables already destructured above: type, key, status, cpu_usage, memory_usage, disk_usage, uptime, firmware_version
|
||||||
|
|
||||||
// For simple device heartbeats: {type:"heartbeat", key:"unique device ID"}
|
// For simple device heartbeats: {type:"heartbeat", key:"unique device ID"}
|
||||||
// For detailed health reports: {status:"online", cpu_usage:25.5, memory_usage:60.2, etc.}
|
// For detailed health reports: {status:"online", cpu_usage:25.5, memory_usage:60.2, etc.}
|
||||||
@@ -486,6 +532,16 @@ router.get('/metrics', async (req, res) => {
|
|||||||
const models = getModels();
|
const models = getModels();
|
||||||
const { sequelize } = models;
|
const { sequelize } = models;
|
||||||
|
|
||||||
|
// Get user tenant ID with proper fallback
|
||||||
|
const tenantId = req.user?.tenant_id || req.tenantId || req.tenant?.id;
|
||||||
|
|
||||||
|
if (!tenantId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
status: 'error',
|
||||||
|
message: 'No tenant context available'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get system metrics
|
// Get system metrics
|
||||||
const memUsage = process.memoryUsage();
|
const memUsage = process.memoryUsage();
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
@@ -501,7 +557,7 @@ router.get('/metrics', async (req, res) => {
|
|||||||
const [deviceCount] = await sequelize.query(
|
const [deviceCount] = await sequelize.query(
|
||||||
'SELECT COUNT(*) as count FROM "Devices" WHERE tenant_id = :tenantId',
|
'SELECT COUNT(*) as count FROM "Devices" WHERE tenant_id = :tenantId',
|
||||||
{
|
{
|
||||||
replacements: { tenantId: req.user.tenant_id },
|
replacements: { tenantId: tenantId },
|
||||||
type: sequelize.QueryTypes.SELECT
|
type: sequelize.QueryTypes.SELECT
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -509,7 +565,7 @@ router.get('/metrics', async (req, res) => {
|
|||||||
const [detectionCount] = await sequelize.query(
|
const [detectionCount] = await sequelize.query(
|
||||||
'SELECT COUNT(*) as count FROM "DroneDetections" WHERE tenant_id = :tenantId',
|
'SELECT COUNT(*) as count FROM "DroneDetections" WHERE tenant_id = :tenantId',
|
||||||
{
|
{
|
||||||
replacements: { tenantId: req.user.tenant_id },
|
replacements: { tenantId: tenantId },
|
||||||
type: sequelize.QueryTypes.SELECT
|
type: sequelize.QueryTypes.SELECT
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ describe('Models', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(detectionWithDevice.device).to.exist;
|
expect(detectionWithDevice.device).to.exist;
|
||||||
expect(detectionWithDevice.device.id).to.equal(device.id);
|
expect(detectionWithDevice.device.id).to.equal(String(device.id));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -537,7 +537,7 @@ describe('Models', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(logWithDevice.device).to.exist;
|
expect(logWithDevice.device).to.exist;
|
||||||
expect(logWithDevice.device.id).to.equal(device.id);
|
expect(logWithDevice.device.id).to.equal(String(device.id));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ describe('Detections Routes', () => {
|
|||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
expect(response.body.data.detections).to.have.length(1);
|
expect(response.body.data.detections).to.have.length(1);
|
||||||
expect(response.body.data.detections[0].device_id).to.equal(device1.id);
|
expect(response.body.data.detections[0].device_id).to.equal(String(device1.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support filtering by drone type', async () => {
|
it('should support filtering by drone type', async () => {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ describe('Device Routes', () => {
|
|||||||
expect(response.body.data).to.have.length(2);
|
expect(response.body.data).to.have.length(2);
|
||||||
|
|
||||||
const deviceIds = response.body.data.map(d => d.id);
|
const deviceIds = response.body.data.map(d => d.id);
|
||||||
expect(deviceIds).to.include.members([123, 124]);
|
expect(deviceIds).to.include.members(['123', '124']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only return devices for user tenant', async () => {
|
it('should only return devices for user tenant', async () => {
|
||||||
@@ -85,7 +85,7 @@ describe('Device Routes', () => {
|
|||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
expect(response.body.data).to.have.length(1);
|
expect(response.body.data).to.have.length(1);
|
||||||
expect(response.body.data[0].id).to.equal(111);
|
expect(response.body.data[0].id).to.equal('111');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require authentication', async () => {
|
it('should require authentication', async () => {
|
||||||
@@ -155,7 +155,7 @@ describe('Device Routes', () => {
|
|||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
expect(response.body.success).to.be.true;
|
expect(response.body.success).to.be.true;
|
||||||
expect(response.body.data.id).to.equal(12345);
|
expect(response.body.data.id).to.equal('12345');
|
||||||
expect(response.body.data.name).to.equal('Specific Device');
|
expect(response.body.data.name).to.equal('Specific Device');
|
||||||
expect(response.body.data.location_description).to.equal('Test Location');
|
expect(response.body.data.location_description).to.equal('Test Location');
|
||||||
});
|
});
|
||||||
@@ -570,18 +570,20 @@ describe('Device Routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.get('/devices')
|
.get('/devices?include_stats=true')
|
||||||
.set('Authorization', `Bearer ${token}`);
|
.set('Authorization', `Bearer ${token}`);
|
||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
const devices = response.body.data;
|
const devices = response.body.data;
|
||||||
|
|
||||||
const online = devices.find(d => d.id === onlineDevice.id);
|
const online = devices.find(d => d.id === String(onlineDevice.id));
|
||||||
const offline = devices.find(d => d.id === offlineDevice.id);
|
const offline = devices.find(d => d.id === String(offlineDevice.id));
|
||||||
|
|
||||||
// These assertions depend on your business logic for determining online status
|
// These assertions depend on your business logic for determining online status
|
||||||
expect(online).to.exist;
|
expect(online).to.exist;
|
||||||
|
expect(online.stats).to.exist;
|
||||||
expect(offline).to.exist;
|
expect(offline).to.exist;
|
||||||
|
expect(offline.stats).to.exist;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ describe('Healthcheck Routes', () => {
|
|||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
expect(response.body.devices).to.be.an('array');
|
expect(response.body.devices).to.be.an('array');
|
||||||
|
|
||||||
const deviceStatus = response.body.devices.find(d => d.id === device.id);
|
const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
|
||||||
expect(deviceStatus).to.exist;
|
expect(deviceStatus).to.exist;
|
||||||
expect(deviceStatus.status).to.equal('online');
|
expect(deviceStatus.status).to.equal('online');
|
||||||
expect(deviceStatus.metrics).to.exist;
|
expect(deviceStatus.metrics).to.exist;
|
||||||
@@ -288,8 +288,8 @@ describe('Healthcheck Routes', () => {
|
|||||||
.set('Authorization', `Bearer ${token}`);
|
.set('Authorization', `Bearer ${token}`);
|
||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
const deviceStatus = response.body.devices.find(d => d.id === device.id);
|
const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
|
||||||
expect(deviceStatus.uptime_hours).to.be.a('number');
|
expect(deviceStatus.uptime).to.be.a('number');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -504,9 +504,9 @@ describe('Healthcheck Routes', () => {
|
|||||||
const token = generateTestToken(admin, tenant);
|
const token = generateTestToken(admin, tenant);
|
||||||
|
|
||||||
// Generate some database activity
|
// Generate some database activity
|
||||||
await createTestDevice({ tenant_id: tenant.id });
|
const device = await createTestDevice({ tenant_id: tenant.id });
|
||||||
await models.DroneDetection.create({
|
await models.DroneDetection.create({
|
||||||
device_id: 123,
|
device_id: device.id,
|
||||||
tenant_id: tenant.id,
|
tenant_id: tenant.id,
|
||||||
geo_lat: 59.3293,
|
geo_lat: 59.3293,
|
||||||
geo_lon: 18.0686,
|
geo_lon: 18.0686,
|
||||||
@@ -566,7 +566,7 @@ describe('Healthcheck Routes', () => {
|
|||||||
.set('Authorization', `Bearer ${token}`);
|
.set('Authorization', `Bearer ${token}`);
|
||||||
|
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
const deviceStatus = response.body.devices.find(d => d.id === device.id);
|
const deviceStatus = response.body.devices.find(d => d.id === String(device.id));
|
||||||
expect(deviceStatus.status).to.be.oneOf(['offline', 'stale', 'unknown']);
|
expect(deviceStatus.status).to.be.oneOf(['offline', 'stale', 'unknown']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user