releases_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package main
  2. import (
  3. "io/fs"
  4. "net/http"
  5. "strings"
  6. "testing"
  7. "testing/fstest"
  8. )
  9. func TestLoadReleases(t *testing.T) {
  10. fsys := fstest.MapFS{
  11. "README.md": {Data: []byte("# Index, not a release\n")},
  12. "001-first/README.md": {Data: []byte("# First\n\nBranch `a` · version (none)\n\nBody one.\n")},
  13. "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")},
  14. "002-second/desktop.png": {Data: []byte("png")},
  15. "003-no-title/README.md": {Data: []byte("No heading here.\n")},
  16. "004-no-readme/x.png": {Data: []byte("png")},
  17. "Bad_Name/README.md": {Data: []byte("# Bad\n")},
  18. }
  19. list, byID, err := loadReleases(fsys, newMarkdown())
  20. if err != nil {
  21. t.Fatal(err)
  22. }
  23. if len(list) != 2 || list[0].ID != "002-second" || list[1].ID != "001-first" {
  24. t.Fatalf("got %d releases, want 002 then 001: %+v", len(list), list)
  25. }
  26. r := byID["002-second"]
  27. if r.Number != "002" || r.Title != "Second" || r.Version != "0.2.0" {
  28. t.Errorf("002: %+v", r)
  29. }
  30. if byID["001-first"].Version != "" {
  31. t.Errorf("001: version = %q, want none", byID["001-first"].Version)
  32. }
  33. if strings.Contains(string(r.HTML), "<h1") {
  34. t.Errorf("title heading repeated in body: %s", r.HTML)
  35. }
  36. }
  37. // Every record committed under pull-releases/ must load, so a malformed one
  38. // fails the build's tests instead of silently disappearing from the site.
  39. func TestEmbeddedReleasesAllLoad(t *testing.T) {
  40. relFS, err := fs.Sub(releaseFS, "pull-releases")
  41. if err != nil {
  42. t.Fatal(err)
  43. }
  44. entries, err := fs.ReadDir(relFS, ".")
  45. if err != nil {
  46. t.Fatal(err)
  47. }
  48. var dirs int
  49. for _, e := range entries {
  50. if e.IsDir() {
  51. dirs++
  52. if !releaseDir.MatchString(e.Name()) {
  53. t.Errorf("pull-releases/%s: folder name must be NNN-name", e.Name())
  54. }
  55. for _, img := range []string{"desktop.png", "mobile.png"} {
  56. if _, err := fs.Stat(relFS, e.Name()+"/"+img); err != nil {
  57. t.Errorf("pull-releases/%s: missing %s", e.Name(), img)
  58. }
  59. }
  60. }
  61. }
  62. list, _, err := loadReleases(relFS, newMarkdown())
  63. if err != nil {
  64. t.Fatal(err)
  65. }
  66. if len(list) != dirs || dirs == 0 {
  67. t.Errorf("loaded %d releases from %d folders", len(list), dirs)
  68. }
  69. }
  70. func TestReleasePages(t *testing.T) {
  71. srv, _ := newTestServer(t, nil)
  72. const id = "003-css-cache-busting"
  73. status, _, body := get(t, srv, "/releases")
  74. if status != 200 {
  75. t.Fatalf("/releases: status = %d", status)
  76. }
  77. for _, want := range []string{
  78. "<h1>Release notes</h1>",
  79. `<a href="/releases/` + id + `/">`,
  80. `<a href="/releases/001-initial/">Add Markdown blog server</a>`,
  81. "#003 · version 0.2.1",
  82. } {
  83. if !strings.Contains(body, want) {
  84. t.Errorf("/releases: missing %q", want)
  85. }
  86. }
  87. if strings.Index(body, "003-css") > strings.Index(body, "001-initial") {
  88. t.Error("/releases: newest release should be listed first")
  89. }
  90. status, _, body = get(t, srv, "/releases/"+id+"/")
  91. if status != 200 {
  92. t.Fatalf("release page: status = %d", status)
  93. }
  94. for _, want := range []string{
  95. "<h1>Add content hash to stylesheet URL; start pull-releases records</h1>",
  96. `<span class="meta">Release #003</span>`,
  97. `<img src="desktop.png" alt="Desktop">`,
  98. `<a href="/releases">`,
  99. } {
  100. if !strings.Contains(body, want) {
  101. t.Errorf("release page: missing %q", want)
  102. }
  103. }
  104. status, h, img := get(t, srv, "/releases/"+id+"/desktop.png")
  105. if status != 200 || h.Get("Content-Type") != "image/png" || !strings.HasPrefix(img, "\x89PNG") {
  106. t.Errorf("screenshot: status = %d, type = %q", status, h.Get("Content-Type"))
  107. }
  108. for _, path := range []string{
  109. "/releases/nope/",
  110. "/releases/nope",
  111. "/releases/" + id + "/README.md",
  112. "/releases/" + id + "/missing.png",
  113. "/releases/" + id + "/..%2f..%2fmain.go",
  114. "/releases/README.md",
  115. } {
  116. if status, _, _ := get(t, srv, path); status != 404 {
  117. t.Errorf("%s: status = %d, want 404", path, status)
  118. }
  119. }
  120. }
  121. func TestReleaseRedirect(t *testing.T) {
  122. srv, _ := newTestServer(t, nil)
  123. client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
  124. resp, err := client.Get(srv.URL + "/releases/001-initial")
  125. if err != nil {
  126. t.Fatal(err)
  127. }
  128. resp.Body.Close()
  129. if resp.StatusCode != 301 || resp.Header.Get("Location") != "/releases/001-initial/" {
  130. t.Errorf("status = %d, Location = %q", resp.StatusCode, resp.Header.Get("Location"))
  131. }
  132. }
  133. func TestAboutLinksToReleases(t *testing.T) {
  134. srv, _ := newTestServer(t, nil)
  135. _, _, body := get(t, srv, "/about")
  136. if !strings.Contains(body, "<dt>Release notes</dt>") || !strings.Contains(body, `<a href="/releases">`) {
  137. t.Errorf("About page has no release notes link:\n%s", body)
  138. }
  139. }