mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-23 00:21:17 +00:00
fix: View Path airtime estimate omits fields on 0-relay (direct) packets
Caught on real stg data: a directly-received packet has relays=0, and while the plain int AirtimeRelayCount's omitempty correctly dropped it from JSON, the *float64 EstimatedAirtimeMs still encoded as a bare "estimatedAirtimeMs":0 (a non-nil pointer isn't "empty" to omitempty even when it points at zero). The frontend's typeof-number check then rendered "~0ms estimated airtime (undefined relays)". Now both fields are omitted together whenever there's nothing to relay. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e9e828c7e9
commit
e81f89da1e
2 files changed
+65
-1
No files matched your search
@@ -112,6 +112,65 @@ func TestHandlePacketPath_Airtime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestHandlePacketPath_Airtime_ZeroRelays confirms the fields are omitted
|
||||||
|
// -- not a bare "estimatedAirtimeMs":0 with no accompanying relay count --
|
||||||
|
// for a directly-received packet (no resolved_path relays at all). This
|
||||||
|
// caught a real bug on stg: the *float64 EstimatedAirtimeMs survived JSON
|
||||||
|
// encoding as 0 (a non-nil pointer isn't "empty"), while the plain int
|
||||||
|
// AirtimeRelayCount's omitempty dropped it at exactly 0, leaving the
|
||||||
|
// frontend a number with nothing to pair it with.
|
||||||
|
func TestHandlePacketPath_Airtime_ZeroRelays(t *testing.T) {
|
||||||
|
srv, router := setupTestServer(t)
|
||||||
|
if _, err := srv.db.conn.Exec(`DELETE FROM transmissions`); err != nil {
|
||||||
|
t.Fatalf("clear transmissions: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := srv.db.conn.Exec(`DELETE FROM observations`); err != nil {
|
||||||
|
t.Fatalf("clear observations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txRes, err := srv.db.conn.Exec(`INSERT INTO transmissions (raw_hex, hash, first_seen, route_type, payload_type, decoded_json, channel_hash)
|
||||||
|
VALUES ('AABBCCDDEE', 'airtimepath00003', '2026-01-15T10:00:00Z', 1, 5,
|
||||||
|
'{"type":"CHAN","channel":"#ping","text":"ping","sender":"Eve"}', '#ping')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("insert tx: %v", err)
|
||||||
|
}
|
||||||
|
txID, _ := txRes.LastInsertId()
|
||||||
|
obsRes, err := srv.db.conn.Exec(`INSERT INTO observers (id, name, iata) VALUES (?,?,?)`, "airtimeobs1", "ObsOne", "SJC")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("insert observer: %v", err)
|
||||||
|
}
|
||||||
|
obsIdx, _ := obsRes.LastInsertId()
|
||||||
|
if _, err := srv.db.conn.Exec(
|
||||||
|
`INSERT INTO observations (transmission_id, observer_idx, snr, rssi, path_json, resolved_path, timestamp) VALUES (?,?,?,?,?,?,?)`,
|
||||||
|
txID, obsIdx, 9.0, -88.0, `[]`, `[]`, 1736935200,
|
||||||
|
); err != nil {
|
||||||
|
t.Fatalf("insert observation: %v", err)
|
||||||
|
}
|
||||||
|
if err := srv.store.Load(); err != nil {
|
||||||
|
t.Fatalf("reload store: %v", err)
|
||||||
|
}
|
||||||
|
if !srv.store.WaitIndexesReady(5 * time.Second) {
|
||||||
|
t.Fatal("background indexes never became ready after reload")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/api/packets/airtimepath00003/path", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
if w.Code != 200 {
|
||||||
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &raw); err != nil {
|
||||||
|
t.Fatalf("decode: %v", err)
|
||||||
|
}
|
||||||
|
if _, present := raw["estimatedAirtimeMs"]; present {
|
||||||
|
t.Errorf("estimatedAirtimeMs = %v, want absent for a direct reception with 0 relays", raw["estimatedAirtimeMs"])
|
||||||
|
}
|
||||||
|
if _, present := raw["airtimeRelayCount"]; present {
|
||||||
|
t.Errorf("airtimeRelayCount = %v, want absent for a direct reception with 0 relays", raw["airtimeRelayCount"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestHandlePacketPath_Airtime_StoreUnavailable confirms the field is
|
// TestHandlePacketPath_Airtime_StoreUnavailable confirms the field is
|
||||||
// simply omitted -- not a guessed zero -- when the in-memory store has no
|
// simply omitted -- not a guessed zero -- when the in-memory store has no
|
||||||
// record of this transmission's ID (e.g. DB-only mode, or an old packet
|
// record of this transmission's ID (e.g. DB-only mode, or an old packet
|
||||||
|
|||||||
@@ -3286,7 +3286,12 @@ func (s *Server) annotatePacketPathAirtime(resp *PacketPathResponse) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, relays, ok := s.store.AirtimeAndRelayCountForTransmission(resp.TxID)
|
total, relays, ok := s.store.AirtimeAndRelayCountForTransmission(resp.TxID)
|
||||||
if !ok {
|
if !ok || relays == 0 {
|
||||||
|
// relays == 0 means a direct reception with nothing to relay --
|
||||||
|
// there's no meaningful airtime estimate to show (and 0 would
|
||||||
|
// otherwise survive JSON encoding as a bare "estimatedAirtimeMs":0
|
||||||
|
// while the omitempty int AirtimeRelayCount vanishes alongside it,
|
||||||
|
// leaving the frontend with a number but no relay count to pair it with).
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ms := float64(total.Microseconds()) / 1000.0
|
ms := float64(total.Microseconds()) / 1000.0
|
||||||
|
|||||||
Reference in new issue
Block a user