#!/usr/bin/env node /** * Test script for Geofence functionality * * This script: * 1. Creates a test geofence * 2. Simulates location updates (outside โ†’ inside โ†’ outside) * 3. Shows how to check events in database */ const Database = require('better-sqlite3'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const dbPath = path.join(__dirname, '..', 'data', 'database.sqlite'); const locationsDbPath = path.join(__dirname, '..', 'data', 'locations.sqlite'); console.log('๐Ÿงช Geofence Test Script\n'); // Test configuration const TEST_CONFIG = { deviceId: '10', userId: 'admin-001', // Geofence center (Frankfurt, Germany) geofenceCenter: { lat: 50.1109, lon: 8.6821, }, geofenceRadius: 500, // meters // Test locations locations: [ { lat: 50.1200, lon: 8.6900, label: 'Outside (Start)' }, { lat: 50.1109, lon: 8.6821, label: 'Inside (Enter)' }, { lat: 50.1115, lon: 8.6825, label: 'Inside (Stay)' }, { lat: 50.1200, lon: 8.6900, label: 'Outside (Exit)' }, ], }; // Haversine distance calculation function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371e3; // Earth radius in meters const ฯ†1 = (lat1 * Math.PI) / 180; const ฯ†2 = (lat2 * Math.PI) / 180; const ฮ”ฯ† = ((lat2 - lat1) * Math.PI) / 180; const ฮ”ฮป = ((lon2 - lon1) * Math.PI) / 180; const a = Math.sin(ฮ”ฯ† / 2) * Math.sin(ฮ”ฯ† / 2) + Math.cos(ฯ†1) * Math.cos(ฯ†2) * Math.sin(ฮ”ฮป / 2) * Math.sin(ฮ”ฮป / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } // Create test location function createTestLocation(lat, lon, deviceId) { const db = new Database(locationsDbPath); try { const timestamp = new Date().toISOString(); const stmt = db.prepare(` INSERT INTO Location ( latitude, longitude, timestamp, user_id, username, marker_label, first_name, last_name, display_time, chat_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = stmt.run( lat, lon, timestamp, 0, deviceId, deviceId, null, null, null, 0 ); return { id: result.lastInsertRowid, latitude: lat, longitude: lon, timestamp, username: deviceId, }; } finally { db.close(); } } // Check geofence and generate events function checkGeofence(location, geofence, deviceId) { const db = new Database(dbPath); try { // Calculate distance const distance = calculateDistance( location.latitude, location.longitude, geofence.center_latitude, geofence.center_longitude ); const isInside = distance <= geofence.radius_meters; // Get previous status const statusStmt = db.prepare(` SELECT is_inside FROM GeofenceStatus WHERE device_id = ? AND geofence_id = ? `); const status = statusStmt.get(deviceId, geofence.id); const wasInside = status ? status.is_inside === 1 : false; let event = null; // Generate event on state change if (isInside && !wasInside) { // ENTER const insertStmt = db.prepare(` INSERT INTO GeofenceEvent ( geofence_id, device_id, location_id, event_type, latitude, longitude, distance_from_center, timestamp ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `); const result = insertStmt.run( geofence.id, deviceId, location.id, 'enter', location.latitude, location.longitude, distance, location.timestamp ); event = { id: result.lastInsertRowid, type: 'enter', distance }; } else if (!isInside && wasInside) { // EXIT const insertStmt = db.prepare(` INSERT INTO GeofenceEvent ( geofence_id, device_id, location_id, event_type, latitude, longitude, distance_from_center, timestamp ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `); const result = insertStmt.run( geofence.id, deviceId, location.id, 'exit', location.latitude, location.longitude, distance, location.timestamp ); event = { id: result.lastInsertRowid, type: 'exit', distance }; } // Update status const now = new Date().toISOString(); const updateStmt = db.prepare(` INSERT INTO GeofenceStatus (device_id, geofence_id, is_inside, last_checked_at) VALUES (?, ?, ?, ?) ON CONFLICT(device_id, geofence_id) DO UPDATE SET is_inside = ?, last_checked_at = ?, last_enter_time = CASE WHEN ? = 1 AND is_inside = 0 THEN ? ELSE last_enter_time END, last_exit_time = CASE WHEN ? = 0 AND is_inside = 1 THEN ? ELSE last_exit_time END, updated_at = ? `); updateStmt.run( deviceId, geofence.id, isInside ? 1 : 0, now, isInside ? 1 : 0, now, isInside ? 1 : 0, now, isInside ? 1 : 0, now, now ); return { isInside, distance, event }; } finally { db.close(); } } // Main test function runTest() { const db = new Database(dbPath); let testGeofenceId = null; try { console.log('๐Ÿ“ Step 1: Creating test geofence...'); testGeofenceId = uuidv4(); const insertGeofence = db.prepare(` INSERT INTO Geofence ( id, name, description, shape_type, center_latitude, center_longitude, radius_meters, owner_id, device_id, color ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); insertGeofence.run( testGeofenceId, 'Test Geofence (Script)', 'Auto-generated test geofence', 'circle', TEST_CONFIG.geofenceCenter.lat, TEST_CONFIG.geofenceCenter.lon, TEST_CONFIG.geofenceRadius, TEST_CONFIG.userId, TEST_CONFIG.deviceId, '#f59e0b' ); console.log(`โœ“ Created geofence (ID: ${testGeofenceId})`); console.log(` Center: ${TEST_CONFIG.geofenceCenter.lat}, ${TEST_CONFIG.geofenceCenter.lon}`); console.log(` Radius: ${TEST_CONFIG.geofenceRadius}m\n`); const geofence = db .prepare('SELECT * FROM Geofence WHERE id = ?') .get(testGeofenceId); // Process locations let totalEvents = 0; for (let i = 0; i < TEST_CONFIG.locations.length; i++) { const testLoc = TEST_CONFIG.locations[i]; console.log(`๐Ÿ“Œ Step ${i + 2}: Processing location "${testLoc.label}"...`); console.log(` Coordinates: ${testLoc.lat}, ${testLoc.lon}`); const location = createTestLocation( testLoc.lat, testLoc.lon, TEST_CONFIG.deviceId ); console.log(` โœ“ Location saved (ID: ${location.id})`); const result = checkGeofence(location, geofence, TEST_CONFIG.deviceId); console.log( ` ๐Ÿ“ Distance from center: ${Math.round(result.distance)}m (${result.isInside ? 'INSIDE' : 'OUTSIDE'})` ); if (result.event) { console.log( ` ๐Ÿ”” Generated ${result.event.type.toUpperCase()} event (ID: ${result.event.id})` ); totalEvents++; } else { console.log(` โ„น No event (no state change)`); } console.log(''); } // Show results console.log('๐Ÿ“Š Final Results\n'); console.log(`Total events generated: ${totalEvents}\n`); const events = db .prepare( 'SELECT * FROM GeofenceEvent WHERE geofence_id = ? ORDER BY timestamp ASC' ) .all(testGeofenceId); if (events.length > 0) { console.log('Event History:'); events.forEach((event, idx) => { console.log(` ${idx + 1}. ${event.event_type.toUpperCase()}`); console.log(` Time: ${event.timestamp}`); console.log(` Distance: ${Math.round(event.distance_from_center)}m from center`); }); } console.log('\nโœ… Test completed successfully!\n'); console.log('๐Ÿ’ก Test geofence and events are in the database.'); console.log(' View with: sqlite3 data/database.sqlite "SELECT * FROM GeofenceEvent"'); console.log(` Delete with: sqlite3 data/database.sqlite "DELETE FROM Geofence WHERE id = '${testGeofenceId}'"`); } catch (error) { console.error('\nโŒ Test failed:', error); if (testGeofenceId) { db.prepare('DELETE FROM Geofence WHERE id = ?').run(testGeofenceId); console.log('โœ“ Cleaned up test data'); } process.exit(1); } finally { db.close(); } } // Run runTest();