helpers_test.go
⎇
Raw
1package handler
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "net/url"
8 "testing"
9
10 "github.com/go-chi/chi/v5"
11)
12
13// A flash must be shown exactly once: reading it also expires the cookie.
14func TestConsumeFlashClearsCookie(t *testing.T) {
15 w := httptest.NewRecorder()
16 setFlash(w, "success", "Preset created.")
17
18 cookie := w.Result().Cookies()[0]
19 r := httptest.NewRequest("GET", "/settings", nil)
20 r.AddCookie(cookie)
21
22 next := httptest.NewRecorder()
23 flash := consumeFlash(next, r)
24 if flash == nil {
25 t.Fatal("no flash read back")
26 }
27 if flash.Kind != "success" || flash.Message != "Preset created." {
28 t.Errorf("flash = %+v", flash)
29 }
30
31 cleared := next.Result().Cookies()
32 if len(cleared) != 1 || cleared[0].MaxAge >= 0 {
33 t.Errorf("flash cookie was not expired: %+v", cleared)
34 }
35}
36
37// The cookie is client-editable, so an arbitrary kind must not reach the
38// banner's class name.
39func TestConsumeFlashClampsKind(t *testing.T) {
40 cases := map[string]string{
41 "success": "success",
42 "error": "error",
43 "evil\" onload=alert1": "error",
44 }
45 for kind, want := range cases {
46 r := httptest.NewRequest("GET", "/", nil)
47 r.AddCookie(&http.Cookie{Name: flashCookie, Value: url.QueryEscape(kind + "|hello")})
48 flash := consumeFlash(httptest.NewRecorder(), r)
49 if flash == nil {
50 t.Fatalf("kind %q: no flash read back", kind)
51 }
52 if flash.Kind != want {
53 t.Errorf("kind %q became %q, want %q", kind, flash.Kind, want)
54 }
55 }
56}
57
58func TestConsumeFlashIgnoresMalformedValues(t *testing.T) {
59 for _, value := range []string{"", "no-separator", "%zz"} {
60 r := httptest.NewRequest("GET", "/", nil)
61 r.AddCookie(&http.Cookie{Name: flashCookie, Value: value})
62 if flash := consumeFlash(httptest.NewRecorder(), r); flash != nil {
63 t.Errorf("value %q produced a flash: %+v", value, flash)
64 }
65 }
66 // No cookie at all.
67 if flash := consumeFlash(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)); flash != nil {
68 t.Errorf("missing cookie produced a flash: %+v", flash)
69 }
70}
71
72// An explicit ?sort= is remembered, and a request without one restores the last
73// choice — the auto-refresh reloads these pages without the query string.
74func TestSortFromRequest(t *testing.T) {
75 w := httptest.NewRecorder()
76 r := httptest.NewRequest("GET", "/library?sort=title", nil)
77 if got := sortFromRequest(w, r, "library_sort", "date"); got != "title" {
78 t.Fatalf("explicit sort = %q, want title", got)
79 }
80 cookies := w.Result().Cookies()
81 if len(cookies) != 1 || cookies[0].Name != "library_sort" || cookies[0].Value != "title" {
82 t.Fatalf("sort was not remembered: %+v", cookies)
83 }
84
85 next := httptest.NewRequest("GET", "/library", nil)
86 next.AddCookie(cookies[0])
87 if got := sortFromRequest(httptest.NewRecorder(), next, "library_sort", "date"); got != "title" {
88 t.Errorf("restored sort = %q, want title", got)
89 }
90
91 // With neither query nor cookie, the caller's default applies.
92 bare := httptest.NewRequest("GET", "/library", nil)
93 if got := sortFromRequest(httptest.NewRecorder(), bare, "library_sort", "date"); got != "date" {
94 t.Errorf("default sort = %q, want date", got)
95 }
96}
97
98// The wildcard arrives still percent-encoded. '+' is a literal plus in a path,
99// so an item directory named "a+b" must round-trip.
100func TestNormalizeRelPath(t *testing.T) {
101 cases := map[string]string{
102 "folder/item": "folder/item",
103 "/folder/item/": "folder/item",
104 "a%2Bb": "a+b",
105 "a+b": "a+b",
106 "spaced%20name": "spaced name",
107 "%E6%97%A5%E6%9C%AC": "日本",
108 }
109 for raw, want := range cases {
110 r := httptest.NewRequest("GET", "/library/item/"+raw, nil)
111 ctx := chi.NewRouteContext()
112 ctx.URLParams.Add("*", raw)
113 r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
114 if got := normalizeRelPath(r); got != want {
115 t.Errorf("normalizeRelPath(%q) = %q, want %q", raw, got, want)
116 }
117 }
118}
119
120func TestParseIDRejectsNonNumeric(t *testing.T) {
121 r := httptest.NewRequest("POST", "/queue/abc/delete", nil)
122 ctx := chi.NewRouteContext()
123 ctx.URLParams.Add("id", "abc")
124 r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
125
126 w := httptest.NewRecorder()
127 if _, ok := parseID(w, r); ok {
128 t.Error("parseID accepted a non-numeric id")
129 }
130 if w.Code != http.StatusBadRequest {
131 t.Errorf("status = %d, want 400", w.Code)
132 }
133}
134
135func TestFormatDuration(t *testing.T) {
136 cases := map[int]string{
137 -1: "--:--",
138 0: "--:--",
139 59: "0:59",
140 61: "1:01",
141 3661: "1:01:01",
142 }
143 for seconds, want := range cases {
144 if got := formatDuration(seconds); got != want {
145 t.Errorf("formatDuration(%d) = %q, want %q", seconds, got, want)
146 }
147 }
148}
149