check / check (push) Failing after 15m27s
Stop now counts the origin changes inside a running write, in a new writingOriginChanges field, and adds them to the count it logs. The prune test checks which rows survive, including a prefix with rows on both sides of 72 hours. The prefix page tests cover 5,000 and 5,001 rows and a row's time and peer count. New tests cover the dropped-rows warning and the dropped count's reset. The prune floor is named minOriginChangesPerPrefix. The README, TODO, package doc and doc comments are corrected to match. Model: deepseek-v4-pro
220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/config"
|
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
|
"git.eeqj.de/sneak/routewatch/internal/logger"
|
|
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
|
"git.eeqj.de/sneak/routewatch/internal/ristypes"
|
|
"git.eeqj.de/sneak/routewatch/internal/routetable"
|
|
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
|
)
|
|
|
|
// newPrefixDetailServer returns a server whose routing table holds testPrefixV4
|
|
// with origin AS 64500, and whose database holds the given origin changes.
|
|
func newPrefixDetailServer(
|
|
t *testing.T, changes []routetable.OriginChange,
|
|
) *Server {
|
|
t.Helper()
|
|
|
|
db, err := database.New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
|
if err != nil {
|
|
t.Fatalf("database.New: %v", err)
|
|
}
|
|
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
|
|
table := emptyTable()
|
|
announce(table, testPrefixV4, 64500, testPeer(1), testRouteTime(0))
|
|
|
|
err = db.WriteOriginChanges(changes)
|
|
if err != nil {
|
|
t.Fatalf("WriteOriginChanges: %v", err)
|
|
}
|
|
|
|
return New(db, table, streamer.New(logger.New(), metrics.New()),
|
|
logger.New(), &config.Config{})
|
|
}
|
|
|
|
// TestPrefixDetailShowsOriginChanges checks that the prefix page shows the
|
|
// origin changes newest first, with the announced and withdrawn rows and the
|
|
// AS link.
|
|
func TestPrefixDetailShowsOriginChanges(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prefix := netip.MustParsePrefix(testPrefixV4)
|
|
|
|
s := newPrefixDetailServer(t, []routetable.OriginChange{
|
|
{Prefix: prefix, OriginASN: 64500, Announced: true, PeerCount: 1,
|
|
Time: testRouteTime(1)},
|
|
{Prefix: prefix, OriginASN: 64500, Announced: false, PeerCount: 0,
|
|
Time: testRouteTime(2)},
|
|
})
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/prefix/"+testPrefixV4, nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
|
|
page := rec.Body.String()
|
|
|
|
withdrawn := strings.Index(page, "Withdrawn")
|
|
announced := strings.Index(page, "Announced")
|
|
|
|
if withdrawn < 0 || announced < 0 {
|
|
t.Fatalf("page does not show the announced and withdrawn rows")
|
|
}
|
|
|
|
// The withdrawn row is newer, so it comes first.
|
|
if withdrawn > announced {
|
|
t.Errorf("the withdrawn row is not shown before the announced row")
|
|
}
|
|
|
|
if !strings.Contains(page, `href="/as/64500"`) {
|
|
t.Errorf("page does not link to AS64500")
|
|
}
|
|
}
|
|
|
|
// TestPrefixDetailShowsOriginZeroAsNoKnownOrigin checks that the origin
|
|
// changes table shows AS 0 as no known origin, without a link to AS0.
|
|
func TestPrefixDetailShowsOriginZeroAsNoKnownOrigin(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prefix := netip.MustParsePrefix(testPrefixV4)
|
|
|
|
s := newPrefixDetailServer(t, []routetable.OriginChange{
|
|
{Prefix: prefix, OriginASN: ristypes.NoOrigin, Announced: true,
|
|
PeerCount: 1, Time: testRouteTime(1)},
|
|
})
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/prefix/"+testPrefixV4, nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
|
|
page := rec.Body.String()
|
|
|
|
if !strings.Contains(page, "No known origin") {
|
|
t.Errorf("page does not say the origin change has no known origin")
|
|
}
|
|
|
|
if strings.Contains(page, `href="/as/0"`) {
|
|
t.Errorf("page links to AS0")
|
|
}
|
|
}
|
|
|
|
// TestPrefixDetailSaysWhenMoreThan5000Changes checks that the prefix page says
|
|
// so when a prefix has more than 5,000 origin changes.
|
|
func TestPrefixDetailSaysWhenMoreThan5000Changes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prefix := netip.MustParsePrefix(testPrefixV4)
|
|
|
|
changes := make([]routetable.OriginChange, 0, 5001)
|
|
for i := range 5001 {
|
|
changes = append(changes, routetable.OriginChange{
|
|
Prefix: prefix, OriginASN: 64500, Announced: true, PeerCount: 1,
|
|
Time: testRouteTime(i),
|
|
})
|
|
}
|
|
|
|
s := newPrefixDetailServer(t, changes)
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/prefix/"+testPrefixV4, nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
|
|
page := rec.Body.String()
|
|
|
|
if !strings.Contains(page, "more than 5000 changes") {
|
|
t.Errorf("page does not say there are more than 5000 changes")
|
|
}
|
|
|
|
if got := strings.Count(page, "<td>Announced</td>"); got != 5000 {
|
|
t.Errorf("page shows %d change rows, want 5000", got)
|
|
}
|
|
}
|
|
|
|
// TestPrefixDetailShowsNoNoteAtExactly5000Changes checks that a prefix with
|
|
// exactly 5,000 origin changes shows all 5,000 and no note.
|
|
func TestPrefixDetailShowsNoNoteAtExactly5000Changes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prefix := netip.MustParsePrefix(testPrefixV4)
|
|
|
|
changes := make([]routetable.OriginChange, 0, 5000)
|
|
for i := range 5000 {
|
|
changes = append(changes, routetable.OriginChange{
|
|
Prefix: prefix, OriginASN: 64500, Announced: true, PeerCount: 1,
|
|
Time: testRouteTime(i),
|
|
})
|
|
}
|
|
|
|
s := newPrefixDetailServer(t, changes)
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/prefix/"+testPrefixV4, nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
|
|
page := rec.Body.String()
|
|
|
|
if strings.Contains(page, "more than") {
|
|
t.Errorf("page says there are more changes, but a prefix has 5000")
|
|
}
|
|
|
|
if got := strings.Count(page, "<td>Announced</td>"); got != 5000 {
|
|
t.Errorf("page shows %d change rows, want 5000", got)
|
|
}
|
|
}
|
|
|
|
// TestPrefixDetailShowsOriginChangeTimeAndPeerCount checks that a row shows
|
|
// its time and its peer count.
|
|
func TestPrefixDetailShowsOriginChangeTimeAndPeerCount(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prefix := netip.MustParsePrefix(testPrefixV4)
|
|
|
|
s := newPrefixDetailServer(t, []routetable.OriginChange{
|
|
{Prefix: prefix, OriginASN: 64500, Announced: true, PeerCount: 7,
|
|
Time: testRouteTime(5)},
|
|
})
|
|
|
|
rec := httptest.NewRecorder()
|
|
s.router.ServeHTTP(rec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, "/prefix/"+testPrefixV4, nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
|
}
|
|
|
|
page := rec.Body.String()
|
|
|
|
if !strings.Contains(page, "2026-01-02 03:09:05") {
|
|
t.Errorf("page does not show the origin change's time")
|
|
}
|
|
|
|
if !strings.Contains(page, "<td>7</td>") {
|
|
t.Errorf("page does not show the origin change's peer count")
|
|
}
|
|
}
|