121 lines
3.4 KiB
Go
121 lines
3.4 KiB
Go
package web
|
|
|
|
import (
|
|
"io/fs"
|
|
"path"
|
|
"strings"
|
|
"testing"
|
|
|
|
webui "github.com/owen/vibedns/web"
|
|
)
|
|
|
|
// TestTemplatesParse fails the build if any embedded template has a syntax
|
|
// error or calls a function that is not registered. Without this, a typo in a
|
|
// template only surfaces when a user opens that particular page.
|
|
func TestTemplatesParse(t *testing.T) {
|
|
tmpl, err := loadTemplates(templateFuncs())
|
|
if err != nil {
|
|
t.Fatalf("templates did not parse: %v", err)
|
|
}
|
|
|
|
pages, err := fs.Glob(webui.Templates(), "pages/*.html")
|
|
if err != nil {
|
|
t.Fatalf("could not list pages: %v", err)
|
|
}
|
|
if len(pages) == 0 {
|
|
t.Fatal("no page templates were embedded")
|
|
}
|
|
|
|
for _, p := range pages {
|
|
name := strings.TrimSuffix(path.Base(p), ".html")
|
|
if _, ok := tmpl.sets[name]; !ok {
|
|
t.Errorf("page %s produced no template set", name)
|
|
}
|
|
}
|
|
t.Logf("parsed %d page templates", len(pages))
|
|
}
|
|
|
|
// TestEveryRoutedPageHasTemplate guards against a handler rendering a page name
|
|
// that does not exist, which would otherwise be a 500 at runtime.
|
|
func TestEveryRoutedPageHasTemplate(t *testing.T) {
|
|
tmpl, err := loadTemplates(templateFuncs())
|
|
if err != nil {
|
|
t.Fatalf("templates did not parse: %v", err)
|
|
}
|
|
|
|
// Every name passed to s.render anywhere in the package.
|
|
rendered := []string{
|
|
"dashboard", "error", "zones", "zone_form", "records", "records_all",
|
|
"resolver", "cache", "networks", "network_form", "policies", "policy_form",
|
|
"lists", "list_detail", "querylog", "audit", "tools", "account",
|
|
"settings_dns", "settings_resolver", "settings_cache", "settings_logging",
|
|
"settings_http", "settings_database", "settings_api",
|
|
}
|
|
for _, name := range rendered {
|
|
if _, ok := tmpl.sets[name]; !ok {
|
|
t.Errorf("handler renders %q but no such template exists", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestStaticAssetsEmbedded confirms the offline assets actually made it into
|
|
// the binary. The management interface must work without Internet access.
|
|
func TestStaticAssetsEmbedded(t *testing.T) {
|
|
static := webui.Static()
|
|
required := []string{
|
|
"css/bootstrap.min.css",
|
|
"css/bootstrap-icons.min.css",
|
|
"css/app.css",
|
|
"js/bootstrap.bundle.min.js",
|
|
"js/chart.umd.js",
|
|
"js/app.js",
|
|
"fonts/bootstrap-icons.woff2",
|
|
"img/favicon.svg",
|
|
}
|
|
for _, name := range required {
|
|
f, err := static.Open(name)
|
|
if err != nil {
|
|
t.Errorf("static asset %s is missing from the binary: %v", name, err)
|
|
continue
|
|
}
|
|
info, err := f.Stat()
|
|
if err == nil && info.Size() == 0 {
|
|
t.Errorf("static asset %s is empty", name)
|
|
}
|
|
f.Close()
|
|
}
|
|
}
|
|
|
|
// TestNoCDNReferences guards the offline guarantee: a stray absolute URL in a
|
|
// template or stylesheet would silently break the UI on an air-gapped network.
|
|
func TestNoCDNReferences(t *testing.T) {
|
|
check := func(fsys fs.FS, label string) {
|
|
err := fs.WalkDir(fsys, ".", func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
switch path.Ext(p) {
|
|
case ".html", ".css", ".js":
|
|
default:
|
|
return nil
|
|
}
|
|
body, err := fs.ReadFile(fsys, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, bad := range []string{"https://cdn.", "http://cdn.", "//cdn.jsdelivr", "//unpkg.com"} {
|
|
if strings.Contains(string(body), bad) {
|
|
t.Errorf("%s/%s references an external CDN (%q); assets must be served from the binary",
|
|
label, p, bad)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("walking %s: %v", label, err)
|
|
}
|
|
}
|
|
check(webui.Templates(), "templates")
|
|
check(webui.Static(), "static")
|
|
}
|