package templates_test import ( "bytes" "html/template" "strings" "testing" "git.eeqj.de/sneak/routewatch/internal/database" "git.eeqj.de/sneak/routewatch/internal/templates" ) // renderPage renders a page with the data every page needs, and the visitor's // address, which the home page also needs. func renderPage(t *testing.T, tmpl *template.Template) string { t.Helper() var page bytes.Buffer err := tmpl.Execute(&page, struct{ CSPNonce, ClientIP string }{}) if err != nil { t.Fatalf("rendering the page: %v", err) } return page.String() } // TestFooterLicense renders the home and status pages and expects each footer // to name the licence in LICENSE, MIT, and not WTFPL. func TestFooterLicense(t *testing.T) { t.Parallel() const ( license = "MIT" wrongLicense = "WTFPL" ) pages := map[string]*template.Template{ "index": templates.IndexTemplate(), "status": templates.StatusTemplate(), } for name, tmpl := range pages { t.Run(name, func(t *testing.T) { t.Parallel() _, footer, found := strings.Cut(renderPage(t, tmpl), "") if !strings.Contains(footer, license) { t.Errorf("footer does not name %s: %q", license, footer) } if strings.Contains(footer, wrongLicense) { t.Errorf("footer names %s: %q", wrongLicense, footer) } }) } } // TestFreshnessIndicator renders the home and status pages and expects each to // have, right after the status element, the element where the page's script // shows its spinner or "stale". func TestFreshnessIndicator(t *testing.T) { t.Parallel() pages := map[string]struct { tmpl *template.Template status string indicator string }{ "index": { tmpl: templates.IndexTemplate(), status: `-`, indicator: ``, }, "status": { tmpl: templates.StatusTemplate(), status: `-`, indicator: ``, }, } for name, page := range pages { t.Run(name, func(t *testing.T) { t.Parallel() _, afterStatus, found := strings.Cut(renderPage(t, page.tmpl), page.status) if !found { t.Fatalf("the page has no status element %s", page.status) } if !strings.HasPrefix(strings.TrimSpace(afterStatus), page.indicator) { t.Errorf("the status element is not followed by %s", page.indicator) } }) } } // TestASDetailPrefixListsFold renders the AS page with one IPv4 and one IPv6 // prefix. It expects each prefix list inside its own
element that // starts open and whose holds the list's heading, so that a click on // the heading folds the list. func TestASDetailPrefixListsFold(t *testing.T) { t.Parallel() const ( asn = 64500 prefixV4 = "192.0.2.0/24" prefixV6 = "2001:db8::/32" ) ipv4 := []database.LiveRoute{{Prefix: prefixV4}} ipv6 := []database.LiveRoute{{Prefix: prefixV6}} // The fields the AS page handler gives the template. data := struct { ASN *database.ASN IPv4Prefixes []database.LiveRoute IPv6Prefixes []database.LiveRoute TotalCount int IPv4Count int IPv6Count int Peers []database.ASPeer PeerCount int CSPNonce string }{ ASN: &database.ASN{ASN: asn}, IPv4Prefixes: ipv4, IPv6Prefixes: ipv6, TotalCount: len(ipv4) + len(ipv6), IPv4Count: len(ipv4), IPv6Count: len(ipv6), } var rendered bytes.Buffer err := templates.ASDetailTemplate().Execute(&rendered, data) if err != nil { t.Fatalf("rendering the page: %v", err) } page := rendered.String() lists := []struct{ heading, prefix string }{ {"IPv4 Prefixes", prefixV4}, {"IPv6 Prefixes", prefixV6}, } for _, list := range lists { before, _, found := strings.Cut(page, list.prefix) if !found { t.Fatalf("the page does not list %s", list.prefix) } // The list's section starts at the last
") { t.Errorf("%s: the list is not inside a
element", list.heading) continue } tag, section, _ := strings.Cut(before[start:], ">") if !strings.Contains(tag, " open") { t.Errorf("%s: the
element does not start open: %s>", list.heading, tag) } _, summary, _ := strings.Cut(section, "") if !strings.Contains(summary, list.heading) { t.Errorf("%s: the does not hold the heading", list.heading) } } }