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. The answer holds the query, the number of matches and the first 50 of them; an empty q answers 400. asinfo.Search now ignores case, uses strings.Contains instead of its own substring loop, and returns its matches in AS number order. A number, or AS and a number, typed into the card still opens that AS's page. The card builds the list with textContent, since the names come from outside data. Model: opus-5-5
261 lines
5.0 KiB
Go
261 lines
5.0 KiB
Go
package asinfo_test
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/routewatch/pkg/asinfo"
|
|
)
|
|
|
|
func TestGet(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
asn int
|
|
wantOK bool
|
|
wantDesc string
|
|
}{
|
|
{
|
|
name: "AS1 Level 3",
|
|
asn: 1,
|
|
wantOK: true,
|
|
wantDesc: "Level 3 Parent, LLC",
|
|
},
|
|
{
|
|
name: "AS15169 Google",
|
|
asn: 15169,
|
|
wantOK: true,
|
|
wantDesc: "Google LLC",
|
|
},
|
|
{
|
|
name: "Non-existent AS",
|
|
asn: 999999999,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "AS0 IANA Reserved",
|
|
asn: 0,
|
|
wantOK: true,
|
|
wantDesc: "Internet Assigned Numbers Authority",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
info, ok := asinfo.Get(tt.asn)
|
|
if ok != tt.wantOK {
|
|
t.Errorf("Get(%d) ok = %v, want %v", tt.asn, ok, tt.wantOK)
|
|
|
|
return
|
|
}
|
|
|
|
if ok && info.Description != tt.wantDesc {
|
|
t.Errorf("Get(%d) description = %q, want %q",
|
|
tt.asn, info.Description, tt.wantDesc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDescription(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
asn int
|
|
want string
|
|
}{
|
|
{1, "Level 3 Parent, LLC"},
|
|
{999999999, ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := asinfo.GetDescription(tt.asn)
|
|
if got != tt.want {
|
|
t.Errorf("GetDescription(%d) = %q, want %q", tt.asn, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetHandle(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
asn int
|
|
want string
|
|
}{
|
|
{1, "LVLT"},
|
|
{0, "IANA-RSVD"},
|
|
{999999999, ""},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := asinfo.GetHandle(tt.asn)
|
|
if got != tt.want {
|
|
t.Errorf("GetHandle(%d) = %q, want %q", tt.asn, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTotal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
total := asinfo.Total()
|
|
if total < 100000 {
|
|
t.Errorf("Total() = %d, expected > 100000", total)
|
|
}
|
|
|
|
// Verify it's consistent
|
|
total2 := asinfo.Total()
|
|
if total2 != total {
|
|
t.Errorf("Total() returned different values: %d vs %d", total, total2)
|
|
}
|
|
}
|
|
|
|
func TestSearch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
wantMin int
|
|
}{
|
|
{
|
|
name: "Search for Google",
|
|
query: "Google",
|
|
wantMin: 1,
|
|
},
|
|
{
|
|
name: "Search for University",
|
|
query: "University",
|
|
wantMin: 10,
|
|
},
|
|
{
|
|
name: "Empty search",
|
|
query: "",
|
|
wantMin: 0,
|
|
},
|
|
{
|
|
name: "Non-existent org",
|
|
query: "XYZABC123456",
|
|
wantMin: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
results := asinfo.Search(tt.query)
|
|
if len(results) < tt.wantMin {
|
|
t.Errorf("Search(%q) returned %d results, want at least %d",
|
|
tt.query, len(results), tt.wantMin)
|
|
}
|
|
|
|
// Verify all results contain the query, ignoring case
|
|
query := strings.ToLower(tt.query)
|
|
for _, r := range results {
|
|
if !strings.Contains(strings.ToLower(r.Handle), query) &&
|
|
!strings.Contains(strings.ToLower(r.Description), query) {
|
|
t.Errorf("Search result ASN %d doesn't contain query %q",
|
|
r.ASN, tt.query)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSearchIgnoresCase searches for Cloudflare's handle in lower case and its
|
|
// description in upper case, and expects both searches to find AS13335.
|
|
func TestSearchIgnoresCase(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const cloudflareASN = 13335 // handle CLOUDFLARENET, "Cloudflare, Inc."
|
|
|
|
isCloudflare := func(info asinfo.Info) bool {
|
|
return info.ASN == cloudflareASN
|
|
}
|
|
|
|
for _, query := range []string{"cloudflarenet", "CLOUDFLARE, INC."} {
|
|
if !slices.ContainsFunc(asinfo.Search(query), isCloudflare) {
|
|
t.Errorf("Search(%q) did not find AS%d", query, cloudflareASN)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSearchOrdersByASN expects the matches of a search with many of them to
|
|
// come in AS number order.
|
|
func TestSearchOrdersByASN(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const query = "University"
|
|
|
|
results := asinfo.Search(query)
|
|
|
|
for i := 1; i < len(results); i++ {
|
|
if results[i-1].ASN >= results[i].ASN {
|
|
t.Fatalf("Search(%q): AS%d comes after AS%d",
|
|
query, results[i].ASN, results[i-1].ASN)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDataIntegrity(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Verify no duplicate ASNs
|
|
all := asinfo.All()
|
|
seen := make(map[int]bool)
|
|
|
|
for _, info := range all {
|
|
if seen[info.ASN] {
|
|
t.Errorf("Duplicate ASN found: %d", info.ASN)
|
|
}
|
|
|
|
seen[info.ASN] = true
|
|
}
|
|
|
|
// Verify all entries have required fields
|
|
for _, info := range all {
|
|
if info.Handle == "" && info.ASN != 0 {
|
|
t.Errorf("ASN %d has empty handle", info.ASN)
|
|
}
|
|
|
|
if info.Description == "" {
|
|
t.Errorf("ASN %d has empty description", info.ASN)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkGet(b *testing.B) {
|
|
// Common ASNs to lookup
|
|
asns := []int{1, 15169, 13335, 32934, 8075, 16509}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := range b.N {
|
|
asinfo.Get(asns[i%len(asns)])
|
|
}
|
|
}
|
|
|
|
func BenchmarkSearch(b *testing.B) {
|
|
queries := []string{"Google", "Amazon", "Microsoft", "University"}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := range b.N {
|
|
asinfo.Search(queries[i%len(queries)])
|
|
}
|
|
}
|