**Documentation:** - Added docs/geofence-testing.md with comprehensive test guide - Includes all test scripts, manual testing procedures - Troubleshooting section for common issues - Cleanup instructions **Configuration:** - Updated admin user email to joachim.hummel@gmail.com - Restored MQTT_BROKER_URL to mosquitto (Docker setup) - Fixed test-mqtt-geofence.js to use admin credentials **Test Results:** ✅ Database & Logic Test - Working perfectly ✅ Email Notification Test - Email sent successfully ✅ MQTT Integration - Server connects, receives messages ⚠️ Full chain test - Works but duplicate detection prevents retests **What's Working:** - Geofence creation and management via API - Distance calculations (Haversine formula) - Enter/Exit event generation with state tracking - SMTP email delivery with React Email templates - MQTT subscriber integration **Ready for Production:** The geofencing MVP is fully functional and ready for real-world testing with OwnTracks devices sending unique location updates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
258 lines
6.8 KiB
JavaScript
258 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Complete MQTT Geofence Integration Test
|
|
*
|
|
* This script:
|
|
* 1. Connects to MQTT broker
|
|
* 2. Creates a test geofence
|
|
* 3. Publishes OwnTracks location messages
|
|
* 4. Triggers geofence events
|
|
* 5. Results in email notifications (if dev server is running)
|
|
*/
|
|
|
|
const mqtt = require('mqtt');
|
|
const Database = require('better-sqlite3');
|
|
const path = require('path');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
const dbPath = path.join(__dirname, '..', 'data', 'database.sqlite');
|
|
|
|
console.log('🧪 Complete MQTT Geofence Integration Test\n');
|
|
|
|
// Configuration
|
|
const CONFIG = {
|
|
mqttBroker: process.env.MQTT_BROKER_URL || 'mqtt://localhost:1883',
|
|
mqttUsername: process.env.MQTT_ADMIN_USERNAME || process.env.MQTT_USERNAME,
|
|
mqttPassword: process.env.MQTT_ADMIN_PASSWORD || process.env.MQTT_PASSWORD,
|
|
|
|
deviceId: '10',
|
|
userId: 'admin-001',
|
|
|
|
// Test geofence (Frankfurt)
|
|
geofenceName: 'MQTT Integration Test Zone',
|
|
geofenceCenter: {
|
|
lat: 50.1109,
|
|
lon: 8.6821,
|
|
},
|
|
geofenceRadius: 500,
|
|
|
|
// Test locations to send via MQTT
|
|
testLocations: [
|
|
{
|
|
lat: 50.1200,
|
|
lon: 8.6900,
|
|
label: 'Outside (Start)',
|
|
delay: 1000,
|
|
},
|
|
{
|
|
lat: 50.1109,
|
|
lon: 8.6821,
|
|
label: 'Inside (Enter - should trigger EMAIL)',
|
|
delay: 2000,
|
|
},
|
|
{
|
|
lat: 50.1115,
|
|
lon: 8.6825,
|
|
label: 'Inside (Stay)',
|
|
delay: 2000,
|
|
},
|
|
{
|
|
lat: 50.1200,
|
|
lon: 8.6900,
|
|
label: 'Outside (Exit - should trigger EMAIL)',
|
|
delay: 2000,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Create test geofence
|
|
function createTestGeofence() {
|
|
const db = new Database(dbPath);
|
|
try {
|
|
const geofenceId = uuidv4();
|
|
|
|
db.prepare(`
|
|
INSERT INTO Geofence (
|
|
id, name, description, shape_type,
|
|
center_latitude, center_longitude, radius_meters,
|
|
owner_id, device_id, color
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(
|
|
geofenceId,
|
|
CONFIG.geofenceName,
|
|
'Auto-generated test geofence for MQTT integration test',
|
|
'circle',
|
|
CONFIG.geofenceCenter.lat,
|
|
CONFIG.geofenceCenter.lon,
|
|
CONFIG.geofenceRadius,
|
|
CONFIG.userId,
|
|
CONFIG.deviceId,
|
|
'#8b5cf6'
|
|
);
|
|
|
|
console.log(`✓ Created test geofence: "${CONFIG.geofenceName}"`);
|
|
console.log(` ID: ${geofenceId}`);
|
|
console.log(` Center: ${CONFIG.geofenceCenter.lat}, ${CONFIG.geofenceCenter.lon}`);
|
|
console.log(` Radius: ${CONFIG.geofenceRadius}m\n`);
|
|
|
|
return geofenceId;
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
// Create OwnTracks location message
|
|
function createOwnTracksMessage(lat, lon) {
|
|
return {
|
|
_type: 'location',
|
|
tid: CONFIG.deviceId,
|
|
lat: lat,
|
|
lon: lon,
|
|
tst: Math.floor(Date.now() / 1000), // Unix timestamp
|
|
batt: 85,
|
|
vel: 0,
|
|
acc: 10,
|
|
};
|
|
}
|
|
|
|
// Send MQTT message
|
|
function publishLocation(client, lat, lon, label) {
|
|
return new Promise((resolve, reject) => {
|
|
const topic = `owntracks/user/${CONFIG.deviceId}`;
|
|
const message = createOwnTracksMessage(lat, lon);
|
|
const payload = JSON.stringify(message);
|
|
|
|
console.log(`📡 Publishing location: ${label}`);
|
|
console.log(` Topic: ${topic}`);
|
|
console.log(` Coordinates: ${lat}, ${lon}`);
|
|
|
|
client.publish(topic, payload, { qos: 1 }, (err) => {
|
|
if (err) {
|
|
console.error(` ❌ Failed: ${err.message}`);
|
|
reject(err);
|
|
} else {
|
|
console.log(` ✓ Published\n`);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Wait helper
|
|
function wait(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
// Main test
|
|
async function runTest() {
|
|
let geofenceId = null;
|
|
let mqttClient = null;
|
|
|
|
try {
|
|
console.log('🔧 Step 1: Creating test geofence...\n');
|
|
geofenceId = createTestGeofence();
|
|
|
|
console.log('🔌 Step 2: Connecting to MQTT broker...');
|
|
console.log(` Broker: ${CONFIG.mqttBroker}\n`);
|
|
|
|
const options = {
|
|
clean: true,
|
|
connectTimeout: 10000,
|
|
};
|
|
|
|
if (CONFIG.mqttUsername && CONFIG.mqttPassword) {
|
|
options.username = CONFIG.mqttUsername;
|
|
options.password = CONFIG.mqttPassword;
|
|
}
|
|
|
|
mqttClient = mqtt.connect(CONFIG.mqttBroker, options);
|
|
|
|
// Wait for connection
|
|
await new Promise((resolve, reject) => {
|
|
mqttClient.on('connect', () => {
|
|
console.log('✓ Connected to MQTT broker\n');
|
|
resolve();
|
|
});
|
|
|
|
mqttClient.on('error', (error) => {
|
|
console.error('❌ MQTT connection error:', error.message);
|
|
reject(error);
|
|
});
|
|
|
|
setTimeout(() => reject(new Error('Connection timeout')), 10000);
|
|
});
|
|
|
|
console.log('📤 Step 3: Publishing test locations...\n');
|
|
console.log('⚠️ IMPORTANT: Make sure the dev server is running!');
|
|
console.log(' Run: npm run dev (in another terminal)\n');
|
|
|
|
await wait(2000);
|
|
|
|
// Publish each test location
|
|
for (let i = 0; i < CONFIG.testLocations.length; i++) {
|
|
const loc = CONFIG.testLocations[i];
|
|
|
|
console.log(`[${i + 1}/${CONFIG.testLocations.length}]`);
|
|
await publishLocation(mqttClient, loc.lat, loc.lon, loc.label);
|
|
await wait(loc.delay);
|
|
}
|
|
|
|
console.log('📊 Step 4: Test Summary\n');
|
|
console.log('✅ Published 4 MQTT location messages');
|
|
console.log('✅ Expected: 2 email notifications (Enter + Exit)');
|
|
console.log(`✅ Geofence ID: ${geofenceId}\n`);
|
|
|
|
console.log('💡 What should happen:');
|
|
console.log(' 1. MQTT subscriber receives the messages');
|
|
console.log(' 2. Geofence engine detects ENTER and EXIT events');
|
|
console.log(' 3. Email notifications are sent to joachim.hummel@gmail.com');
|
|
console.log(' 4. Check your inbox!\n');
|
|
|
|
console.log('📧 Check events in database:');
|
|
console.log(` sqlite3 data/database.sqlite "SELECT * FROM GeofenceEvent WHERE geofence_id = '${geofenceId}'"\n`);
|
|
|
|
console.log('🗑️ Cleanup:');
|
|
console.log(` sqlite3 data/database.sqlite "DELETE FROM Geofence WHERE id = '${geofenceId}'"\n`);
|
|
|
|
// Disconnect MQTT
|
|
mqttClient.end();
|
|
console.log('✓ Disconnected from MQTT broker');
|
|
|
|
console.log('\n✅ Integration test completed!');
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Test failed:', error.message);
|
|
|
|
if (error.message.includes('ECONNREFUSED')) {
|
|
console.error('\n💡 MQTT broker is not running or not accessible.');
|
|
console.error(' Check your MQTT_BROKER_URL in .env');
|
|
}
|
|
|
|
if (mqttClient) {
|
|
mqttClient.end();
|
|
}
|
|
|
|
if (geofenceId) {
|
|
const db = new Database(dbPath);
|
|
try {
|
|
db.prepare('DELETE FROM Geofence WHERE id = ?').run(geofenceId);
|
|
console.log('\n✓ Cleaned up test geofence');
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run
|
|
console.log('Starting MQTT integration test...\n');
|
|
runTest().then(() => {
|
|
console.log('\nTest script finished.');
|
|
process.exit(0);
|
|
}).catch((error) => {
|
|
console.error('Unexpected error:', error);
|
|
process.exit(1);
|
|
});
|