mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 13:49:56 +00:00
Warn at startup when limits are on without trusted_proxies, and once when a request carries forwarding headers Beacon ignores. Reject short or whitespace admin keys and negative durations. Key default-window stats by hour, return 500 for scope lookup failures, and validate IATA path params.
90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
)
|
|
|
|
type scopeStatsReader struct {
|
|
api.Reader
|
|
calls int64
|
|
}
|
|
|
|
func (r *scopeStatsReader) GetScopeStats(context.Context, []string, time.Time) ([]api.ScopeStats, error) {
|
|
r.calls++
|
|
return []api.ScopeStats{{Name: "#test", PacketCount: r.calls}}, nil
|
|
}
|
|
|
|
func (r *scopeStatsReader) AnalyticsRevision(context.Context) (int64, error) { return 0, nil }
|
|
|
|
func TestScopeStatsCacheSeparatesIATAs(t *testing.T) {
|
|
c, _ := newTestClient(t)
|
|
inner := &scopeStatsReader{}
|
|
reader := NewCachedReader(inner, c, CacheTTLs{Reference: time.Minute})
|
|
for _, tc := range []struct {
|
|
iatas []string
|
|
want int64
|
|
}{
|
|
{nil, 1},
|
|
{[]string{"YVR"}, 2},
|
|
{[]string{"YYJ"}, 3},
|
|
{[]string{"YYJ", "YVR"}, 4},
|
|
{[]string{"YVR", "YYJ", "YVR"}, 4},
|
|
{[]string{"YVR"}, 2},
|
|
{[]string{}, 1},
|
|
{[]string{"ZZZ"}, 5},
|
|
{[]string{""}, 6},
|
|
{nil, 1},
|
|
} {
|
|
before := slices.Clone(tc.iatas)
|
|
rows, err := reader.GetScopeStats(context.Background(), tc.iatas, time.Time{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(rows) != 1 || rows[0].PacketCount != tc.want {
|
|
t.Fatalf("IATAs %v: got %+v, want cached count %d", tc.iatas, rows, tc.want)
|
|
}
|
|
if !slices.Equal(before, tc.iatas) {
|
|
t.Fatalf("caller IATAs mutated: %v -> %v", before, tc.iatas)
|
|
}
|
|
}
|
|
if inner.calls != 6 {
|
|
t.Fatalf("underlying calls = %d, want 6", inner.calls)
|
|
}
|
|
}
|
|
|
|
func TestDefaultWindowKeyFollowsTheHour(t *testing.T) {
|
|
c, _ := newTestClient(t)
|
|
inner := &scopeStatsReader{}
|
|
cr := NewCachedReader(inner, c, CacheTTLs{Stats: time.Hour}).(*CachedReader)
|
|
clock := time.Date(2026, 10, 2, 10, 30, 0, 0, time.UTC)
|
|
cr.now = func() time.Time { return clock }
|
|
get := func(since time.Time) int64 {
|
|
t.Helper()
|
|
rows, err := cr.GetScopeStats(context.Background(), nil, since)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return rows[0].PacketCount
|
|
}
|
|
if get(time.Time{}) != 1 || get(time.Time{}) != 1 {
|
|
t.Fatal("default window not cached within the hour")
|
|
}
|
|
clock = clock.Add(35 * time.Minute)
|
|
if get(time.Time{}) != 2 {
|
|
t.Fatal("default window served from the previous hour")
|
|
}
|
|
// An explicit since at the current hour is a different window.
|
|
if get(clock.Truncate(time.Hour)) != 3 {
|
|
t.Fatal("explicit since collided with the default window")
|
|
}
|
|
|
|
}
|