internal/frontend: add a test that checks templates
Call a "static" template checker on our templates after we parse
them. It can catch errors in a test, instead of during serving.
For example, it will find misspelled field names.
Change-Id: I8eb352b5f1c584957e8c0cf6f30b24f44e2b5743
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/275972
Trust: Jonathan Amsterdam <jba@google.com>
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/go.mod b/go.mod
index 0c1128a..f9829e5 100644
--- a/go.mod
+++ b/go.mod
@@ -27,9 +27,10 @@
github.com/google/go-cmp v0.5.2
github.com/google/go-replayers/httpreplay v0.1.0
github.com/google/licensecheck v0.0.0-20200805042302-c54f297c3b57
- github.com/google/safehtml v0.0.1
+ github.com/google/safehtml v0.0.2
github.com/jackc/pgconn v1.7.2
github.com/jackc/pgx/v4 v4.9.2
+ github.com/jba/templatecheck v0.2.0
github.com/lib/pq v1.3.0
github.com/microcosm-cc/bluemonday v1.0.2
github.com/russross/blackfriday/v2 v2.0.1
@@ -42,6 +43,7 @@
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 // indirect
+ golang.org/x/text v0.3.4 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
google.golang.org/api v0.32.0
google.golang.org/genproto v0.0.0-20200923140941-5646d36feee1
diff --git a/go.sum b/go.sum
index ee83ac5..4ea8016 100644
--- a/go.sum
+++ b/go.sum
@@ -270,8 +270,8 @@
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7 h1:k+KkMRk8mGOu1xG38StS7dQ+Z6oW1i9n3dgrAVU9Q/E=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/safehtml v0.0.1 h1:w2QjiCjg5S0Ca7JPd4H+fbuB0eLTK9qR3vJz3xLnhWE=
-github.com/google/safehtml v0.0.1/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
+github.com/google/safehtml v0.0.2 h1:ZOt2VXg4x24bW0m2jtzAOkhoXV0iM8vNKc0paByCZqM=
+github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -347,6 +347,10 @@
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jba/templatecheck v0.1.0 h1:Rawj2Z1sx5nW3CtaZ+d973VKENKkXSIlscBCXMFAUjs=
+github.com/jba/templatecheck v0.1.0/go.mod h1:HzXVhxZv+uArJx7Reareec/jWUvKoHpKDvs6I3wdsRw=
+github.com/jba/templatecheck v0.2.0 h1:OHHJOumS3D6HiHiRp7FuRF17icl7AenH2cvufaBw5Ss=
+github.com/jba/templatecheck v0.2.0/go.mod h1:HzXVhxZv+uArJx7Reareec/jWUvKoHpKDvs6I3wdsRw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -700,6 +704,8 @@
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/internal/frontend/server.go b/internal/frontend/server.go
index 339ff32..219371c 100644
--- a/internal/frontend/server.go
+++ b/internal/frontend/server.go
@@ -481,6 +481,19 @@
return buf.Bytes(), nil
}
+var templateFuncs = template.FuncMap{
+ "add": func(i, j int) int { return i + j },
+ "pluralize": func(i int, s string) string {
+ if i == 1 {
+ return s
+ }
+ return s + "s"
+ },
+ "commaseparate": func(s []string) string {
+ return strings.Join(s, ", ")
+ },
+}
+
// parsePageTemplates parses html templates contained in the given base
// directory in order to generate a map of Name->*template.Template.
//
@@ -507,18 +520,7 @@
templates := make(map[string]*template.Template)
for _, set := range htmlSets {
- t, err := template.New("base.tmpl").Funcs(template.FuncMap{
- "add": func(i, j int) int { return i + j },
- "pluralize": func(i int, s string) string {
- if i == 1 {
- return s
- }
- return s + "s"
- },
- "commaseparate": func(s []string) string {
- return strings.Join(s, ", ")
- },
- }).ParseFilesFromTrustedSources(join(base, tsc("base.tmpl")))
+ t, err := template.New("base.tmpl").Funcs(templateFuncs).ParseFilesFromTrustedSources(join(base, tsc("base.tmpl")))
if err != nil {
return nil, fmt.Errorf("ParseFiles: %v", err)
}
diff --git a/internal/frontend/server_test.go b/internal/frontend/server_test.go
index 7b8bb06..da9899f 100644
--- a/internal/frontend/server_test.go
+++ b/internal/frontend/server_test.go
@@ -16,6 +16,7 @@
"time"
"github.com/google/safehtml/template"
+ "github.com/jba/templatecheck"
"golang.org/x/net/html"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/experiment"
@@ -1106,3 +1107,63 @@
postgres.ResetTestDB(testDB, t)
}
}
+
+func TestCheckTemplates(t *testing.T) {
+ // Perform additional checks on parsed templates.
+ staticPath := template.TrustedSourceFromConstant("../../content/static")
+ templateDir := template.TrustedSourceJoin(staticPath, template.TrustedSourceFromConstant("html"))
+ templates, err := parsePageTemplates(templateDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, c := range []struct {
+ name string
+ subs []string
+ typeval interface{}
+ }{
+ {"badge", nil, badgePage{}},
+ // error.tmpl omitted because relies on an associated "message" template
+ // that's parsed on demand; see renderErrorPage above.
+ {"fetch", nil, errorPage{}},
+ {"index", nil, basePage{}},
+ {"license_policy", nil, licensePolicyPage{}},
+ {"search", nil, SearchPage{}},
+ {"search_help", nil, basePage{}},
+ {"unit_details", nil, UnitPage{}},
+ {
+ "unit_details",
+ []string{"unit_outline", "legacy_unit_outline", "unit_readme", "unit_doc", "unit_files", "unit_directories"},
+ MainDetails{},
+ },
+ {"unit_importedby", nil, UnitPage{}},
+ {"unit_importedby", []string{"importedby"}, ImportedByDetails{}},
+ {"unit_imports", nil, UnitPage{}},
+ {"unit_imports", []string{"imports"}, ImportsDetails{}},
+ {"unit_licenses", nil, UnitPage{}},
+ {"unit_licenses", []string{"licenses"}, LicensesDetails{}},
+ {"unit_versions", nil, UnitPage{}},
+ {"unit_versions", []string{"versions"}, VersionsDetails{}},
+ } {
+ t.Run(c.name, func(t *testing.T) {
+ tm := templates[c.name+".tmpl"]
+ if tm == nil {
+ t.Fatalf("no template %q", c.name)
+ }
+ if c.subs == nil {
+ if err := templatecheck.CheckSafe(tm, c.typeval, templateFuncs); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ for _, n := range c.subs {
+ s := tm.Lookup(n)
+ if s == nil {
+ t.Fatalf("no sub-template %q of %q", n, c.name)
+ }
+ if err := templatecheck.CheckSafe(s, c.typeval, templateFuncs); err != nil {
+ t.Fatalf("%s: %v", n, err)
+ }
+ }
+ }
+ })
+ }
+}