server_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package main
  2. import (
  3. "html"
  4. "net/http"
  5. "net/http/httptest"
  6. "net/url"
  7. "strings"
  8. "testing"
  9. )
  10. func resetServer(t *testing.T) {
  11. t.Helper()
  12. resetLinks(t)
  13. previousQueue, previousDomain := toSave, *domain
  14. toSave = make(chan string, 10)
  15. *domain = ""
  16. t.Cleanup(func() {
  17. toSave, *domain = previousQueue, previousDomain
  18. })
  19. }
  20. func TestShorten(t *testing.T) {
  21. link := "https://example.com/path"
  22. for _, tc := range []struct{ name, target, domain, prefix string }{
  23. {"query", "/s/?link=" + url.QueryEscape(link), "", "short.test/"},
  24. {"path", "/s/" + link, "", "short.test/"},
  25. {"configured domain", "/s/?link=" + url.QueryEscape(link), "https://short.test", "https://short.test/"},
  26. } {
  27. t.Run(tc.name, func(t *testing.T) {
  28. resetServer(t)
  29. *domain = tc.domain
  30. r := httptest.NewRequest(http.MethodGet, tc.target, nil)
  31. r.Host = "short.test"
  32. w := httptest.NewRecorder()
  33. shorten(w, r)
  34. if w.Code != http.StatusOK {
  35. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  36. }
  37. want := `value="` + tc.prefix + getHash(link) + `"`
  38. if !strings.Contains(w.Body.String(), want) || getLink(getHash(link)) != link {
  39. t.Fatalf("shortened link missing or not stored: %s", w.Body.String())
  40. }
  41. if len(toSave) != 1 {
  42. t.Fatal("shorten did not queue exactly one record")
  43. }
  44. })
  45. }
  46. }
  47. func TestShortenRejectsInvalidURL(t *testing.T) {
  48. resetServer(t)
  49. w := httptest.NewRecorder()
  50. shorten(w, httptest.NewRequest(http.MethodGet, "/s/?link="+url.QueryEscape("http://%zz"), nil))
  51. if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "Error parsing link:") {
  52. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  53. }
  54. if len(toSave) != 0 {
  55. t.Fatal("invalid URL was queued")
  56. }
  57. }
  58. func TestDecode(t *testing.T) {
  59. for _, target := range []string{"/d/abc", "/d/?link=abc", "/d/?link=" + url.QueryEscape("https://short.test/abc")} {
  60. t.Run(target, func(t *testing.T) {
  61. resetServer(t)
  62. link := "https://example.com/?a=1&b=2"
  63. links.Store("abc", link)
  64. w := httptest.NewRecorder()
  65. decode(w, httptest.NewRequest(http.MethodGet, target, nil))
  66. if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `value="`+html.EscapeString(link)+`"`) {
  67. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  68. }
  69. })
  70. }
  71. }
  72. func TestDecodeMissing(t *testing.T) {
  73. for _, target := range []string{"/d/", "/d/missing", "/d/?link=https://short.test/missing"} {
  74. t.Run(target, func(t *testing.T) {
  75. resetServer(t)
  76. w := httptest.NewRecorder()
  77. decode(w, httptest.NewRequest(http.MethodGet, target, nil))
  78. if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "Not found!") {
  79. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  80. }
  81. })
  82. }
  83. }
  84. func TestRedirectOrServe(t *testing.T) {
  85. t.Run("known ID redirects", func(t *testing.T) {
  86. resetServer(t)
  87. link := "https://example.com/path?q=one&other=two"
  88. links.Store("abc", link)
  89. w := httptest.NewRecorder()
  90. redirectOrServe(w, httptest.NewRequest(http.MethodGet, "/abc", nil))
  91. if w.Code != http.StatusMovedPermanently || w.Header().Get("Location") != link {
  92. t.Fatalf("status = %d, Location = %q", w.Code, w.Header().Get("Location"))
  93. }
  94. })
  95. for _, target := range []string{"/", "/missing"} {
  96. t.Run(target, func(t *testing.T) {
  97. resetServer(t)
  98. w := httptest.NewRecorder()
  99. redirectOrServe(w, httptest.NewRequest(http.MethodGet, target, nil))
  100. if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "Link to shorten:") {
  101. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  102. }
  103. })
  104. }
  105. }
  106. func TestReturnErrorEscapesHTML(t *testing.T) {
  107. message := `<script>alert("oops")</script>`
  108. w := httptest.NewRecorder()
  109. returnErorr(w, message)
  110. if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), html.EscapeString(message)) {
  111. t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
  112. }
  113. if strings.Contains(w.Body.String(), message) {
  114. t.Fatal("error message was rendered as executable HTML")
  115. }
  116. }