Show the prefix page's origin names from the prefix query (closes #67)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The prefix page looked up each origin AS's details only for its handle and description, and that lookup also reads every live route the AS originates. GetPrefixDetailsContext already selects the handle and description; it now returns them keyed by AS number, and the page uses them instead. The origin list was handed each description but never rendered it; it now shows it after the handle. Model: opus-5-5
This commit is contained in:
@@ -3,10 +3,13 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -15,6 +18,7 @@ import (
|
||||
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||
"git.eeqj.de/sneak/routewatch/internal/metrics"
|
||||
"git.eeqj.de/sneak/routewatch/internal/streamer"
|
||||
"git.eeqj.de/sneak/routewatch/pkg/asinfo"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -200,6 +204,72 @@ func TestPrefixDetailHandlersParsePrefix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// failingASDetailsDB wraps a real store and fails every AS details lookup.
|
||||
type failingASDetailsDB struct {
|
||||
database.Store
|
||||
}
|
||||
|
||||
func (failingASDetailsDB) GetASDetailsContext(_ context.Context, _ int) (*database.ASN, []database.LiveRoute, error) {
|
||||
return nil, nil, errors.New("AS details lookup failed")
|
||||
}
|
||||
|
||||
// TestPrefixDetailShowsOriginNames checks that the prefix page shows each
|
||||
// origin AS's handle and description without looking up the AS's details,
|
||||
// which also read every route the AS originates. Before the fix the page did
|
||||
// that lookup once per origin, so with it failing the names were missing
|
||||
// (https://git.eeqj.de/sneak/routewatch/issues/67). The AS path column shows
|
||||
// handles too, so the test looks for the origin list's "(handle)" form.
|
||||
func TestPrefixDetailShowsOriginNames(t *testing.T) {
|
||||
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() })
|
||||
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
origins := []int{15169, 13335}
|
||||
for _, asn := range origins {
|
||||
if _, err := db.GetOrCreateASN(asn, ts); err != nil {
|
||||
t.Fatalf("GetOrCreateASN(%d): %v", asn, err)
|
||||
}
|
||||
}
|
||||
if err := db.UpsertLiveRouteBatch([]*database.LiveRoute{
|
||||
{
|
||||
ID: uuid.New(), Prefix: "192.0.2.0/24", MaskLength: 24, IPVersion: 4,
|
||||
OriginASN: origins[0], PeerIP: "198.51.100.1", ASPath: []int{origins[0]}, NextHop: "198.51.100.1",
|
||||
LastUpdated: ts,
|
||||
},
|
||||
{
|
||||
ID: uuid.New(), Prefix: "192.0.2.0/24", MaskLength: 24, IPVersion: 4,
|
||||
OriginASN: origins[1], PeerIP: "198.51.100.2", ASPath: []int{origins[1]}, NextHop: "198.51.100.2",
|
||||
LastUpdated: ts,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||
}
|
||||
|
||||
s := New(failingASDetailsDB{db}, streamer.New(logger.New(), metrics.New()), logger.New(), &config.Config{})
|
||||
rec := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/prefix/192.0.2.0/24", nil))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status %d, want %d; body %s", rec.Code, http.StatusOK, rec.Body)
|
||||
}
|
||||
|
||||
page := rec.Body.String()
|
||||
for _, asn := range origins {
|
||||
info, ok := asinfo.Get(asn)
|
||||
if !ok {
|
||||
t.Fatalf("AS%d is not in the AS info data", asn)
|
||||
}
|
||||
if want := "(" + html.EscapeString(info.Handle) + ")"; !strings.Contains(page, want) {
|
||||
t.Errorf("page does not show AS%d's handle %q", asn, want)
|
||||
}
|
||||
if want := html.EscapeString(info.Description); !strings.Contains(page, want) {
|
||||
t.Errorf("page does not show AS%d's description %q", asn, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// settledGoroutineCount lets transient goroutines finish, then reports the
|
||||
// current count.
|
||||
func settledGoroutineCount() int {
|
||||
|
||||
Reference in New Issue
Block a user