mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-12 13:24:42 +00:00
136e1d23c8
## Summary **Partial fix for #730 (M1 only — M2 frontend and M3 alerting deferred).** Today the ingestor **silently drops** ADVERTs whose GPS lies outside the configured `geo_filter` polygon. That's the wrong default for an analytics tool — operators get zero visibility into bridged or leaked meshes. This PR makes the new default **flag, don't drop**: foreign adverts are stored, the node row is tagged `foreign_advert=1`, and the API surfaces `"foreign": true` so dashboards / map overlays can be built on top. ## Behavior | Mode | What happens to an ADVERT outside `geo_filter` | |---|---| | (default) flag | Stored, marked `foreign_advert=1`, exposed via API | | drop (legacy) | Silently dropped (preserves old behavior for ops who want it) | ## What's done (M1 — Backend) - ingestor stores foreign adverts instead of dropping - `nodes.foreign_advert` column added (migration) - `/api/nodes` and `/api/nodes/{pk}` expose `foreign: true` field - Config: `geofilter.action: "flag"|"drop"` (default `flag`) - Tests + config docs ## What's NOT done (deferred to M2 + M3) - **M2 — Frontend:** Map overlay showing foreign adverts as distinct markers, foreign-advert filter on packets/nodes pages, dedicated foreign-advert dashboard - **M3 — Alerting:** Time-series detection of bridging events, alert when foreign advert rate spikes, identify bridge entry-point nodes Issue #730 remains open for M2 and M3. --------- Co-authored-by: corescope-bot <bot@corescope>
57 lines
1.6 KiB
Go
57 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// TestHandleNodes_ExposesForeignAdvertField asserts the /api/nodes response
|
|
// surfaces the foreign_advert column as a boolean `foreign` field on each
|
|
// node, so operators can see bridged/leaked nodes (#730).
|
|
func TestHandleNodes_ExposesForeignAdvertField(t *testing.T) {
|
|
srv, router := setupTestServer(t)
|
|
conn := srv.db.conn
|
|
|
|
if _, err := conn.Exec(`INSERT INTO nodes
|
|
(public_key, name, role, lat, lon, last_seen, first_seen, advert_count, foreign_advert)
|
|
VALUES
|
|
('PK_LOCAL', 'local-node', 'companion', 37.0, -122.0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z', 1, 0),
|
|
('PK_FOREIGN', 'foreign-node', 'companion', 50.0, 10.0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z', 1, 1)`,
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/api/nodes?limit=100", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
Nodes []map[string]interface{} `json:"nodes"`
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := map[string]bool{}
|
|
for _, n := range resp.Nodes {
|
|
pk, _ := n["public_key"].(string)
|
|
f, ok := n["foreign"].(bool)
|
|
if !ok {
|
|
t.Errorf("node %s: missing/non-bool 'foreign' field, got %T %v", pk, n["foreign"], n["foreign"])
|
|
continue
|
|
}
|
|
got[pk] = f
|
|
}
|
|
if !got["PK_LOCAL"] == false || got["PK_LOCAL"] != false {
|
|
t.Errorf("PK_LOCAL foreign=%v, want false", got["PK_LOCAL"])
|
|
}
|
|
if got["PK_FOREIGN"] != true {
|
|
t.Errorf("PK_FOREIGN foreign=%v, want true", got["PK_FOREIGN"])
|
|
}
|
|
}
|