package database import ( "net/netip" "reflect" "testing" "time" "git.eeqj.de/sneak/routewatch/internal/routetable" ) // TestOriginChangesWriteThenRead writes three origin changes and reads them // back newest first, honouring the limit. func TestOriginChangesWriteThenRead(t *testing.T) { t.Parallel() db := newRoutesTestDatabase(t) prefix := netip.MustParsePrefix(tablePrefixV4) changes := []routetable.OriginChange{ {Prefix: prefix, OriginASN: asA, Announced: true, PeerCount: 1, Time: tableTime(10)}, {Prefix: prefix, OriginASN: asA, Announced: false, PeerCount: 0, Time: tableTime(20)}, {Prefix: prefix, OriginASN: asB, Announced: true, PeerCount: 1, Time: tableTime(30)}, } err := db.WriteOriginChanges(changes) if err != nil { t.Fatalf("WriteOriginChanges: %v", err) } got, err := db.GetOriginChanges(t.Context(), prefix, 2) if err != nil { t.Fatalf("GetOriginChanges: %v", err) } want := []routetable.OriginChange{ {Prefix: prefix, OriginASN: asB, Announced: true, PeerCount: 1, Time: tableTime(30)}, {Prefix: prefix, OriginASN: asA, Announced: false, PeerCount: 0, Time: tableTime(20)}, } if !reflect.DeepEqual(got, want) { t.Errorf("GetOriginChanges = %+v, want %+v", got, want) } } // TestOriginChangesPrune checks that the prune keeps every row newer than 72 // hours, keeps the newest 500 rows of each prefix, deletes the rest, and // leaves other prefixes alone. func TestOriginChangesPrune(t *testing.T) { t.Parallel() db := newRoutesTestDatabase(t) oldPrefix := netip.MustParsePrefix(tablePrefixV4) newPrefix := netip.MustParsePrefix(tablePrefixShortV4) other := netip.MustParsePrefix("10.20.0.0/16") now := time.Now() changes := make([]routetable.OriginChange, 0, 1201) // 600 rows for oldPrefix, all older than 72 hours, oldest first. The // prune keeps the newest 500 and deletes the oldest 100. for i := range 600 { changes = append(changes, routetable.OriginChange{ Prefix: oldPrefix, OriginASN: asA, Announced: true, PeerCount: 1, Time: now.Add(-100 * time.Hour).Add(time.Duration(i) * time.Minute), }) } // 600 rows for newPrefix, all newer than 72 hours, oldest first. The // prune keeps all of them. for i := range 600 { changes = append(changes, routetable.OriginChange{ Prefix: newPrefix, OriginASN: asA, Announced: true, PeerCount: 1, Time: now.Add(-71 * time.Hour).Add(time.Duration(i) * time.Minute), }) } // One row for other, which the prune leaves alone. changes = append(changes, routetable.OriginChange{ Prefix: other, OriginASN: asA, Announced: true, PeerCount: 1, Time: now.Add(-100 * time.Hour), }) err := db.WriteOriginChanges(changes) if err != nil { t.Fatalf("WriteOriginChanges: %v", err) } err = db.PruneOriginChanges(t.Context()) if err != nil { t.Fatalf("PruneOriginChanges: %v", err) } gotOld, err := db.GetOriginChanges(t.Context(), oldPrefix, 1000) if err != nil { t.Fatalf("GetOriginChanges: %v", err) } if len(gotOld) != 500 { t.Errorf("GetOriginChanges(oldPrefix) = %d rows, want 500", len(gotOld)) } gotNew, err := db.GetOriginChanges(t.Context(), newPrefix, 1000) if err != nil { t.Fatalf("GetOriginChanges: %v", err) } if len(gotNew) != 600 { t.Errorf("GetOriginChanges(newPrefix) = %d rows, want 600", len(gotNew)) } gotOther, err := db.GetOriginChanges(t.Context(), other, 10) if err != nil { t.Fatalf("GetOriginChanges: %v", err) } if len(gotOther) != 1 { t.Errorf("GetOriginChanges(other) = %d rows, want 1", len(gotOther)) } } // TestOriginChangesSurviveRouteDeletion checks that deleting a routes row // leaves the prefix's origin_changes rows. func TestOriginChangesSurviveRouteDeletion(t *testing.T) { t.Parallel() db := newRoutesTestDatabase(t) prefix := netip.MustParsePrefix(tablePrefixV4) err := db.WriteOriginChanges([]routetable.OriginChange{{ Prefix: prefix, OriginASN: asA, Announced: true, PeerCount: 1, Time: tableTime(10), }}) if err != nil { t.Fatalf("WriteOriginChanges: %v", err) } // Write a route, then delete it. table := routetable.New(routetable.Params{}) announceTo(table, tablePrefixV4, asA, peerA, 10) writeTableChanges(t, db, table) table.Withdraw(prefix, peerA) writeTableChanges(t, db, table) got, err := db.GetOriginChanges(t.Context(), prefix, 10) if err != nil { t.Fatalf("GetOriginChanges: %v", err) } if len(got) != 1 { t.Errorf("GetOriginChanges = %d rows, want 1", len(got)) } }