|
|
@@ -0,0 +1,149 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "io/fs"
|
|
|
+ "net/http"
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+ "testing/fstest"
|
|
|
+)
|
|
|
+
|
|
|
+func TestLoadReleases(t *testing.T) {
|
|
|
+ fsys := fstest.MapFS{
|
|
|
+ "README.md": {Data: []byte("# Index, not a release\n")},
|
|
|
+ "001-first/README.md": {Data: []byte("# First\n\nBranch `a` · version (none)\n\nBody one.\n")},
|
|
|
+ "002-second/README.md": {Data: []byte("# Second\r\n\r\nBranch `b` · version 0.2.0\r\n\r\n- **Version:** 0.2.0\r\n")},
|
|
|
+ "002-second/desktop.png": {Data: []byte("png")},
|
|
|
+ "003-no-title/README.md": {Data: []byte("No heading here.\n")},
|
|
|
+ "004-no-readme/x.png": {Data: []byte("png")},
|
|
|
+ "Bad_Name/README.md": {Data: []byte("# Bad\n")},
|
|
|
+ }
|
|
|
+ list, byID, err := loadReleases(fsys, newMarkdown())
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if len(list) != 2 || list[0].ID != "002-second" || list[1].ID != "001-first" {
|
|
|
+ t.Fatalf("got %d releases, want 002 then 001: %+v", len(list), list)
|
|
|
+ }
|
|
|
+ r := byID["002-second"]
|
|
|
+ if r.Number != "002" || r.Title != "Second" || r.Version != "0.2.0" {
|
|
|
+ t.Errorf("002: %+v", r)
|
|
|
+ }
|
|
|
+ if byID["001-first"].Version != "" {
|
|
|
+ t.Errorf("001: version = %q, want none", byID["001-first"].Version)
|
|
|
+ }
|
|
|
+ if strings.Contains(string(r.HTML), "<h1") {
|
|
|
+ t.Errorf("title heading repeated in body: %s", r.HTML)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Every record committed under pull-releases/ must load, so a malformed one
|
|
|
+// fails the build's tests instead of silently disappearing from the site.
|
|
|
+func TestEmbeddedReleasesAllLoad(t *testing.T) {
|
|
|
+ relFS, err := fs.Sub(releaseFS, "pull-releases")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ entries, err := fs.ReadDir(relFS, ".")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ var dirs int
|
|
|
+ for _, e := range entries {
|
|
|
+ if e.IsDir() {
|
|
|
+ dirs++
|
|
|
+ if !releaseDir.MatchString(e.Name()) {
|
|
|
+ t.Errorf("pull-releases/%s: folder name must be NNN-name", e.Name())
|
|
|
+ }
|
|
|
+ for _, img := range []string{"desktop.png", "mobile.png"} {
|
|
|
+ if _, err := fs.Stat(relFS, e.Name()+"/"+img); err != nil {
|
|
|
+ t.Errorf("pull-releases/%s: missing %s", e.Name(), img)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ list, _, err := loadReleases(relFS, newMarkdown())
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if len(list) != dirs || dirs == 0 {
|
|
|
+ t.Errorf("loaded %d releases from %d folders", len(list), dirs)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestReleasePages(t *testing.T) {
|
|
|
+ srv, _ := newTestServer(t, nil)
|
|
|
+ const id = "003-css-cache-busting"
|
|
|
+
|
|
|
+ status, _, body := get(t, srv, "/releases")
|
|
|
+ if status != 200 {
|
|
|
+ t.Fatalf("/releases: status = %d", status)
|
|
|
+ }
|
|
|
+ for _, want := range []string{
|
|
|
+ "<h1>Release notes</h1>",
|
|
|
+ `<a href="/releases/` + id + `/">`,
|
|
|
+ `<a href="/releases/001-initial/">Add Markdown blog server</a>`,
|
|
|
+ "#003 · version 0.2.1",
|
|
|
+ } {
|
|
|
+ if !strings.Contains(body, want) {
|
|
|
+ t.Errorf("/releases: missing %q", want)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if strings.Index(body, "003-css") > strings.Index(body, "001-initial") {
|
|
|
+ t.Error("/releases: newest release should be listed first")
|
|
|
+ }
|
|
|
+
|
|
|
+ status, _, body = get(t, srv, "/releases/"+id+"/")
|
|
|
+ if status != 200 {
|
|
|
+ t.Fatalf("release page: status = %d", status)
|
|
|
+ }
|
|
|
+ for _, want := range []string{
|
|
|
+ "<h1>Add content hash to stylesheet URL; start pull-releases records</h1>",
|
|
|
+ `<span class="meta">Release #003</span>`,
|
|
|
+ `<img src="desktop.png" alt="Desktop">`,
|
|
|
+ `<a href="/releases">`,
|
|
|
+ } {
|
|
|
+ if !strings.Contains(body, want) {
|
|
|
+ t.Errorf("release page: missing %q", want)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ status, h, img := get(t, srv, "/releases/"+id+"/desktop.png")
|
|
|
+ if status != 200 || h.Get("Content-Type") != "image/png" || !strings.HasPrefix(img, "\x89PNG") {
|
|
|
+ t.Errorf("screenshot: status = %d, type = %q", status, h.Get("Content-Type"))
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, path := range []string{
|
|
|
+ "/releases/nope/",
|
|
|
+ "/releases/nope",
|
|
|
+ "/releases/" + id + "/README.md",
|
|
|
+ "/releases/" + id + "/missing.png",
|
|
|
+ "/releases/" + id + "/..%2f..%2fmain.go",
|
|
|
+ "/releases/README.md",
|
|
|
+ } {
|
|
|
+ if status, _, _ := get(t, srv, path); status != 404 {
|
|
|
+ t.Errorf("%s: status = %d, want 404", path, status)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestReleaseRedirect(t *testing.T) {
|
|
|
+ srv, _ := newTestServer(t, nil)
|
|
|
+ client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
|
|
|
+ resp, err := client.Get(srv.URL + "/releases/001-initial")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ resp.Body.Close()
|
|
|
+ if resp.StatusCode != 301 || resp.Header.Get("Location") != "/releases/001-initial/" {
|
|
|
+ t.Errorf("status = %d, Location = %q", resp.StatusCode, resp.Header.Get("Location"))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestAboutLinksToReleases(t *testing.T) {
|
|
|
+ srv, _ := newTestServer(t, nil)
|
|
|
+ _, _, body := get(t, srv, "/about")
|
|
|
+ if !strings.Contains(body, "<dt>Release notes</dt>") || !strings.Contains(body, `<a href="/releases">`) {
|
|
|
+ t.Errorf("About page has no release notes link:\n%s", body)
|
|
|
+ }
|
|
|
+}
|