mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 12:18:47 +00:00
Fixes #2331 with the additive API option. The Last Relayed row shows green **actively relaying** only when `last_certain_relay` falls within the existing `healthThresholds.relayActiveHours` window. Missing, invalid, future or expired evidence adds no idle/alive claim. Existing `relay_active`, attributed timestamps and counts retain their meanings. The optional node list/detail field uses the existing certain-relay path: an eligible observed frame, matching hop, unique prefix, no retired namesake and existing signature checks. Originating an advert does not count. Forwarding another node's advert can count, even with zero non-advert attributions. Adverts with an unknown origin cannot establish forwarding. Evidence uses transmission `first_seen`, not late observation receipt time, and follows node-membership eviction. Snapshots take one read lock over requested keys, O(page size), with no packet scan or new database query. Work per qualifying hop stays bounded. No new dependency or setting; configuration documentation and OpenAPI explain the distinction. The test-first history includes assertion-failing RED commits and GREEN fixes. The initial RED's nil-return compile stub is explicitly permitted by `docs/agents/TDD.md`. Local native server/ingestor suites, focused race checks, 809 frontend helper assertions and 70 browser cases pass. Browser coverage includes both themes and full/side/tablet detail views. [Review regression RED](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40421464618/job/121916723339) proves both malformed-origin failures. All three independent review roles pass at `d1fe79cb`. [Final CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40422655728) passes on that exact commit, including all three browser shards and the two-architecture image check. --------- Co-authored-by: n30nex <6686206+n30nex@users.noreply.github.com>
124 lines
4.5 KiB
Go
124 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Review (#2261): every load path must feed certainHeard. A nil prefix map
|
|
// at any of the call sites in Load, loadChunk, scanAndMergeChunk, live
|
|
// ingest or late observations would leave the unit tests green; these go
|
|
// through the real paths and assert LastHeardMap.
|
|
|
|
const lpRelay = "4f2a" + "5555555555555555555555555555555555555555555555555555555555555"
|
|
|
|
// lpFixture is one flood transmission whose single 2-byte hop is lpRelay,
|
|
// a registered repeater, observed a minute ago. withResolved keeps the
|
|
// observation's resolved_path (the ingestor resolved the hop); otherwise it
|
|
// is NULL and the server re-resolves from path_json. hopInFirstObs false
|
|
// leaves the first observation without a path (for the late-observation
|
|
// case).
|
|
func lpFixture(t *testing.T, withResolved, hopInFirstObs bool) (*DB, *sql.DB, string) {
|
|
t.Helper()
|
|
path := createTestDBWithResolvedPath(t, 1, []string{lpRelay})
|
|
w, err := sql.Open("sqlite3", path+"?_journal_mode=WAL")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { w.Close() })
|
|
at := time.Now().UTC().Add(-time.Minute).Format(time.RFC3339)
|
|
raw := "1541" + lpRelay[:4] + "00" // GRP_TXT, flood, path byte: 2-byte hashes x1
|
|
pathJSON, resolved := `["`+strings.ToUpper(lpRelay[:4])+`"]`, interface{}(`["`+lpRelay+`"]`)
|
|
if !withResolved {
|
|
resolved = nil
|
|
}
|
|
if !hopInFirstObs {
|
|
pathJSON, resolved = `[]`, nil
|
|
}
|
|
for _, q := range []struct {
|
|
sql string
|
|
args []interface{}
|
|
}{
|
|
{`ALTER TABLE transmissions ADD COLUMN from_pubkey TEXT`, nil},
|
|
{`UPDATE transmissions SET raw_hex=?, route_type=1, payload_type=5, first_seen=?`, []interface{}{raw, at}},
|
|
{`UPDATE observations SET raw_hex=?, timestamp=?, path_json=?, resolved_path=?`, []interface{}{raw, at, pathJSON, resolved}},
|
|
{`INSERT INTO nodes (public_key, name, role) VALUES (?, 'Relay', 'repeater')`, []interface{}{lpRelay}},
|
|
} {
|
|
if _, err := w.Exec(q.sql, q.args...); err != nil {
|
|
t.Fatalf("%s: %v", q.sql, err)
|
|
}
|
|
}
|
|
db, err := OpenDB(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { db.conn.Close() })
|
|
return db, w, at
|
|
}
|
|
|
|
func TestCertainHeardFedByEveryLoadPath(t *testing.T) {
|
|
type mode struct {
|
|
name string
|
|
withResolved, hopInFirstObs bool
|
|
run func(t *testing.T, s *PacketStore, db *DB, w *sql.DB, at string)
|
|
}
|
|
load := func(t *testing.T, s *PacketStore, _ *DB, _ *sql.DB, _ string) {
|
|
if err := s.Load(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
chunk := func(t *testing.T, s *PacketStore, _ *DB, _ *sql.DB, _ string) {
|
|
if err := s.loadChunk(time.Now().Add(-time.Hour), time.Now()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
chunked := func(t *testing.T, s *PacketStore, _ *DB, _ *sql.DB, _ string) {
|
|
if err := s.LoadChunked(1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
live := func(t *testing.T, s *PacketStore, _ *DB, _ *sql.DB, _ string) {
|
|
s.IngestNewFromDB(0, 100)
|
|
}
|
|
late := func(t *testing.T, s *PacketStore, db *DB, w *sql.DB, at string) {
|
|
if err := s.Load(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if heard, _ := s.LastHeardMap([]string{lpRelay}); len(heard) != 0 {
|
|
t.Fatal("setup: the first observation carries no hop")
|
|
}
|
|
maxObs := db.GetMaxObservationID()
|
|
if _, err := w.Exec(`INSERT INTO observations (id, transmission_id, observer_id, observer_name, direction, snr, rssi, score, path_json, timestamp, raw_hex, resolved_path)
|
|
VALUES (2, 1, 'obs2', 'Obs2', 'RX', -9, -79, 5, ?, ?, ?, NULL)`, `["`+strings.ToUpper(lpRelay[:4])+`"]`, at, "1541"+lpRelay[:4]+"00"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s.IngestNewObservations(maxObs, 100)
|
|
}
|
|
for _, m := range []mode{
|
|
{"Load/resolved_path", true, true, load},
|
|
{"Load/path_json", false, true, load},
|
|
{"loadChunk/resolved_path", true, true, chunk},
|
|
{"loadChunk/path_json", false, true, chunk},
|
|
{"LoadChunked/resolved_path", true, true, chunked},
|
|
{"LoadChunked/path_json", false, true, chunked},
|
|
{"IngestNewFromDB", false, true, live},
|
|
{"IngestNewObservations", false, false, late},
|
|
} {
|
|
t.Run(m.name, func(t *testing.T) {
|
|
db, w, at := lpFixture(t, m.withResolved, m.hopInFirstObs)
|
|
s := NewPacketStore(db, &PacketStoreConfig{})
|
|
s.useResolvedPathIndex = true
|
|
s.initResolvedPathIndex()
|
|
m.run(t, s, db, w, at)
|
|
heard, _ := s.LastHeardMap([]string{lpRelay})
|
|
want, _ := time.Parse(time.RFC3339, at)
|
|
if !heard[lpRelay].Equal(want) {
|
|
t.Fatalf("LastHeardMap[relay] = %v; want %v: this path does not feed certainHeard", heard[lpRelay], want)
|
|
}
|
|
assertCertainRelay(t, s, lpRelay, at)
|
|
})
|
|
}
|
|
}
|