Files
meshcore-analyzer/cmd/server/multibyte_enrich_test.go
T
Kpa-clawbot e86b5a3a0c feat: show multi-byte hash support indicator on map markers (#1002)
## Summary

Show 2-byte hash support indicator on map markers. Fixes #903.

## What changed

### Backend (`cmd/server/store.go`, `cmd/server/routes.go`)

- **`EnrichNodeWithMultiByte()`** — new enrichment function that adds
`multi_byte_status` (confirmed/suspected/unknown), `multi_byte_evidence`
(advert/path), and `multi_byte_max_hash_size` fields to node API
responses
- **`GetMultiByteCapMap()`** — cached (15s TTL) map of pubkey →
`MultiByteCapEntry`, reusing the existing `computeMultiByteCapability()`
logic that combines advert-based and path-hop-based evidence
- Wired into both `/api/nodes` (list) and `/api/nodes/{pubkey}` (detail)
endpoints

### Frontend (`public/map.js`)

- Added **"Multi-byte support"** checkbox in the map Display controls
section
- When toggled on, repeater markers change color:
  - 🟢 Green (`#27ae60`) — **confirmed** (advertised with hash_size ≥ 2)
- 🟡 Yellow (`#f39c12`) — **suspected** (seen as hop in multi-byte path)
  - 🔴 Red (`#e74c3c`) — **unknown** (no multi-byte evidence)
- Popup tooltip shows multi-byte status and evidence for repeaters
- State persisted in localStorage (`meshcore-map-multibyte-overlay`)

## TDD

- Red commit: `2f49cbc` — failing test for `EnrichNodeWithMultiByte`
- Green commit: `4957782` — implementation + passing tests

## Performance

- `GetMultiByteCapMap()` uses a 15s TTL cache (same pattern as
`GetNodeHashSizeInfo`)
- Enrichment is O(n) over nodes, no per-item API calls
- Frontend color override is computed inline during existing marker
render loop — no additional DOM rebuilds

---------

Co-authored-by: you <you@example.com>
2026-05-03 08:56:09 -07:00

58 lines
1.7 KiB
Go

package main
import "testing"
func TestEnrichNodeWithMultiByte(t *testing.T) {
t.Run("nil entry leaves no fields", func(t *testing.T) {
node := map[string]interface{}{"public_key": "abc123"}
EnrichNodeWithMultiByte(node, nil)
if _, ok := node["multi_byte_status"]; ok {
t.Error("expected no multi_byte_status with nil entry")
}
})
t.Run("confirmed entry sets fields", func(t *testing.T) {
node := map[string]interface{}{"public_key": "abc123"}
entry := &MultiByteCapEntry{
Status: "confirmed",
Evidence: "advert",
MaxHashSize: 2,
}
EnrichNodeWithMultiByte(node, entry)
if node["multi_byte_status"] != "confirmed" {
t.Errorf("expected confirmed, got %v", node["multi_byte_status"])
}
if node["multi_byte_evidence"] != "advert" {
t.Errorf("expected advert, got %v", node["multi_byte_evidence"])
}
if node["multi_byte_max_hash_size"] != 2 {
t.Errorf("expected 2, got %v", node["multi_byte_max_hash_size"])
}
})
t.Run("suspected entry sets fields", func(t *testing.T) {
node := map[string]interface{}{"public_key": "abc123"}
entry := &MultiByteCapEntry{
Status: "suspected",
Evidence: "path",
MaxHashSize: 2,
}
EnrichNodeWithMultiByte(node, entry)
if node["multi_byte_status"] != "suspected" {
t.Errorf("expected suspected, got %v", node["multi_byte_status"])
}
})
t.Run("unknown entry sets status unknown", func(t *testing.T) {
node := map[string]interface{}{"public_key": "abc123"}
entry := &MultiByteCapEntry{
Status: "unknown",
MaxHashSize: 1,
}
EnrichNodeWithMultiByte(node, entry)
if node["multi_byte_status"] != "unknown" {
t.Errorf("expected unknown, got %v", node["multi_byte_status"])
}
})
}