mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-23 19:55:12 +00:00
51f823bf7e
## Summary - Adds `POST /api/admin/prune-geo-filter` endpoint — dry-run by default, `?confirm=true` to permanently delete nodes outside the current geofilter polygon + buffer. Requires `X-API-Key` header. - Adds **Prune nodes** section inside the GeoFilter customizer tab (write-access only, same `writeEnabled` gate as PUT). **Preview** lists affected nodes; **Confirm delete** removes them. - Adds `GetNodesForGeoPrune` and `DeleteNodesByPubkeys` DB helpers. - Updates `docs/user-guide/geofilter.md` — documents the UI button as primary workflow, CLI script as alternative. > **Depends on M3** (`feat/geofilter-m3-customizer`, PR #736). Merge M3 first. ## Test plan - [x] `cd cmd/server && go test ./...` — all pass - [x] Customizer GeoFilter tab without `apiKey` — Prune section not visible - [x] With `apiKey` + polygon active — Prune section visible - [x] **Preview** returns list of nodes outside polygon (no deletions) - [x] **Confirm delete** removes nodes, list clears - [x] `POST /api/admin/prune-geo-filter` without `X-API-Key` → 401 - [x] `POST /api/admin/prune-geo-filter` with no polygon configured → 400 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
69 lines
1.8 KiB
Go
69 lines
1.8 KiB
Go
package prunequeue
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
dbPath := filepath.Join(dir, "x.db")
|
|
|
|
id := NewID()
|
|
req := Request{ID: id, RequestedAt: time.Now().UTC(), Reason: "test", Pubkeys: []string{"aa", "bb"}}
|
|
if err := WriteRequest(dbPath, req); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pending, err := RequestExists(dbPath, id)
|
|
if err != nil || !pending {
|
|
t.Fatalf("RequestExists: pending=%v err=%v", pending, err)
|
|
}
|
|
|
|
list, err := ListPending(dbPath)
|
|
if err != nil || len(list) != 1 {
|
|
t.Fatalf("ListPending: %v / %v", list, err)
|
|
}
|
|
parsed, err := ReadRequest(list[0])
|
|
if err != nil || parsed.ID != id || len(parsed.Pubkeys) != 2 {
|
|
t.Fatalf("ReadRequest: %+v / %v", parsed, err)
|
|
}
|
|
|
|
// Writing the result removes the request marker.
|
|
if err := WriteResult(dbPath, Result{ID: id, RequestedAt: req.RequestedAt, CompletedAt: time.Now().UTC(), Deleted: 2}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
pending, _ = RequestExists(dbPath, id)
|
|
if pending {
|
|
t.Error("request marker should be gone after WriteResult")
|
|
}
|
|
res, err := ReadResult(dbPath, id)
|
|
if err != nil || res == nil || res.Deleted != 2 {
|
|
t.Fatalf("ReadResult: %+v / %v", res, err)
|
|
}
|
|
}
|
|
|
|
func TestRejectsBadIDs(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "x.db")
|
|
for _, bad := range []string{"", "../escape", "abcg", "0123456789ABCDEFG/extra", "../../etc/passwd"} {
|
|
if _, err := RequestPath(dbPath, bad); err == nil {
|
|
t.Errorf("RequestPath should reject %q", bad)
|
|
}
|
|
if _, err := ResultPath(dbPath, bad); err == nil {
|
|
t.Errorf("ResultPath should reject %q", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReadResultMissing(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "x.db")
|
|
res, err := ReadResult(dbPath, "abcdef0123456789")
|
|
if err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if res != nil {
|
|
t.Errorf("expected nil result, got %+v", res)
|
|
}
|
|
}
|