pip compatible server to serve Python packages out of GitHub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

326 lines
7.6 KiB

package web
import (
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"testing"
"time"
)
// These tests can probably be DRY'd up a bunch
func chHandler(ch chan string, s string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ch <- s
})
}
var methods = []string{"CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH",
"POST", "PUT", "TRACE", "OTHER"}
func TestMethods(t *testing.T) {
t.Parallel()
m := New()
ch := make(chan string, 1)
m.Connect("/", chHandler(ch, "CONNECT"))
m.Delete("/", chHandler(ch, "DELETE"))
m.Head("/", chHandler(ch, "HEAD"))
m.Get("/", chHandler(ch, "GET"))
m.Options("/", chHandler(ch, "OPTIONS"))
m.Patch("/", chHandler(ch, "PATCH"))
m.Post("/", chHandler(ch, "POST"))
m.Put("/", chHandler(ch, "PUT"))
m.Trace("/", chHandler(ch, "TRACE"))
m.Handle("/", chHandler(ch, "OTHER"))
for _, method := range methods {
r, _ := http.NewRequest(method, "/", nil)
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
select {
case val := <-ch:
if val != method {
t.Errorf("Got %q, expected %q", val, method)
}
case <-time.After(5 * time.Millisecond):
t.Errorf("Timeout waiting for method %q", method)
}
}
}
type testPattern struct{}
func (t testPattern) Prefix() string {
return ""
}
func (t testPattern) Match(r *http.Request, c *C) bool {
return true
}
func (t testPattern) Run(r *http.Request, c *C) {
}
var _ Pattern = testPattern{}
func TestPatternTypes(t *testing.T) {
t.Parallel()
m := New()
m.Get("/hello/carl", http.NotFound)
m.Get("/hello/:name", http.NotFound)
m.Get(regexp.MustCompile(`^/hello/(?P<name>.+)$`), http.NotFound)
m.Get(testPattern{}, http.NotFound)
}
type testHandler chan string
func (t testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t <- "http"
}
func (t testHandler) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {
t <- "httpc"
}
var testHandlerTable = map[string]string{
"/a": "http fn",
"/b": "http handler",
"/c": "web fn",
"/d": "web handler",
"/e": "httpc",
}
func TestHandlerTypes(t *testing.T) {
t.Parallel()
m := New()
ch := make(chan string, 1)
m.Get("/a", func(w http.ResponseWriter, r *http.Request) {
ch <- "http fn"
})
m.Get("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ch <- "http handler"
}))
m.Get("/c", func(c C, w http.ResponseWriter, r *http.Request) {
ch <- "web fn"
})
m.Get("/d", HandlerFunc(func(c C, w http.ResponseWriter, r *http.Request) {
ch <- "web handler"
}))
m.Get("/e", testHandler(ch))
for route, response := range testHandlerTable {
r, _ := http.NewRequest("GET", route, nil)
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
select {
case resp := <-ch:
if resp != response {
t.Errorf("Got %q, expected %q", resp, response)
}
case <-time.After(5 * time.Millisecond):
t.Errorf("Timeout waiting for path %q", route)
}
}
}
// The idea behind this test is to comprehensively test if routes are being
// applied in the right order. We define a special pattern type that always
// matches so long as it's greater than or equal to the global test index. By
// incrementing this index, we can invalidate all routes up to some point, and
// therefore test the routing guarantee that Goji provides: for any path P, if
// both A and B match P, and if A was inserted before B, then Goji will route to
// A before it routes to B.
var rsRoutes = []string{
"/",
"/a",
"/a",
"/b",
"/ab",
"/",
"/ba",
"/b",
"/a",
}
var rsTests = []struct {
key string
results []int
}{
{"/", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}},
{"/a", []int{0, 1, 2, 5, 5, 5, 8, 8, 8, -1}},
{"/b", []int{0, 3, 3, 3, 5, 5, 7, 7, -1, -1}},
{"/ab", []int{0, 1, 2, 4, 4, 5, 8, 8, 8, -1}},
{"/ba", []int{0, 3, 3, 3, 5, 5, 6, 7, -1, -1}},
{"/c", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}},
{"nope", []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}},
}
type rsPattern struct {
i int
counter *int
prefix string
ichan chan int
}
func (rs rsPattern) Prefix() string {
return rs.prefix
}
func (rs rsPattern) Match(_ *http.Request, _ *C) bool {
return rs.i >= *rs.counter
}
func (rs rsPattern) Run(_ *http.Request, _ *C) {
}
func (rs rsPattern) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
rs.ichan <- rs.i
}
var _ Pattern = rsPattern{}
var _ http.Handler = rsPattern{}
func TestRouteSelection(t *testing.T) {
t.Parallel()
m := New()
counter := 0
ichan := make(chan int, 1)
m.NotFound(func(w http.ResponseWriter, r *http.Request) {
ichan <- -1
})
for i, s := range rsRoutes {
pat := rsPattern{
i: i,
counter: &counter,
prefix: s,
ichan: ichan,
}
m.Get(pat, pat)
}
for _, test := range rsTests {
var n int
for counter, n = range test.results {
r, _ := http.NewRequest("GET", test.key, nil)
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
actual := <-ichan
if n != actual {
t.Errorf("Expected %q @ %d to be %d, got %d",
test.key, counter, n, actual)
}
}
}
}
func TestNotFound(t *testing.T) {
t.Parallel()
m := New()
r, _ := http.NewRequest("post", "/", nil)
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
if w.Code != 404 {
t.Errorf("Expected 404, got %d", w.Code)
}
m.NotFound(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "I'm a teapot!", http.StatusTeapot)
})
r, _ = http.NewRequest("POST", "/", nil)
w = httptest.NewRecorder()
m.ServeHTTP(w, r)
if w.Code != http.StatusTeapot {
t.Errorf("Expected a teapot, got %d", w.Code)
}
}
func TestPrefix(t *testing.T) {
t.Parallel()
m := New()
ch := make(chan string, 1)
m.Handle("/hello/*", func(w http.ResponseWriter, r *http.Request) {
ch <- r.URL.Path
})
r, _ := http.NewRequest("GET", "/hello/world", nil)
w := httptest.NewRecorder()
m.ServeHTTP(w, r)
select {
case val := <-ch:
if val != "/hello/world" {
t.Errorf("Got %q, expected /hello/world", val)
}
case <-time.After(5 * time.Millisecond):
t.Errorf("Timeout waiting for hello")
}
}
var validMethodsTable = map[string][]string{
"/hello/carl": {"DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"},
"/hello/bob": {"DELETE", "GET", "HEAD", "PATCH", "PUT"},
"/hola/carl": {"DELETE", "GET", "HEAD", "PUT"},
"/hola/bob": {"DELETE"},
"/does/not/compute": {},
}
func TestValidMethods(t *testing.T) {
t.Parallel()
m := New()
ch := make(chan []string, 1)
m.NotFound(func(c C, w http.ResponseWriter, r *http.Request) {
if c.Env == nil {
ch <- []string{}
return
}
methods, ok := c.Env[ValidMethodsKey]
if !ok {
ch <- []string{}
return
}
ch <- methods.([]string)
})
m.Get("/hello/carl", http.NotFound)
m.Post("/hello/carl", http.NotFound)
m.Head("/hello/bob", http.NotFound)
m.Get("/hello/:name", http.NotFound)
m.Put("/hello/:name", http.NotFound)
m.Patch("/hello/:name", http.NotFound)
m.Get("/:greet/carl", http.NotFound)
m.Put("/:greet/carl", http.NotFound)
m.Delete("/:greet/:anyone", http.NotFound)
for path, eMethods := range validMethodsTable {
r, _ := http.NewRequest("BOGUS", path, nil)
m.ServeHTTP(httptest.NewRecorder(), r)
aMethods := <-ch
if !reflect.DeepEqual(eMethods, aMethods) {
t.Errorf("For %q, expected %v, got %v", path, eMethods,
aMethods)
}
}
// This should also work when c.Env has already been initalized
m.Use(func(c *C, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Env = make(map[interface{}]interface{})
h.ServeHTTP(w, r)
})
})
for path, eMethods := range validMethodsTable {
r, _ := http.NewRequest("BOGUS", path, nil)
m.ServeHTTP(httptest.NewRecorder(), r)
aMethods := <-ch
if !reflect.DeepEqual(eMethods, aMethods) {
t.Errorf("For %q, expected %v, got %v", path, eMethods,
aMethods)
}
}
}