1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
package server
import (
"net/http"
"net/http/httptest"
"rss_parrot/shared"
"strings"
"testing"
)
type fakeGroup struct {
prefix string
defs []handlerDef
authed *[]string
}
func (g fakeGroup) Prefix() string { return g.prefix }
func (g fakeGroup) GroupDefs() []handlerDef { return g.defs }
func (g fakeGroup) AuthMW() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
*g.authed = append(*g.authed, r.URL.Path)
next.ServeHTTP(w, r)
})
}
}
// echo answers with its name and the path values it was given.
func echo(name string, keys ...string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
out := name
for _, k := range keys {
out += " " + k + "=" + r.PathValue(k)
}
w.Write([]byte(out))
}
}
func TestMuxRouting(t *testing.T) {
var authed []string
groups := []IHandlerGroup{
fakeGroup{"/api", []handlerDef{
{"DELETE", "/accounts/{account}", echo("delete", "account")},
}, &authed},
fakeGroup{"", []handlerDef{
{"GET", "/u/{user}/status/{id}", echo("status", "user", "id")},
{"POST", "/u/{user}/inbox", echo("inbox", "user")},
}, &authed},
fakeGroup{"/web", []handlerDef{
{"GET", "/about", echo("about")},
{"GET", rootPlacholder, echo("root")},
{"GET", notFoundPlacholder, func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "custom 404", http.StatusNotFound)
}},
}, &authed},
}
router := trimSlashHandler(NewMux(groups, shared.NewLogger(&strings.Builder{}, "error")))
for _, tc := range []struct {
method, path string
status int
body string
}{
{"DELETE", "/api/accounts/foo.example", 200, "delete account=foo.example"},
{"GET", "/u/birb/status/42", 200, "status user=birb id=42"},
{"GET", "/u/birb/status/42/", 200, "status user=birb id=42"},
{"POST", "/u/birb/inbox", 200, "inbox user=birb"},
{"GET", "/web/about", 200, "about"},
{"GET", "/", 200, "root"},
// HEAD is a GET without the body.
{"HEAD", "/web/about", 200, ""},
// A known path asked with the wrong method.
{"POST", "/web/about", 405, ""},
{"OPTIONS", "/api/accounts/foo.example", 405, ""},
// Any GET is caught by the static files, even on a POST-only path.
{"GET", "/u/birb/inbox", 404, "custom 404"},
// Unknown paths go to the static files, and from there to the 404 page.
{"GET", "/nope", 404, "custom 404"},
{"GET", "/assets/nope.css", 404, "custom 404"},
{"POST", "/nope", 405, ""},
} {
rec := httptest.NewRecorder()
router.ServeHTTP(rec, httptest.NewRequest(tc.method, tc.path, nil))
if rec.Code != tc.status {
t.Errorf("%s %s: status %d, want %d", tc.method, tc.path, rec.Code, tc.status)
continue
}
if tc.body != "" && !strings.Contains(rec.Body.String(), tc.body) {
t.Errorf("%s %s: body %q, want it to contain %q", tc.method, tc.path, rec.Body.String(), tc.body)
}
if tc.status == 200 && tc.path != "/" && rec.Header().Get(strCacheControlHdr) != "no-cache, no-store, must-revalidate" {
t.Errorf("%s %s: Cache-Control %q, want no-cache", tc.method, tc.path, rec.Header().Get(strCacheControlHdr))
}
}
// The root route is outside the groups and skips their middleware.
for _, p := range authed {
if p == "/" {
t.Errorf("auth middleware ran for /")
}
}
if len(authed) == 0 {
t.Errorf("auth middleware never ran")
}
}
|