mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-03-29 08:29:55 +00:00
- Extract pure/near-pure functions from server.js into server-helpers.js: loadConfigFile, loadThemeFile, buildHealthConfig, getHealthMs, isHashSizeFlipFlop, computeContentHash, geoDist, deriveHashtagChannelKey, buildBreakdown, disambiguateHops, updateHashSizeForPacket, rebuildHashSizeMap, requireApiKey - Add test-server-helpers.js (70 tests) covering all extracted functions - Add test-db.js (68 tests) covering all db.js exports with temp SQLite DB - Coverage: 39.97% → 81.3% statements, 56% → 68.5% branches, 65.5% → 89.5% functions
268 lines
9.2 KiB
JavaScript
268 lines
9.2 KiB
JavaScript
'use strict';
|
|
|
|
// Test db.js functions with a temp database
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'meshcore-db-test-'));
|
|
const dbPath = path.join(tmpDir, 'test.db');
|
|
process.env.DB_PATH = dbPath;
|
|
|
|
// Now require db.js — it will use our temp DB
|
|
const db = require('./db');
|
|
|
|
let passed = 0, failed = 0;
|
|
function assert(cond, msg) {
|
|
if (cond) { passed++; console.log(` ✅ ${msg}`); }
|
|
else { failed++; console.error(` ❌ ${msg}`); }
|
|
}
|
|
|
|
function cleanup() {
|
|
try { db.db.close(); } catch {}
|
|
try { fs.rmSync(tmpDir, { recursive: true }); } catch {}
|
|
}
|
|
|
|
console.log('── db.js tests ──\n');
|
|
|
|
// --- Schema ---
|
|
console.log('Schema:');
|
|
{
|
|
const tables = db.db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map(r => r.name);
|
|
assert(tables.includes('nodes'), 'nodes table exists');
|
|
assert(tables.includes('observers'), 'observers table exists');
|
|
assert(tables.includes('transmissions'), 'transmissions table exists');
|
|
assert(tables.includes('observations'), 'observations table exists');
|
|
}
|
|
|
|
// --- upsertNode ---
|
|
console.log('\nupsertNode:');
|
|
{
|
|
db.upsertNode({ public_key: 'aabbccdd11223344aabbccdd11223344', name: 'TestNode', role: 'repeater', lat: 37.0, lon: -122.0 });
|
|
const node = db.getNode('aabbccdd11223344aabbccdd11223344');
|
|
assert(node !== null, 'node inserted');
|
|
assert(node.name === 'TestNode', 'name correct');
|
|
assert(node.role === 'repeater', 'role correct');
|
|
assert(node.lat === 37.0, 'lat correct');
|
|
|
|
// Update
|
|
db.upsertNode({ public_key: 'aabbccdd11223344aabbccdd11223344', name: 'UpdatedNode', role: 'room' });
|
|
const node2 = db.getNode('aabbccdd11223344aabbccdd11223344');
|
|
assert(node2.name === 'UpdatedNode', 'name updated');
|
|
assert(node2.advert_count === 2, 'advert_count incremented');
|
|
}
|
|
|
|
// --- upsertObserver ---
|
|
console.log('\nupsertObserver:');
|
|
{
|
|
db.upsertObserver({ id: 'obs-1', name: 'Observer One', iata: 'SFO' });
|
|
const observers = db.getObservers();
|
|
assert(observers.length >= 1, 'observer inserted');
|
|
assert(observers.some(o => o.id === 'obs-1'), 'observer found by id');
|
|
assert(observers.find(o => o.id === 'obs-1').name === 'Observer One', 'observer name correct');
|
|
|
|
// Upsert again
|
|
db.upsertObserver({ id: 'obs-1', name: 'Observer Updated' });
|
|
const obs2 = db.getObservers().find(o => o.id === 'obs-1');
|
|
assert(obs2.name === 'Observer Updated', 'observer name updated');
|
|
assert(obs2.packet_count === 2, 'packet_count incremented');
|
|
}
|
|
|
|
// --- updateObserverStatus ---
|
|
console.log('\nupdateObserverStatus:');
|
|
{
|
|
db.updateObserverStatus({ id: 'obs-2', name: 'Status Observer', iata: 'LAX', model: 'T-Deck' });
|
|
const obs = db.getObservers().find(o => o.id === 'obs-2');
|
|
assert(obs !== null, 'observer created via status update');
|
|
assert(obs.model === 'T-Deck', 'model set');
|
|
assert(obs.packet_count === 0, 'packet_count stays 0 for status update');
|
|
}
|
|
|
|
// --- insertTransmission ---
|
|
console.log('\ninsertTransmission:');
|
|
{
|
|
const result = db.insertTransmission({
|
|
raw_hex: '0400aabbccdd',
|
|
hash: 'hash-001',
|
|
timestamp: '2025-01-01T00:00:00Z',
|
|
observer_id: 'obs-1',
|
|
observer_name: 'Observer One',
|
|
direction: 'rx',
|
|
snr: 10.5,
|
|
rssi: -85,
|
|
route_type: 1,
|
|
payload_type: 4,
|
|
payload_version: 1,
|
|
path_json: '["aabb","ccdd"]',
|
|
decoded_json: '{"type":"ADVERT","pubKey":"aabbccdd11223344aabbccdd11223344","name":"TestNode"}',
|
|
});
|
|
assert(result !== null, 'transmission inserted');
|
|
assert(result.transmissionId > 0, 'has transmissionId');
|
|
assert(result.observationId > 0, 'has observationId');
|
|
|
|
// Duplicate hash = same transmission, new observation
|
|
const result2 = db.insertTransmission({
|
|
raw_hex: '0400aabbccdd',
|
|
hash: 'hash-001',
|
|
timestamp: '2025-01-01T00:01:00Z',
|
|
observer_id: 'obs-2',
|
|
observer_name: 'Observer Two',
|
|
direction: 'rx',
|
|
snr: 8.0,
|
|
rssi: -90,
|
|
route_type: 1,
|
|
payload_type: 4,
|
|
path_json: '["aabb"]',
|
|
decoded_json: '{"type":"ADVERT","pubKey":"aabbccdd11223344aabbccdd11223344","name":"TestNode"}',
|
|
});
|
|
assert(result2.transmissionId === result.transmissionId, 'same transmissionId for duplicate hash');
|
|
|
|
// No hash = null
|
|
const result3 = db.insertTransmission({ raw_hex: '0400' });
|
|
assert(result3 === null, 'no hash returns null');
|
|
}
|
|
|
|
// --- getPackets ---
|
|
console.log('\ngetPackets:');
|
|
{
|
|
const { rows, total } = db.getPackets({ limit: 10 });
|
|
assert(total >= 1, 'has packets');
|
|
assert(rows.length >= 1, 'returns rows');
|
|
assert(rows[0].hash === 'hash-001', 'correct hash');
|
|
|
|
// Filter by type
|
|
const { rows: r2 } = db.getPackets({ type: 4 });
|
|
assert(r2.length >= 1, 'filter by type works');
|
|
|
|
const { rows: r3 } = db.getPackets({ type: 99 });
|
|
assert(r3.length === 0, 'filter by nonexistent type returns empty');
|
|
|
|
// Filter by hash
|
|
const { rows: r4 } = db.getPackets({ hash: 'hash-001' });
|
|
assert(r4.length >= 1, 'filter by hash works');
|
|
}
|
|
|
|
// --- getPacket ---
|
|
console.log('\ngetPacket:');
|
|
{
|
|
const { rows } = db.getPackets({ limit: 1 });
|
|
const pkt = db.getPacket(rows[0].id);
|
|
assert(pkt !== null, 'getPacket returns packet');
|
|
assert(pkt.hash === 'hash-001', 'correct packet');
|
|
|
|
const missing = db.getPacket(999999);
|
|
assert(missing === null, 'missing packet returns null');
|
|
}
|
|
|
|
// --- getTransmission ---
|
|
console.log('\ngetTransmission:');
|
|
{
|
|
const tx = db.getTransmission(1);
|
|
assert(tx !== null, 'getTransmission returns data');
|
|
assert(tx.hash === 'hash-001', 'correct hash');
|
|
|
|
const missing = db.getTransmission(999999);
|
|
assert(missing === null, 'missing transmission returns null');
|
|
}
|
|
|
|
// --- getNodes ---
|
|
console.log('\ngetNodes:');
|
|
{
|
|
const { rows, total } = db.getNodes({ limit: 10 });
|
|
assert(total >= 1, 'has nodes');
|
|
assert(rows.length >= 1, 'returns node rows');
|
|
|
|
// Sort by name
|
|
const { rows: r2 } = db.getNodes({ sortBy: 'name' });
|
|
assert(r2.length >= 1, 'sort by name works');
|
|
|
|
// Invalid sort falls back to last_seen
|
|
const { rows: r3 } = db.getNodes({ sortBy: 'DROP TABLE nodes' });
|
|
assert(r3.length >= 1, 'invalid sort is safe');
|
|
}
|
|
|
|
// --- getNode ---
|
|
console.log('\ngetNode:');
|
|
{
|
|
const node = db.getNode('aabbccdd11223344aabbccdd11223344');
|
|
assert(node !== null, 'getNode returns node');
|
|
assert(Array.isArray(node.recentPackets), 'has recentPackets');
|
|
|
|
const missing = db.getNode('nonexistent');
|
|
assert(missing === null, 'missing node returns null');
|
|
}
|
|
|
|
// --- searchNodes ---
|
|
console.log('\nsearchNodes:');
|
|
{
|
|
const results = db.searchNodes('Updated');
|
|
assert(results.length >= 1, 'search by name');
|
|
|
|
const r2 = db.searchNodes('aabbcc');
|
|
assert(r2.length >= 1, 'search by pubkey prefix');
|
|
|
|
const r3 = db.searchNodes('nonexistent_xyz');
|
|
assert(r3.length === 0, 'no results for nonexistent');
|
|
}
|
|
|
|
// --- getStats ---
|
|
console.log('\ngetStats:');
|
|
{
|
|
const stats = db.getStats();
|
|
assert(stats.totalNodes >= 1, 'totalNodes');
|
|
assert(stats.totalObservers >= 1, 'totalObservers');
|
|
assert(typeof stats.totalPackets === 'number', 'totalPackets is number');
|
|
assert(typeof stats.packetsLastHour === 'number', 'packetsLastHour is number');
|
|
}
|
|
|
|
// --- getNodeHealth ---
|
|
console.log('\ngetNodeHealth:');
|
|
{
|
|
const health = db.getNodeHealth('aabbccdd11223344aabbccdd11223344');
|
|
assert(health !== null, 'returns health data');
|
|
assert(health.node.name === 'UpdatedNode', 'has node info');
|
|
assert(typeof health.stats.totalPackets === 'number', 'has totalPackets stat');
|
|
assert(Array.isArray(health.observers), 'has observers array');
|
|
assert(Array.isArray(health.recentPackets), 'has recentPackets array');
|
|
|
|
const missing = db.getNodeHealth('nonexistent');
|
|
assert(missing === null, 'missing node returns null');
|
|
}
|
|
|
|
// --- getNodeAnalytics ---
|
|
console.log('\ngetNodeAnalytics:');
|
|
{
|
|
const analytics = db.getNodeAnalytics('aabbccdd11223344aabbccdd11223344', 7);
|
|
assert(analytics !== null, 'returns analytics');
|
|
assert(analytics.node.name === 'UpdatedNode', 'has node info');
|
|
assert(Array.isArray(analytics.activityTimeline), 'has activityTimeline');
|
|
assert(Array.isArray(analytics.snrTrend), 'has snrTrend');
|
|
assert(Array.isArray(analytics.packetTypeBreakdown), 'has packetTypeBreakdown');
|
|
assert(Array.isArray(analytics.observerCoverage), 'has observerCoverage');
|
|
assert(Array.isArray(analytics.hopDistribution), 'has hopDistribution');
|
|
assert(Array.isArray(analytics.peerInteractions), 'has peerInteractions');
|
|
assert(Array.isArray(analytics.uptimeHeatmap), 'has uptimeHeatmap');
|
|
assert(typeof analytics.computedStats.availabilityPct === 'number', 'has availabilityPct');
|
|
assert(typeof analytics.computedStats.signalGrade === 'string', 'has signalGrade');
|
|
|
|
const missing = db.getNodeAnalytics('nonexistent', 7);
|
|
assert(missing === null, 'missing node returns null');
|
|
}
|
|
|
|
// --- seed ---
|
|
console.log('\nseed:');
|
|
{
|
|
// Already has data, should return false
|
|
const result = db.seed();
|
|
assert(result === false, 'seed returns false when data exists');
|
|
}
|
|
|
|
cleanup();
|
|
delete process.env.DB_PATH;
|
|
|
|
console.log(`\n═══════════════════════════════════════`);
|
|
console.log(` PASSED: ${passed}`);
|
|
console.log(` FAILED: ${failed}`);
|
|
console.log(`═══════════════════════════════════════`);
|
|
if (failed > 0) process.exit(1);
|