mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 07:19:58 +00:00
Imported boundaries override borderFile on the border map and for foreign marking, falling back to the manual border when MeshMapper has none.
125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package api_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/borders"
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/cache"
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/google/uuid"
|
|
"github.com/paulmach/orb"
|
|
)
|
|
|
|
type borderReader struct {
|
|
api.Reader
|
|
node *api.Node
|
|
page api.Page[api.NodeSummary]
|
|
getCalls, listCalls int
|
|
err error
|
|
}
|
|
|
|
func (r *borderReader) GetNode(context.Context, uuid.UUID) (*api.Node, error) {
|
|
r.getCalls++
|
|
return r.node, r.err
|
|
}
|
|
func (r *borderReader) ListNodes(_ context.Context, _ int16, _ []string, _, _ *bool, _ []byte, _, _, _ string, _ int64, _ int32, _ bool) (api.Page[api.NodeSummary], error) {
|
|
r.listCalls++
|
|
return r.page, r.err
|
|
}
|
|
|
|
func TestLocalBordersAfterCache(t *testing.T) {
|
|
ctx := context.Background()
|
|
local, err := borders.New([]orb.Polygon{{{{10, 10}, {20, 10}, {20, 20}, {10, 20}, {10, 10}}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other, err := borders.New([]orb.Polygon{{{{30, 30}, {40, 30}, {40, 40}, {30, 40}, {30, 30}}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
node := &api.Node{NodeSummary: api.NodeSummary{ID: uuid.New(), NodeType: 2, Latitude: new(15.0), Longitude: new(15.0), IATAs: []api.NodeIATA{{IATA: "ZZZ", LastHeard: 1}}}}
|
|
base := &borderReader{node: node, page: api.Page[api.NodeSummary]{Items: []api.NodeSummary{node.NodeSummary}, HasMore: true, NextCursor: new(int64(123))}}
|
|
mr := miniredis.RunT(t)
|
|
client := cache.NewClient(mr.Addr(), "", 0)
|
|
t.Cleanup(func() { client.Close() })
|
|
cached := cache.NewCachedReader(base, client, cache.CacheTTLs{Nodes: time.Hour})
|
|
first := api.WithLocalBorders(cached, borders.NewLive(local))
|
|
second := api.WithLocalBorders(cached, borders.NewLive(other))
|
|
if api.WithLocalBorders(cached, nil) != cached {
|
|
t.Fatal("disabled decorator changed reader")
|
|
}
|
|
for _, tc := range []struct {
|
|
reader api.Reader
|
|
want bool
|
|
}{{first, false}, {second, true}} {
|
|
got, err := tc.reader.GetNode(ctx, node.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.PossiblyForeign == nil || *got.PossiblyForeign != tc.want || got.IATAs[0].IATA != "ZZZ" {
|
|
t.Fatalf("detail: %+v", got)
|
|
}
|
|
page, err := tc.reader.ListNodes(ctx, 2, []string{"ZZZ"}, nil, nil, nil, "", "", "", 0, 50, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !page.HasMore || *page.NextCursor != 123 || page.Items[0].PossiblyForeign == nil || *page.Items[0].PossiblyForeign != tc.want {
|
|
t.Fatalf("page: %+v", page)
|
|
}
|
|
data, _ := json.Marshal(got)
|
|
if !strings.Contains(string(data), `"possiblyForeign":`) {
|
|
t.Fatal("known false was omitted")
|
|
}
|
|
}
|
|
if base.getCalls != 1 || base.listCalls != 2 {
|
|
t.Fatalf("extra reads: get=%d list=%d", base.getCalls, base.listCalls)
|
|
}
|
|
if node.PossiblyForeign != nil || base.page.Items[0].PossiblyForeign != nil {
|
|
t.Fatal("shared reader data was mutated")
|
|
}
|
|
disabled, err := cached.GetNode(ctx, node.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, _ := json.Marshal(disabled)
|
|
if strings.Contains(string(data), "possiblyForeign") {
|
|
t.Fatal("classification leaked into cached data")
|
|
}
|
|
// Existing ingest invalidation still supplies fresh coordinates to the wrapper.
|
|
base.node.Longitude = new(35.0)
|
|
cached.(*cache.CachedReader).InvalidateNode(ctx, node.ID)
|
|
updated, err := first.GetNode(ctx, node.ID)
|
|
if err != nil || updated.PossiblyForeign == nil || !*updated.PossiblyForeign || base.getCalls != 2 {
|
|
t.Fatal("updated coordinates not classified")
|
|
}
|
|
}
|
|
|
|
func TestLocalBordersPreserveReaderErrors(t *testing.T) {
|
|
local, err := borders.New([]orb.Polygon{{{{10, 10}, {20, 10}, {20, 20}, {10, 20}, {10, 10}}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := errors.New("reader unavailable")
|
|
base := &borderReader{err: want}
|
|
reader := api.WithLocalBorders(base, borders.NewLive(local))
|
|
if _, err := reader.GetNode(context.Background(), uuid.New()); !errors.Is(err, want) {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := reader.ListNodes(context.Background(), 0, nil, nil, nil, nil, "", "", "", 0, 1, false); !errors.Is(err, want) {
|
|
t.Fatal(err)
|
|
}
|
|
base.err = nil
|
|
if node, err := reader.GetNode(context.Background(), uuid.New()); node != nil || err != nil {
|
|
t.Fatal("nil result changed")
|
|
}
|
|
}
|