| // Package alice implements a middleware chaining solution. |
| package alice |
| |
| import ( |
| "net/http" |
| "net/http/httptest" |
| "reflect" |
| "testing" |
| ) |
| |
| // A constructor for middleware |
| // that writes its own "tag" into the RW and does nothing else. |
| // Useful in checking if a chain is behaving in the right order. |
| func tagMiddleware(tag string) Constructor { |
| return func(h http.Handler) http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.Write([]byte(tag)) |
| h.ServeHTTP(w, r) |
| }) |
| } |
| } |
| |
| // Not recommended (https://golang.org/pkg/reflect/#Value.Pointer), |
| // but the best we can do. |
| func funcsEqual(f1, f2 interface{}) bool { |
| val1 := reflect.ValueOf(f1) |
| val2 := reflect.ValueOf(f2) |
| return val1.Pointer() == val2.Pointer() |
| } |
| |
| var testApp = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.Write([]byte("app\n")) |
| }) |
| |
| func TestNew(t *testing.T) { |
| c1 := func(h http.Handler) http.Handler { |
| return nil |
| } |
| |
| c2 := func(h http.Handler) http.Handler { |
| return http.StripPrefix("potato", nil) |
| } |
| |
| slice := []Constructor{c1, c2} |
| |
| chain := New(slice...) |
| for k := range slice { |
| if !funcsEqual(chain.constructors[k], slice[k]) { |
| t.Error("New does not add constructors correctly") |
| } |
| } |
| } |
| |
| func TestThenWorksWithNoMiddleware(t *testing.T) { |
| if !funcsEqual(New().Then(testApp), testApp) { |
| t.Error("Then does not work with no middleware") |
| } |
| } |
| |
| func TestThenTreatsNilAsDefaultServeMux(t *testing.T) { |
| if New().Then(nil) != http.DefaultServeMux { |
| t.Error("Then does not treat nil as DefaultServeMux") |
| } |
| } |
| |
| func TestThenFuncTreatsNilAsDefaultServeMux(t *testing.T) { |
| if New().ThenFunc(nil) != http.DefaultServeMux { |
| t.Error("ThenFunc does not treat nil as DefaultServeMux") |
| } |
| } |
| |
| func TestThenFuncConstructsHandlerFunc(t *testing.T) { |
| fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| w.WriteHeader(200) |
| }) |
| chained := New().ThenFunc(fn) |
| rec := httptest.NewRecorder() |
| |
| chained.ServeHTTP(rec, (*http.Request)(nil)) |
| |
| if reflect.TypeOf(chained) != reflect.TypeOf((http.HandlerFunc)(nil)) { |
| t.Error("ThenFunc does not construct HandlerFunc") |
| } |
| } |
| |
| func TestThenOrdersHandlersCorrectly(t *testing.T) { |
| t1 := tagMiddleware("t1\n") |
| t2 := tagMiddleware("t2\n") |
| t3 := tagMiddleware("t3\n") |
| |
| chained := New(t1, t2, t3).Then(testApp) |
| |
| w := httptest.NewRecorder() |
| r, err := http.NewRequest("GET", "/", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| chained.ServeHTTP(w, r) |
| |
| if w.Body.String() != "t1\nt2\nt3\napp\n" { |
| t.Error("Then does not order handlers correctly") |
| } |
| } |
| |
| func TestAppendAddsHandlersCorrectly(t *testing.T) { |
| chain := New(tagMiddleware("t1\n"), tagMiddleware("t2\n")) |
| newChain := chain.Append(tagMiddleware("t3\n"), tagMiddleware("t4\n")) |
| |
| if len(chain.constructors) != 2 { |
| t.Error("chain should have 2 constructors") |
| } |
| if len(newChain.constructors) != 4 { |
| t.Error("newChain should have 4 constructors") |
| } |
| |
| chained := newChain.Then(testApp) |
| |
| w := httptest.NewRecorder() |
| r, err := http.NewRequest("GET", "/", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| chained.ServeHTTP(w, r) |
| |
| if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" { |
| t.Error("Append does not add handlers correctly") |
| } |
| } |
| |
| func TestAppendRespectsImmutability(t *testing.T) { |
| chain := New(tagMiddleware("")) |
| newChain := chain.Append(tagMiddleware("")) |
| |
| if &chain.constructors[0] == &newChain.constructors[0] { |
| t.Error("Apppend does not respect immutability") |
| } |
| } |
| |
| func TestExtendAddsHandlersCorrectly(t *testing.T) { |
| chain1 := New(tagMiddleware("t1\n"), tagMiddleware("t2\n")) |
| chain2 := New(tagMiddleware("t3\n"), tagMiddleware("t4\n")) |
| newChain := chain1.Extend(chain2) |
| |
| if len(chain1.constructors) != 2 { |
| t.Error("chain1 should contain 2 constructors") |
| } |
| if len(chain2.constructors) != 2 { |
| t.Error("chain2 should contain 2 constructors") |
| } |
| if len(newChain.constructors) != 4 { |
| t.Error("newChain should contain 4 constructors") |
| } |
| |
| chained := newChain.Then(testApp) |
| |
| w := httptest.NewRecorder() |
| r, err := http.NewRequest("GET", "/", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| chained.ServeHTTP(w, r) |
| |
| if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" { |
| t.Error("Extend does not add handlers in correctly") |
| } |
| } |
| |
| func TestExtendRespectsImmutability(t *testing.T) { |
| chain := New(tagMiddleware("")) |
| newChain := chain.Extend(New(tagMiddleware(""))) |
| |
| if &chain.constructors[0] == &newChain.constructors[0] { |
| t.Error("Extend does not respect immutability") |
| } |
| } |