Make the home page's AS name search work (closes #151)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The AS Name Search card on the home page said the search was coming
soon. It now asks the new GET /api/v1/search/as?q=<text>, which
searches the AS list built into asinfo, and lists the matches as links
to their AS pages. An empty q answers 400. asinfo.Search now ignores
case and returns its matches in AS number order. asinfo puts each
handle and description in lower case once, when the list loads.
A match can be an AS that the database has no record of. Its AS page
and /api/v1/as/{asn} then take its handle and description from asinfo
and still list its prefixes. They answer 404 only for an AS with no
record, no asinfo entry and no prefix. Empty prefix lists in that JSON
answer are [] rather than null.
Model: opus-5-5
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"git.eeqj.de/sneak/routewatch/internal/config"
|
||||
"git.eeqj.de/sneak/routewatch/internal/logger"
|
||||
"git.eeqj.de/sneak/routewatch/pkg/asinfo"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
@@ -1699,6 +1700,86 @@ func TestGetASDetailsListsEachPrefixOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetASDetailsOfASNotInDatabase asks for ASes that the database has no
|
||||
// record of, some with stored routes. Each gets the prefixes of its routes and
|
||||
// no seen times. One that the AS info lookup knows gets its handle and
|
||||
// description from it. An AS with no record, no lookup entry and no route is
|
||||
// ErrNoRoute. Before the fix an AS with no record got no prefixes, although
|
||||
// its routes were stored.
|
||||
func TestGetASDetailsOfASNotInDatabase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := New(&config.Config{StateDir: t.TempDir()}, logger.New())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database: %v", err)
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
|
||||
ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
|
||||
tests := []struct {
|
||||
asn int
|
||||
listed bool // in the AS info lookup
|
||||
prefixes []string // of the routes stored for the AS
|
||||
}{
|
||||
{15169, true, []string{"192.0.2.0/24"}},
|
||||
{13335, true, nil},
|
||||
{4200000000, false, []string{"198.51.100.0/24"}},
|
||||
}
|
||||
|
||||
var routes []*LiveRoute
|
||||
|
||||
for _, tt := range tests {
|
||||
if _, ok := asinfo.Get(tt.asn); ok != tt.listed {
|
||||
t.Fatalf("AS%d in the AS info data: %t, want %t", tt.asn, ok, tt.listed)
|
||||
}
|
||||
|
||||
for _, prefix := range tt.prefixes {
|
||||
routes = append(routes, mkV4Route(t, prefix, tt.asn, ts))
|
||||
}
|
||||
}
|
||||
|
||||
err = db.UpsertLiveRouteBatch(routes)
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertLiveRouteBatch: %v", err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, prefixes, err := db.GetASDetails(tt.asn)
|
||||
if err != nil {
|
||||
t.Errorf("GetASDetails(%d): %v", tt.asn, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
want := ASN{ASN: tt.asn}
|
||||
if info, ok := asinfo.Get(tt.asn); ok {
|
||||
want.Handle, want.Description = info.Handle, info.Description
|
||||
}
|
||||
|
||||
var gotPrefixes []string
|
||||
for _, route := range prefixes {
|
||||
gotPrefixes = append(gotPrefixes, route.Prefix)
|
||||
}
|
||||
|
||||
if *got != want || !slices.Equal(gotPrefixes, tt.prefixes) {
|
||||
t.Errorf("GetASDetails(%d) = %+v with prefixes %v, want %+v with %v",
|
||||
tt.asn, *got, gotPrefixes, want, tt.prefixes)
|
||||
}
|
||||
}
|
||||
|
||||
const absentASN = 4200000001 // no record, no lookup entry and no route
|
||||
|
||||
if _, ok := asinfo.Get(absentASN); ok {
|
||||
t.Fatalf("AS%d is in the AS info data", absentASN)
|
||||
}
|
||||
|
||||
_, _, err = db.GetASDetails(absentASN)
|
||||
if !errors.Is(err, ErrNoRoute) {
|
||||
t.Errorf("GetASDetails(%d): error %v, want %v", absentASN, err, ErrNoRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// TestASPrefixQueriesUseCoveringIndexes checks the plan of each query that
|
||||
// lists the prefixes an AS originates: a search of a covering index and no
|
||||
// temporary B-tree, so the list reads one index entry per prefix, not one
|
||||
|
||||
Reference in New Issue
Block a user