Files
meshcore-analyzer/cmd/server/decoder_test.go
Kpa-clawbot ce6e8d5237 feat: show transport code (T_FLOOD) in packets view (#337)
## Summary

Surfaces transport route types in the packets view by adding a **"T"
badge** next to the payload type badge for packets with
`TRANSPORT_FLOOD` (route type 0) or `TRANSPORT_DIRECT` (route type 3)
routes.

This helps mesh analysis — communities can quickly identify transported
packets and gain insights into scope usage adoption.

Closes #241

## What Changed

### Frontend (`public/`)
- **app.js**: Added `isTransportRoute(rt)` and `transportBadge(rt)`
helper functions that render a `<span class="badge
badge-transport">T</span>` badge with the full route type name as a
tooltip
- **packets.js**: Applied `transportBadge()` in all three packet row
render paths:
  - Flat (ungrouped) packet rows
  - Grouped packet header rows
  - Grouped packet child rows
- **style.css**: Added `.badge-transport` class with amber styling and
CSS variable support (`--transport-badge-bg`, `--transport-badge-fg`)
for theme customization

### Backend (`cmd/server/`)
- **decoder_test.go**: Added 6 new tests covering:
- `TestDecodeHeader_TransportFlood` — verifies route type 0 decodes as
TRANSPORT_FLOOD
- `TestDecodeHeader_TransportDirect` — verifies route type 3 decodes as
TRANSPORT_DIRECT
- `TestDecodeHeader_Flood` — verifies route type 1 (non-transport)
decodes correctly
- `TestIsTransportRoute` — verifies the helper identifies transport vs
non-transport routes
- `TestDecodePacket_TransportFloodHasCodes` — verifies transport codes
are extracted from T_FLOOD packets
- `TestDecodePacket_FloodHasNoCodes` — verifies FLOOD packets have no
transport codes

## Visual

In the packets table Type column, transport packets now show:
```
[Channel Msg] [T]    ← transport packet
[Channel Msg]        ← normal flood packet
```

The "T" badge has an amber color scheme and shows the full route type
name on hover.

## Tests

- All Go tests pass (`cmd/server` and `cmd/ingestor`)
- All frontend tests pass (`test-packet-filter.js`, `test-aging.js`,
`test-frontend-helpers.js`)
- Cache busters bumped in `index.html`

---------

Co-authored-by: you <you@example.com>
Co-authored-by: Kpa-clawbot <259247574+Kpa-clawbot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 18:39:38 -07:00

96 lines
2.8 KiB
Go

package main
import (
"testing"
)
func TestDecodeHeader_TransportFlood(t *testing.T) {
// Route type 0 = TRANSPORT_FLOOD, payload type 5 = GRP_TXT, version 0
// Header byte: (0 << 6) | (5 << 2) | 0 = 0x14
h := decodeHeader(0x14)
if h.RouteType != RouteTransportFlood {
t.Errorf("expected RouteTransportFlood (0), got %d", h.RouteType)
}
if h.RouteTypeName != "TRANSPORT_FLOOD" {
t.Errorf("expected TRANSPORT_FLOOD, got %s", h.RouteTypeName)
}
if h.PayloadType != PayloadGRP_TXT {
t.Errorf("expected PayloadGRP_TXT (5), got %d", h.PayloadType)
}
}
func TestDecodeHeader_TransportDirect(t *testing.T) {
// Route type 3 = TRANSPORT_DIRECT, payload type 2 = TXT_MSG, version 0
// Header byte: (0 << 6) | (2 << 2) | 3 = 0x0B
h := decodeHeader(0x0B)
if h.RouteType != RouteTransportDirect {
t.Errorf("expected RouteTransportDirect (3), got %d", h.RouteType)
}
if h.RouteTypeName != "TRANSPORT_DIRECT" {
t.Errorf("expected TRANSPORT_DIRECT, got %s", h.RouteTypeName)
}
}
func TestDecodeHeader_Flood(t *testing.T) {
// Route type 1 = FLOOD, payload type 4 = ADVERT
// Header byte: (0 << 6) | (4 << 2) | 1 = 0x11
h := decodeHeader(0x11)
if h.RouteType != RouteFlood {
t.Errorf("expected RouteFlood (1), got %d", h.RouteType)
}
if h.RouteTypeName != "FLOOD" {
t.Errorf("expected FLOOD, got %s", h.RouteTypeName)
}
}
func TestIsTransportRoute(t *testing.T) {
if !isTransportRoute(RouteTransportFlood) {
t.Error("expected RouteTransportFlood to be transport")
}
if !isTransportRoute(RouteTransportDirect) {
t.Error("expected RouteTransportDirect to be transport")
}
if isTransportRoute(RouteFlood) {
t.Error("expected RouteFlood to NOT be transport")
}
if isTransportRoute(RouteDirect) {
t.Error("expected RouteDirect to NOT be transport")
}
}
func TestDecodePacket_TransportFloodHasCodes(t *testing.T) {
// Build a minimal TRANSPORT_FLOOD packet:
// Header 0x14 (route=0/T_FLOOD, payload=5/GRP_TXT)
// Transport codes: AABB CCDD (4 bytes)
// Path byte: 0x00 (hashSize=1, hashCount=0)
// Payload: at least some bytes for GRP_TXT
hex := "14AABBCCDD00112233445566778899"
pkt, err := DecodePacket(hex)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pkt.TransportCodes == nil {
t.Fatal("expected transport codes to be present")
}
if pkt.TransportCodes.Code1 != "AABB" {
t.Errorf("expected Code1=AABB, got %s", pkt.TransportCodes.Code1)
}
if pkt.TransportCodes.Code2 != "CCDD" {
t.Errorf("expected Code2=CCDD, got %s", pkt.TransportCodes.Code2)
}
}
func TestDecodePacket_FloodHasNoCodes(t *testing.T) {
// Header 0x11 (route=1/FLOOD, payload=4/ADVERT)
// Path byte: 0x00 (no hops)
// Some payload bytes
hex := "110011223344556677889900AABBCCDD"
pkt, err := DecodePacket(hex)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pkt.TransportCodes != nil {
t.Error("expected no transport codes for FLOOD route")
}
}