mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-07-09 20:31:48 +00:00
1bdb92de88
Red commit: 94dc1d70a5
Fixes #1574.
cross-stack: justified — by design. Adds one server-side knob
(`liveMap.maxNodes`) on the Go API and consumes it on the frontend
(`public/live.js`) via the shared `/api/config/client` bootstrap in
`public/roles.js`. Cannot land server-only or frontend-only without
either dropping operator config (frontend-only) or leaving the literal
in place (server-only).
## Problem (per triage)
`public/live.js:2515-2516` hardcodes `/api/nodes?limit=2000` for the
live-map node-load path. Reporter measured headroom at N=4300 and
asked for an operator knob. Same `2000` magic also lives at
`public/live.js:480` for the VCR-rewind `/api/packets?limit=2000`.
## Fix
- New `liveMap.maxNodes` field in `Config` (default 2000).
- `Config.LiveMapMaxNodes()` server-side clamp: `[100, 20000]`;
zero/negative falls back to default. Defangs misconfig (e.g. 1M
would OOM the SQLite read + JSON serialization path).
- `/api/config/client` now returns `liveMapMaxNodes`.
- `public/roles.js` reads it at bootstrap into
`window.LIVE_MAP_MAX_NODES`
(default 2000 to preserve behavior on stale caches).
- `public/live.js` consumes `LIVE_MAP_MAX_NODES` at both the
`/api/nodes`
call sites (formerly :2515-2516) and the VCR-rewind `/api/packets`
call (formerly :480) — single source of truth, in-scope per triage's
"factor into a sibling const" suggestion.
- `config.example.json` documents the knob with `_comment_maxNodes` per
AGENTS.md config rule.
## TDD
1. **Red** (`94dc1d70`): added `test-issue-1574-live-map-max-nodes.js`
(grep-asserts the literal is gone + `LIVE_MAP_MAX_NODES` /
`liveMapMaxNodes` are wired + config example has the field) and
`cmd/server/livemap_maxnodes_1574_test.go` (`/api/config/client`
exposes `liveMapMaxNodes` + clamp table-driven cases). Stub
`LiveMapMaxNodes()` returns 0 so the test compiles and fails on
assertion, not import.
2. **Green** (this commit): real `LiveMapMaxNodes()` clamp + wire-up.
All assertions pass; existing `cmd/server` suite still green.
## E2E note
Frontend assertion is grep-based (literal removal + constant
reference), in the established `test-issue-*` style used elsewhere
(e.g. `test-issue-1189-live-iata-badge.js`). No Playwright change
needed for a literal-replace; behavior validation is the server-side
clamp + JSON shape tests.
## Out of scope
No customizer UI change — operators set this in `config.json`, same
pattern as `liveMap.propagationBufferMs`. Customizer surfacing can
land as a follow-up if the operator wants it.
---------
Co-authored-by: mc-bot <bot@corescope.local>
Co-authored-by: Kpa-clawbot <bot@meshcore-analyzer>
68 lines
1.9 KiB
Go
68 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// Behavior test (#1574): /api/config/client must expose `liveMapMaxNodes`
|
|
// so the frontend can honor the operator-configured live-map node cap
|
|
// instead of the hardcoded 2000 in public/live.js. Default is 2000;
|
|
// operators tune via `liveMap.maxNodes` in config.json. Server clamps to
|
|
// [100, 20000] to defang misconfig.
|
|
func TestConfigClientExposesLiveMapMaxNodes(t *testing.T) {
|
|
_, router := setupTestServer(t)
|
|
req := httptest.NewRequest("GET", "/api/config/client", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != 200 {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
var body map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
v, present := body["liveMapMaxNodes"]
|
|
if !present {
|
|
t.Fatal("expected liveMapMaxNodes in /api/config/client response")
|
|
}
|
|
n, ok := v.(float64)
|
|
if !ok {
|
|
t.Fatalf("expected liveMapMaxNodes to be a number, got %T", v)
|
|
}
|
|
if int(n) != 2000 {
|
|
t.Errorf("expected default liveMapMaxNodes=2000, got %d", int(n))
|
|
}
|
|
}
|
|
|
|
// Server-side clamp: operator misconfig (negative, zero, absurdly large)
|
|
// must be coerced to safe bounds [100, 20000]. Default (unset) is 2000.
|
|
func TestLiveMapMaxNodesClamp(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
set int
|
|
want int
|
|
}{
|
|
{"default-when-unset", 0, 2000},
|
|
{"negative-clamps-to-default", -42, 2000},
|
|
{"below-min-clamps-up", 50, 100},
|
|
{"in-range-passthrough", 4300, 4300},
|
|
{"above-max-clamps-down", 99999, 20000},
|
|
{"exact-min", 100, 100},
|
|
{"exact-max", 20000, 20000},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cfg := &Config{}
|
|
cfg.LiveMap.MaxNodes = tc.set
|
|
got := cfg.LiveMapMaxNodes()
|
|
if got != tc.want {
|
|
t.Errorf("LiveMapMaxNodes() with set=%d: want %d, got %d",
|
|
tc.set, tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|