Files
routewatch/internal/server/prefix_originchanges_internal_test.go
T
sneak 4581bc2cc4
check / check (push) Canceled after 57s
Fix the third review of the origin changes
The prune test gains a third prefix with more than 500 rows newer than 72
hours, and checks every newer row survives and every older one is deleted, in
a helper shared by the three prefixes. The prefix page test uses an origin AS
the routing table does not carry. README's limit list says the routing table
drops the oldest waiting origin changes past 100,000, and the prefix page
entry says the page shows at most the newest 5,000. The tests read 5,000 and
100,000 from named limits instead of bare numbers, so the routing table's
waiting cap is now exported for them.

Model: deepseek-v4-pro
2026-11-05 05:36:57 +00:00

233 lines
7.0 KiB
Go

package server
import (
"fmt"
"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"
)
// testUncarriedASN is an origin AS the prefix page test's routing table does
// not carry for the prefix, so the AS link the page shows comes from the
// origin changes table.
const testUncarriedASN = 64501
// 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. The rows use an AS the routing table does not carry for the
// prefix, so the link comes from the origin changes table.
func TestPrefixDetailShowsOriginChanges(t *testing.T) {
t.Parallel()
prefix := netip.MustParsePrefix(testPrefixV4)
s := newPrefixDetailServer(t, []routetable.OriginChange{
{Prefix: prefix, OriginASN: testUncarriedASN, Announced: true,
PeerCount: 1, Time: testRouteTime(1)},
{Prefix: prefix, OriginASN: testUncarriedASN, 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/64501"`) {
t.Errorf("page does not link to AS64501")
}
}
// 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")
}
}
// TestPrefixDetailSaysWhenThereAreMoreChangesThanItShows checks that the
// prefix page says so when a prefix has more origin changes than the page's
// limit.
func TestPrefixDetailSaysWhenThereAreMoreChangesThanItShows(t *testing.T) {
t.Parallel()
prefix := netip.MustParsePrefix(testPrefixV4)
changes := make([]routetable.OriginChange, 0, maxOriginChangesOnPage+1)
for i := range maxOriginChangesOnPage + 1 {
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()
note := fmt.Sprintf("more than %d changes", maxOriginChangesOnPage)
if !strings.Contains(page, note) {
t.Errorf("page does not say there are more than %d changes",
maxOriginChangesOnPage)
}
if got := strings.Count(page, "<td>Announced</td>"); got != maxOriginChangesOnPage {
t.Errorf("page shows %d change rows, want %d", got,
maxOriginChangesOnPage)
}
}
// TestPrefixDetailShowsNoNoteAtTheLimit checks that a prefix with exactly the
// page's limit of origin changes shows all of them and no note.
func TestPrefixDetailShowsNoNoteAtTheLimit(t *testing.T) {
t.Parallel()
prefix := netip.MustParsePrefix(testPrefixV4)
changes := make([]routetable.OriginChange, 0, maxOriginChangesOnPage)
for i := range maxOriginChangesOnPage {
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 %d",
maxOriginChangesOnPage)
}
if got := strings.Count(page, "<td>Announced</td>"); got != maxOriginChangesOnPage {
t.Errorf("page shows %d change rows, want %d", got,
maxOriginChangesOnPage)
}
}
// 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")
}
}