mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-27 03:07:54 +00:00
124 lines
4.0 KiB
Go
124 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// TestBridgeScore_HandleNodesSurface verifies that /api/nodes
|
|
// includes a `bridge_score` field on repeater rows after the bridge
|
|
// recomputer has run. Drives the line-graph A-B-C-D through the full
|
|
// pipeline: insert nodes, populate the neighbor graph, force a
|
|
// recompute, hit the handler, parse the response. Issue #672 axis 2.
|
|
func TestBridgeScore_HandleNodesSurface(t *testing.T) {
|
|
db := setupCapabilityTestDB(t)
|
|
defer db.conn.Close()
|
|
// handleNodes/db.GetNodes selects a foreign_advert column not in
|
|
// the minimal capability-test schema.
|
|
if _, err := db.conn.Exec(`ALTER TABLE nodes ADD COLUMN foreign_advert INTEGER DEFAULT 0`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Four repeater nodes in a line.
|
|
pks := []string{
|
|
"aaaa000000000000000000000000000000000000000000000000000000000000",
|
|
"bbbb000000000000000000000000000000000000000000000000000000000000",
|
|
"cccc000000000000000000000000000000000000000000000000000000000000",
|
|
"dddd000000000000000000000000000000000000000000000000000000000000",
|
|
}
|
|
recent := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
|
for _, pk := range pks {
|
|
if _, err := db.conn.Exec(`INSERT INTO nodes
|
|
(public_key, name, role, lat, lon, last_seen, first_seen, advert_count)
|
|
VALUES (?, ?, 'repeater', 37.5, -122.0, ?, ?, 10)`,
|
|
pk, "node-"+pk[:4], recent, recent); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
store := NewPacketStore(db, nil)
|
|
// Build neighbor graph with the line A-B-C-D. Add each edge
|
|
// `count` times so its time-decayed Score saturates.
|
|
g := NewNeighborGraph()
|
|
now := time.Now()
|
|
obs := "obs-test"
|
|
snr := 5.0
|
|
for i := 0; i < 10; i++ {
|
|
g.upsertEdge(pks[0], pks[1], "aa", obs, &snr, now)
|
|
g.upsertEdge(pks[1], pks[2], "bb", obs, &snr, now)
|
|
g.upsertEdge(pks[2], pks[3], "cc", obs, &snr, now)
|
|
}
|
|
store.graph.Store(g)
|
|
|
|
// Direct invocation of the recomputer's compute path — bypassing
|
|
// StartBridgeScoreRecomputer's package-level once-flag (which is
|
|
// problematic across tests).
|
|
recomputeBridgeScoresSafe(store)
|
|
|
|
snap := store.GetBridgeScoreMap()
|
|
if len(snap) == 0 {
|
|
t.Fatalf("expected non-empty bridge score snapshot, got empty")
|
|
}
|
|
// Sanity: middle nodes b/c must be positive, ends must be zero.
|
|
if snap[pks[1]] <= 0 || snap[pks[2]] <= 0 {
|
|
t.Errorf("middle nodes should have positive bridge: b=%v c=%v",
|
|
snap[pks[1]], snap[pks[2]])
|
|
}
|
|
if snap[pks[0]] != 0 || snap[pks[3]] != 0 {
|
|
t.Errorf("end nodes should have zero bridge: a=%v d=%v",
|
|
snap[pks[0]], snap[pks[3]])
|
|
}
|
|
|
|
// Wire a Server, call handleNodes, parse the response.
|
|
cfg := &Config{Port: 3000}
|
|
hub := NewHub()
|
|
srv := NewServer(db, cfg, hub)
|
|
srv.store = store
|
|
|
|
router := mux.NewRouter()
|
|
srv.RegisterRoutes(router)
|
|
|
|
req := httptest.NewRequest("GET", "/api/nodes?limit=100", nil)
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != 200 {
|
|
t.Fatalf("handleNodes status: want 200, got %d body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
var resp struct {
|
|
Nodes []map[string]interface{} `json:"nodes"`
|
|
}
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("decode: %v body=%s", err, rr.Body.String())
|
|
}
|
|
gotBy := map[string]map[string]interface{}{}
|
|
for _, n := range resp.Nodes {
|
|
if pk, _ := n["public_key"].(string); pk != "" {
|
|
gotBy[pk] = n
|
|
}
|
|
}
|
|
for _, pk := range pks {
|
|
n, ok := gotBy[pk]
|
|
if !ok {
|
|
t.Errorf("node %s missing from response", pk[:4])
|
|
continue
|
|
}
|
|
if _, has := n["bridge_score"]; !has {
|
|
t.Errorf("node %s: bridge_score field absent from response", pk[:4])
|
|
}
|
|
}
|
|
// Middle node B must report a non-zero bridge_score; end node A
|
|
// must report exactly zero. These two assertions together prevent
|
|
// a "field present but always 0" regression.
|
|
if v, _ := gotBy[pks[1]]["bridge_score"].(float64); v <= 0 {
|
|
t.Errorf("middle node B bridge_score in API response should be > 0, got %v", v)
|
|
}
|
|
if v, _ := gotBy[pks[0]]["bridge_score"].(float64); v != 0 {
|
|
t.Errorf("end node A bridge_score in API response should be 0, got %v", v)
|
|
}
|
|
}
|