package main
import (
"html"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func resetServer(t *testing.T) {
t.Helper()
resetLinks(t)
previousQueue, previousDomain := toSave, *domain
toSave = make(chan string, 10)
*domain = ""
t.Cleanup(func() {
toSave, *domain = previousQueue, previousDomain
})
}
func TestShorten(t *testing.T) {
link := "https://example.com/path"
for _, tc := range []struct{ name, target, domain, prefix string }{
{"query", "/s/?link=" + url.QueryEscape(link), "", "short.test/"},
{"path", "/s/" + link, "", "short.test/"},
{"configured domain", "/s/?link=" + url.QueryEscape(link), "https://short.test", "https://short.test/"},
} {
t.Run(tc.name, func(t *testing.T) {
resetServer(t)
*domain = tc.domain
r := httptest.NewRequest(http.MethodGet, tc.target, nil)
r.Host = "short.test"
w := httptest.NewRecorder()
shorten(w, r)
if w.Code != http.StatusOK {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
want := `value="` + tc.prefix + getHash(link) + `"`
if !strings.Contains(w.Body.String(), want) || getLink(getHash(link)) != link {
t.Fatalf("shortened link missing or not stored: %s", w.Body.String())
}
if len(toSave) != 1 {
t.Fatal("shorten did not queue exactly one record")
}
})
}
}
func TestShortenRejectsInvalidURL(t *testing.T) {
resetServer(t)
w := httptest.NewRecorder()
shorten(w, httptest.NewRequest(http.MethodGet, "/s/?link="+url.QueryEscape("http://%zz"), nil))
if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "Error parsing link:") {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
if len(toSave) != 0 {
t.Fatal("invalid URL was queued")
}
}
func TestDecode(t *testing.T) {
for _, target := range []string{"/d/abc", "/d/?link=abc", "/d/?link=" + url.QueryEscape("https://short.test/abc")} {
t.Run(target, func(t *testing.T) {
resetServer(t)
link := "https://example.com/?a=1&b=2"
links.Store("abc", link)
w := httptest.NewRecorder()
decode(w, httptest.NewRequest(http.MethodGet, target, nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), `value="`+html.EscapeString(link)+`"`) {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
})
}
}
func TestDecodeMissing(t *testing.T) {
for _, target := range []string{"/d/", "/d/missing", "/d/?link=https://short.test/missing"} {
t.Run(target, func(t *testing.T) {
resetServer(t)
w := httptest.NewRecorder()
decode(w, httptest.NewRequest(http.MethodGet, target, nil))
if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "Not found!") {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
})
}
}
func TestRedirectOrServe(t *testing.T) {
t.Run("known ID redirects", func(t *testing.T) {
resetServer(t)
link := "https://example.com/path?q=one&other=two"
links.Store("abc", link)
w := httptest.NewRecorder()
redirectOrServe(w, httptest.NewRequest(http.MethodGet, "/abc", nil))
if w.Code != http.StatusMovedPermanently || w.Header().Get("Location") != link {
t.Fatalf("status = %d, Location = %q", w.Code, w.Header().Get("Location"))
}
})
for _, target := range []string{"/", "/missing"} {
t.Run(target, func(t *testing.T) {
resetServer(t)
w := httptest.NewRecorder()
redirectOrServe(w, httptest.NewRequest(http.MethodGet, target, nil))
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "Link to shorten:") {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
})
}
}
func TestReturnErrorEscapesHTML(t *testing.T) {
message := ``
w := httptest.NewRecorder()
returnErorr(w, message)
if w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), html.EscapeString(message)) {
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
}
if strings.Contains(w.Body.String(), message) {
t.Fatal("error message was rendered as executable HTML")
}
}