mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-06 06:37:22 +00:00
Fixes #2083. Node-path regression tests could request `/paths` while background indexes were still loading, intermittently receiving HTTP 503 instead of exercising hop resolution or sorting. Seven fixture loads now await the existing bounded `WaitIndexesReady` signal. The anchor-bias test uses the same signal instead of polling. This changes five test files only. Production readiness behavior and every HTTP/content assertion are preserved. No dependencies, configuration or customizer changes. ## Validation - Unchanged baseline: 94 passed / 6 failed across 100 targeted executions; failures were actual HTTP 503 assertions. - Fixed setup: 200/200 targeted executions passed. - Parent independently ran the entire server suite: exit 0, 83.4 seconds. - All 183 standalone frontend suites and 20 real Chromium route-map checks passed. - Formatting, whitespace and PII checks passed. TDD justification: test-fixture synchronization repair, with no production logic change. The existing unchanged behavioral assertions supplied the before/after failure proof; no fabricated failing test was added. Local browser checks used Chromium against the unchanged production source; OpenClaw profile/external preflight were unavailable. The unrelated ingestor symlink test requires a Windows privilege absent locally; Linux CI validates that suite. Final CI must pass before merge.
82 lines
3.2 KiB
Go
82 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// TestHandleNodePaths_PrefixCollisionExclusion verifies that paths through a node
|
|
// sharing a 2-char prefix with another node are not returned as false positives
|
|
// when they have no resolved_path data (issue #929).
|
|
//
|
|
// Setup:
|
|
// - nodeA (target): pubkey starts with "7a", no GPS
|
|
// - nodeB (other): pubkey starts with "7a", has GPS → "7a" resolves to nodeB
|
|
// - tx1: path ["7a"], resolved_path NULL → false positive candidate, must be excluded
|
|
// - tx2: path ["7a"], resolved_path contains nodeA pubkey → SQL-confirmed, must be included
|
|
func TestHandleNodePaths_PrefixCollisionExclusion(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
recent := time.Now().Add(-1 * time.Hour).Format(time.RFC3339)
|
|
recentEpoch := time.Now().Add(-1 * time.Hour).Unix()
|
|
|
|
nodeAPK := "7acb1111aaaabbbb"
|
|
nodeBPK := "7aff2222ccccdddd" // same "7a" prefix, has GPS so resolveHop("7a") picks B
|
|
|
|
db.conn.Exec(`INSERT INTO nodes (public_key, name, role, lat, lon, last_seen, first_seen, advert_count)
|
|
VALUES (?, 'NodeA', 'repeater', 0, 0, ?, '2026-01-01', 1)`, nodeAPK, recent)
|
|
db.conn.Exec(`INSERT INTO nodes (public_key, name, role, lat, lon, last_seen, first_seen, advert_count)
|
|
VALUES (?, 'NodeB', 'repeater', 37.5, -122.0, ?, '2026-01-01', 1)`, nodeBPK, recent)
|
|
|
|
// tx1: no resolved_path — should be excluded by hop-level check
|
|
db.conn.Exec(`INSERT INTO transmissions (id, raw_hex, hash, first_seen) VALUES (10, 'AA', 'hash_fp', ?)`, recent)
|
|
db.conn.Exec(`INSERT INTO observations (transmission_id, observer_idx, path_json, timestamp, resolved_path)
|
|
VALUES (10, NULL, '["7a"]', ?, NULL)`, recentEpoch)
|
|
|
|
// tx2: resolved_path confirms nodeA — must be included
|
|
db.conn.Exec(`INSERT INTO transmissions (id, raw_hex, hash, first_seen) VALUES (11, 'BB', 'hash_tp', ?)`, recent)
|
|
db.conn.Exec(`INSERT INTO observations (transmission_id, observer_idx, path_json, timestamp, resolved_path)
|
|
VALUES (11, NULL, '["7a"]', ?, ?)`, recentEpoch, `["`+nodeAPK+`"]`)
|
|
|
|
cfg := &Config{Port: 3000}
|
|
hub := NewHub()
|
|
srv := NewServer(db, cfg, hub)
|
|
store := NewPacketStore(db, nil)
|
|
if err := store.Load(); err != nil {
|
|
t.Fatalf("store.Load: %v", err)
|
|
}
|
|
if !store.WaitIndexesReady(5 * time.Second) {
|
|
t.Fatal("background indexes not ready within 5s")
|
|
}
|
|
srv.store = store
|
|
router := mux.NewRouter()
|
|
srv.RegisterRoutes(router)
|
|
|
|
req := httptest.NewRequest("GET", "/api/nodes/"+nodeAPK+"/paths", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp NodePathsResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
// Only the SQL-confirmed path (tx2) should be present; tx1 (false positive) must be excluded.
|
|
// tx1 and tx2 share the same raw path ["7a"] so they collapse into 1 unique path group.
|
|
// If tx1 were included, TotalTransmissions would be 2.
|
|
if resp.TotalPaths != 1 {
|
|
t.Errorf("expected 1 path group, got %d", resp.TotalPaths)
|
|
}
|
|
if resp.TotalTransmissions != 1 {
|
|
t.Errorf("expected 1 transmission (false positive tx1 excluded), got %d", resp.TotalTransmissions)
|
|
}
|
|
}
|