package www_test import ( "bytes" "crypto/sha256" "encoding/hex" "sort" "strings" "testing" "testing/fstest" xhtml "golang.org/x/net/html" "amadan.net/rastrillo/fiesta" "amadan.net/rastrillo/fiesta/internal/gallery" "amadan.net/rastrillo/fiesta/internal/site" "amadan.net/rastrillo/fiesta/internal/www" ) // siteOf is a site source tree from path to content. func siteOf(files map[string]string) fstest.MapFS { m := fstest.MapFS{} for name, text := range files { m[name] = &fstest.MapFile{Data: []byte(text)} } return m } // pages runs Pages over Fiesta's own tree and siteDir, failed loudly. // The landing page's template and catalogs are overlaid from the real // site source, since Pages renders the root page from them. func pages(t *testing.T, siteDir fstest.MapFS) map[string][]byte { t.Helper() out, err := www.Pages(fiesta.FS(), withLanding(t, siteDir)) if err != nil { t.Fatalf("Pages: %v", err) } return out } // urlsOf reads every href and src on doc the way a browser would, // whatever the element, so a URL the gallery writes under its mount // and one a sample wrote are both seen. func urlsOf(t *testing.T, doc []byte) []string { t.Helper() var out []string z := xhtml.NewTokenizer(bytes.NewReader(doc)) for { switch z.Next() { case xhtml.ErrorToken: return out case xhtml.StartTagToken, xhtml.SelfClosingTagToken: _, more := z.TagName() for more { var k, v []byte k, v, more = z.TagAttr() if key := string(k); key == "href" || key == "src" { out = append(out, string(v)) } } } } } // TestPagesHasARootAndTheDesignSystem: the tree has a landing page at // the root and the design system's index under design-system/, and // neither is empty. func TestPagesHasARootAndTheDesignSystem(t *testing.T) { out := pages(t, siteOf(nil)) for _, name := range []string{"index.html", "design-system/index.html"} { if len(out[name]) == 0 { t.Errorf("Pages wrote no %s", name) } } } // TestTheDesignSystemIsMounted: on the design system's index and on // one example page, every root-relative href and src starts with // /design-system/, so the gallery served there asks for nothing from // the site's root. func TestTheDesignSystemIsMounted(t *testing.T) { out := pages(t, siteOf(nil)) for _, name := range []string{"design-system/index.html", "design-system/day/en/button.html"} { doc, ok := out[name] if !ok { t.Errorf("Pages wrote no %s", name) continue } rooted := 0 for _, u := range urlsOf(t, doc) { if !strings.HasPrefix(u, "/") { continue } rooted++ if !strings.HasPrefix(u, www.Mount+"/") { t.Errorf("%s links %s, which is not under %s/", name, u, www.Mount) } } if rooted == 0 { t.Errorf("%s has no root-relative URL at all; the check above read nothing", name) } } } // TestTheReleaseBaseIsTheSitesOrigin: the base the design system // writes its release-file links under is this site's origin and its // /1/ rewrite, so a link to a shipped file is one of the site's own. // // Already passing when written: the constant was declared at its final // value. Proved live by a named mutation, gallery.ReleaseBase changed // to end in /2/, which it reported as "is https://fiesta.rastrillo.org/2/, // want https://fiesta.rastrillo.org/1/". func TestTheReleaseBaseIsTheSitesOrigin(t *testing.T) { if gallery.ReleaseBase != www.Origin+"/1/" { t.Errorf("gallery.ReleaseBase is %q, want %q", gallery.ReleaseBase, www.Origin+"/1/") } } // TestAHiddenSiteFileIsRefused: a hidden file under the site source // fails the build, and the error names it, rather than being skipped // or published. func TestAHiddenSiteFileIsRefused(t *testing.T) { _, err := www.Pages(fiesta.FS(), siteOf(map[string]string{ "static/x.css": "body{}\n", "static/.DS_Store": "\x00", })) if err == nil { t.Fatal("Pages accepted static/.DS_Store") } if !strings.Contains(err.Error(), "static/.DS_Store") { t.Errorf("the error does not name the hidden file: %v", err) } } // TestStaticFilesAreCopied: a file under static/ in the site source is // in the tree at the same path, byte for byte. func TestStaticFilesAreCopied(t *testing.T) { want := "/* the site's own */\nbody{margin:0}\n" out := pages(t, siteOf(map[string]string{"static/x.css": want})) if got := string(out["static/x.css"]); got != want { t.Errorf("static/x.css is %q, want %q", got, want) } } // sumsOf is SHA256SUMS as build.Dist writes it: one ` ` // line per file, sorted by path, the sums file itself left out. func sumsOf(files map[string][]byte) []byte { names := make([]string, 0, len(files)) for name := range files { if name != "SHA256SUMS" { names = append(names, name) } } sort.Strings(names) var b bytes.Buffer for _, name := range names { sum := sha256.Sum256(files[name]) b.WriteString(hex.EncodeToString(sum[:]) + " " + name + "\n") } return b.Bytes() } // releases is a releases tree holding one small, checksummed 1.0.0. func releases() fstest.MapFS { files := map[string][]byte{ "fiesta.css": []byte("/*! Fiesta 1.0.0 */\nbody{margin:0}\n"), "LICENSE": []byte("MPL-2.0\n"), } files["SHA256SUMS"] = sumsOf(files) m := fstest.MapFS{} for name, data := range files { m["1.0.0/"+name] = &fstest.MapFile{Data: data} } return m } // TestSiteBuildAcceptsThePages is a preservation test: site.Build // takes FS(Pages(...)) as its pages beside a checksummed 1.0.0/, and // what Pages wrote is in the site at the same paths beside the release. func TestSiteBuildAcceptsThePages(t *testing.T) { out := pages(t, siteOf(map[string]string{"static/x.css": "body{}\n"})) built, err := site.Build(releases(), www.FS(out), site.Rules{}) if err != nil { t.Fatalf("site.Build refused the pages: %v", err) } for name, data := range out { if !bytes.Equal(built[name], data) { t.Errorf("%s in the site differs from Pages, or is missing", name) } } if _, ok := built["1.0.0/fiesta.css"]; !ok { t.Error("the site lost the release") } if got := string(built["_redirects"]); got != "/1/* /1.0.0/:splat 200\n" { t.Errorf("_redirects is %q", got) } }