package store import ( "context" "errors" "testing" "time" "github.com/google/uuid" ) func TestSameSnapshotContentIgnoresOrder(t *testing.T) { comm := "c1" a := &ModulePrefixSnapshot{InputHash: "h", Prefixes: []PrefixRow{ {Prefix: "203.0.113.0/24", CommunityID: &comm, Source: "cdn:a"}, {Prefix: "198.51.100.0/24", Source: "cdn:b"}, }} b := &ModulePrefixSnapshot{InputHash: "h", Prefixes: []PrefixRow{ {Prefix: "198.51.100.0/24", Source: "cdn:b"}, {Prefix: "203.0.113.0/24", CommunityID: &comm, Source: "cdn:a"}, }} if !SameSnapshotContent(a, b) { t.Fatal("same set in another order was treated as new") } b.Prefixes[0].Prefix = "192.0.2.0/24" if SameSnapshotContent(a, b) { t.Fatal("different prefix compared equal") } } func TestPrefixSnapshotHashIgnoresOrder(t *testing.T) { rows := []PrefixRow{ {Prefix: "203.0.113.0/24", Source: "ip_range"}, {Prefix: "198.51.100.0/24", Source: "cdn:a"}, } rev := []PrefixRow{rows[1], rows[0]} if PrefixSnapshotHash(rows) != PrefixSnapshotHash(rev) { t.Fatal("prefix hash depends on order") } rev = append(rev, PrefixRow{Prefix: "2001:db8::/32", Source: "ip_range"}) if PrefixSnapshotHash(rows) == PrefixSnapshotHash(rev) { t.Fatal("prefix change kept the same hash") } } func TestCreateRenderRevisionReusesPrefixSnapshot(t *testing.T) { m := NewMemory() m.SeedDemo() tenant, _, mod, _, _ := m.DemoIDs() ctx := context.Background() rows := []PrefixRow{ {Prefix: "203.0.113.0/24", Source: "ip_range"}, {Prefix: "198.51.100.0/24", Source: "cdn:a"}, } rev1 := uuid.NewString() if err := m.CreateRenderRevision(ctx, rev1, tenant, mod, nil, "hash-peer-a", map[string]string{"bird.conf": "a"}, rows); err != nil { t.Fatal(err) } rev2 := uuid.NewString() reversed := []PrefixRow{rows[1], rows[0]} if err := m.CreateRenderRevision(ctx, rev2, tenant, mod, nil, "hash-peer-b", map[string]string{"bird.conf": "b"}, reversed); err != nil { t.Fatal(err) } if m.revPrefixSnap[rev1] == "" || m.revPrefixSnap[rev1] != m.revPrefixSnap[rev2] { t.Fatalf("peer-only change copied prefix rows: %q vs %q", m.revPrefixSnap[rev1], m.revPrefixSnap[rev2]) } if len(m.prefixSnapRows) != 1 { t.Fatalf("stored prefix snapshots: %d", len(m.prefixSnapRows)) } page, _, _ := m.ListRevisionPrefixes(tenant, rev2, "", 10) if len(page) != 2 { t.Fatalf("reused snapshot not readable: %+v", page) } changed := append(append([]PrefixRow(nil), rows...), PrefixRow{Prefix: "2001:db8::/32", Source: "ip_range"}) rev3 := uuid.NewString() if err := m.CreateRenderRevision(ctx, rev3, tenant, mod, nil, "hash-prefix", nil, changed); err != nil { t.Fatal(err) } if m.revPrefixSnap[rev3] == m.revPrefixSnap[rev1] || len(m.prefixSnapRows) != 2 { t.Fatalf("prefix change did not allocate a snapshot: id=%s count=%d", m.revPrefixSnap[rev3], len(m.prefixSnapRows)) } } func TestUpdateModulePrefixSnapshotCancelledContext(t *testing.T) { m := NewMemory() m.SeedDemo() tenant, mod, _, _, _ := m.DemoIDs() ctx, cancel := context.WithCancel(context.Background()) cancel() err := m.UpdateModulePrefixSnapshot(ctx, tenant, mod, func(*ModulePrefixSnapshot) (*ModulePrefixSnapshot, error) { t.Fatal("snapshot update ran after cancel") return nil, nil }) if !errors.Is(err, context.Canceled) { t.Fatalf("err=%v", err) } } func TestMemorySnapshotLockUsesSnapshotTimeout(t *testing.T) { t.Setenv("EVOBGP_SNAPSHOT_TIMEOUT", "80ms") m := NewMemory() hold, err := m.lockModuleSnapshot(context.Background(), "tenant", "module") if err != nil { t.Fatal(err) } defer hold() start := time.Now() _, err = m.lockModuleSnapshot(context.Background(), "tenant", "module") if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("err=%v", err) } if time.Since(start) > time.Second { t.Fatalf("lock wait ignored snapshot timeout: %s", time.Since(start)) } } func TestSnapshotWriteBudgetKeepsCallerDeadline(t *testing.T) { parent, cancel := context.WithTimeout(context.Background(), time.Hour) defer cancel() got, stop, installed := SnapshotWriteBudget(parent) defer stop() if installed { t.Fatal("caller deadline was replaced") } dl, ok := got.Deadline() if !ok || time.Until(dl) < 30*time.Minute { t.Fatal("caller deadline was shortened") } } func TestSetASNPrefixCacheKeepsRowsWhenSetUnchanged(t *testing.T) { m := NewMemory() if err := m.SetASNPrefixCache(64500, "holder-a", []string{"203.0.113.0/24", "198.51.100.0/24"}); err != nil { t.Fatal(err) } before := m.asnPrefixCache[64500] ptr := &before.Prefixes[0] fetched := before.FetchedAt time.Sleep(5 * time.Millisecond) if err := m.SetASNPrefixCache(64500, "holder-b", []string{"198.51.100.0/24", "203.0.113.0/24"}); err != nil { t.Fatal(err) } after := m.asnPrefixCache[64500] if &after.Prefixes[0] != ptr { t.Fatal("unchanged ASN prefixes were rewritten") } if !after.FetchedAt.After(fetched) { t.Fatal("fetched_at was not updated") } if after.Holder != "holder-b" { t.Fatalf("holder=%q", after.Holder) } }