check / check (push) Successful in 4m6s
The routing table records each prefix and origin AS pair's origin changes: when it gains its first peer and when it loses its last. Each row holds the prefix, the origin AS, announced or withdrawn, the peer count and the time. The prefix handler writes them to a new origin_changes table, and the database maintainer prunes each prefix to its newest 500 rows and 72 hours. The prefix page lists them newest first, at most 5,000. Model: deepseek-v4-pro
149 lines
4.3 KiB
Go
149 lines
4.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 5,000 changes") {
|
|
t.Errorf("page does not say there are more than 5,000 changes")
|
|
}
|
|
}
|