quality / commitlint (push) Skipped
quality / changes (push) Successful in 9s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 46s
quality / web (push) Successful in 1m16s
quality / go (push) Successful in 2m42s
quality / bird2 (push) Successful in 16s
CD / quality (push) Successful in 5m19s
CD / publish (push) Successful in 7m19s
- Deleted unused components: `DashboardActivityTimeline`, `DashboardFramePanel`, `DashboardModulesGrid`, `DashboardRecentJobsGrid`, and `DashboardRecentRevisionsGrid` to streamline the dashboard. - Updated `DashboardKpiGrid` to improve KPI display logic, including progress indicators and enhanced badge functionality. - Refactored `DashboardNetworkHealth` to provide better status representation based on loading states and network conditions. - Introduced new properties for KPI cards to support progress tracking and improved visual feedback. This cleanup aims to enhance performance and maintainability of the dashboard while providing a better user experience.
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func TestResolveASNForEntry_UsesTTLCache(t *testing.T) {
|
|
m := store.NewMemory()
|
|
t.Setenv("EVOBGP_ASN_CACHE_TTL_SEC", "3600")
|
|
|
|
var calls atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls.Add(1)
|
|
if strings.Contains(r.URL.Path, "/announced") {
|
|
_, _ = w.Write([]byte(`{"status":"ok","data":{"prefixes":[{"prefix":"203.0.113.0/24"}]}}`))
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`{"status":"ok","data":{"holder":"Test AS"}}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("EVOBGP_RIPESTAT_ANNOUNCED_PREFIXES_URL", srv.URL+"/announced")
|
|
t.Setenv("EVOBGP_RIPESTAT_AS_OVERVIEW_URL", srv.URL+"/overview")
|
|
|
|
ctx := context.Background()
|
|
hc := srv.Client()
|
|
p1, h1, err := resolveASNForEntry(ctx, m, hc, 64512)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(p1) != 1 || h1 != "Test AS" {
|
|
t.Fatalf("unexpected first resolve: %+v holder=%q", p1, h1)
|
|
}
|
|
if calls.Load() < 2 {
|
|
t.Fatalf("expected ripestat calls on first resolve, got %d", calls.Load())
|
|
}
|
|
firstCalls := calls.Load()
|
|
_, _, err = resolveASNForEntry(ctx, m, hc, 64512)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls.Load() != firstCalls {
|
|
t.Fatalf("expected cache hit (no new HTTP), calls went from %d to %d", firstCalls, calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestResolveASNForEntry_HolderFailureIsNonFatal(t *testing.T) {
|
|
m := store.NewMemory()
|
|
t.Setenv("EVOBGP_ASN_CACHE_TTL_SEC", "1")
|
|
|
|
var holderCalls atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/announced") {
|
|
_, _ = w.Write([]byte(`{"status":"ok","data":{"prefixes":[{"prefix":"203.0.113.0/24"}]}}`))
|
|
return
|
|
}
|
|
holderCalls.Add(1)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("EVOBGP_RIPESTAT_ANNOUNCED_PREFIXES_URL", srv.URL+"/announced")
|
|
t.Setenv("EVOBGP_RIPESTAT_AS_OVERVIEW_URL", srv.URL+"/overview")
|
|
|
|
ctx := context.Background()
|
|
hc := srv.Client()
|
|
pfxs, holder, err := resolveASNForEntry(ctx, m, hc, 64512)
|
|
if err != nil {
|
|
t.Fatalf("holder failure must not fail ingest: %v", err)
|
|
}
|
|
if len(pfxs) != 1 {
|
|
t.Fatalf("expected 1 prefix, got %+v", pfxs)
|
|
}
|
|
if holder != "" {
|
|
t.Fatalf("expected empty holder on upstream failure, got %q", holder)
|
|
}
|
|
if holderCalls.Load() == 0 {
|
|
t.Fatal("expected holder endpoint to be attempted")
|
|
}
|
|
}
|
|
|
|
func TestResolveASNForEntry_KeepsPreviousHolderOnFailure(t *testing.T) {
|
|
m := store.NewMemory()
|
|
t.Setenv("EVOBGP_ASN_CACHE_TTL_SEC", "1")
|
|
t.Setenv("EVOBGP_ASN_HOLDER_TTL_SEC", "3600")
|
|
|
|
holderOK := atomic.Bool{}
|
|
holderOK.Store(true)
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/announced") {
|
|
_, _ = w.Write([]byte(`{"status":"ok","data":{"prefixes":[{"prefix":"203.0.113.0/24"}]}}`))
|
|
return
|
|
}
|
|
if holderOK.Load() {
|
|
_, _ = w.Write([]byte(`{"status":"ok","data":{"holder":"Good AS"}}`))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("EVOBGP_RIPESTAT_ANNOUNCED_PREFIXES_URL", srv.URL+"/announced")
|
|
t.Setenv("EVOBGP_RIPESTAT_AS_OVERVIEW_URL", srv.URL+"/overview")
|
|
|
|
ctx := context.Background()
|
|
hc := srv.Client()
|
|
if _, h, err := resolveASNForEntry(ctx, m, hc, 64512); err != nil || h != "Good AS" {
|
|
t.Fatalf("first resolve: holder=%q err=%v", h, err)
|
|
}
|
|
|
|
// Prefix cache expired; holder endpoint now broken — previous holder must survive.
|
|
holderOK.Store(false)
|
|
time.Sleep(1100 * time.Millisecond)
|
|
pfxs, h, err := resolveASNForEntry(ctx, m, hc, 64512)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(pfxs) != 1 {
|
|
t.Fatalf("expected 1 prefix, got %+v", pfxs)
|
|
}
|
|
if h != "Good AS" {
|
|
t.Fatalf("expected previous holder preserved, got %q", h)
|
|
}
|
|
}
|
|
|
|
func TestModuleDueForScheduler_BucketRollover(t *testing.T) {
|
|
mod := &store.Module{ID: "mod-jitter", Enabled: true, Type: "CDN_CIDRS", RefreshIntervalSec: 300}
|
|
win := int64(300)
|
|
off := moduleSchedulerOffset(mod.ID, win)
|
|
boundary := time.Unix(win+off, 0)
|
|
if !ModuleDueForScheduler(mod, boundary) {
|
|
t.Fatal("expected due when refresh bucket rolls")
|
|
}
|
|
mid := time.Unix(win+off+SchedulerTickSec, 0)
|
|
if ModuleDueForScheduler(mod, mid) {
|
|
t.Fatal("expected not due within same bucket")
|
|
}
|
|
}
|