Files
meshcore-analyzer/test-db.js
you c809d46b54 Extract server-helpers.js and add unit tests for server logic + db.js
- 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
2026-03-24 01:09:03 +00:00

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);