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, "