Start with core protocol tests

This commit is contained in:
Felix Geisendörfer 2013-05-07 18:50:44 +02:00
parent 11343caaae
commit 96e431cfda
2 changed files with 154 additions and 55 deletions

View File

@ -6,10 +6,13 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
var fileUrlMatcher = regexp.MustCompilePOSIX("^/([a-z0-9]{32})$")
// HandlerConfig holds the configuration for a tus Handler. // HandlerConfig holds the configuration for a tus Handler.
type HandlerConfig struct { type HandlerConfig struct {
// Dir points to a filesystem path used by tus to store uploaded and partial // Dir points to a filesystem path used by tus to store uploaded and partial
@ -57,6 +60,7 @@ type Handler struct {
sendError chan<- error sendError chan<- error
} }
// ServeHTTP processes an incoming request according to the tus protocol.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Verify that url matches BasePath // Verify that url matches BasePath
absPath := r.URL.Path absPath := r.URL.Path
@ -83,6 +87,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if matches := fileUrlMatcher.FindStringSubmatch(relPath); matches != nil {
//id := matches[1]
if r.Method == "PATCH" {
return
}
// handle invalid method
allowed := "PATCH"
w.Header().Set("Allow", allowed)
err := errors.New(r.Method + " used against file creation url. Allowed: "+allowed)
h.err(err, w, http.StatusMethodNotAllowed)
return
}
// handle unknown url // handle unknown url
err := errors.New("unknown url: " + absPath + " - does not match file pattern") err := errors.New("unknown url: " + absPath + " - does not match file pattern")
h.err(err, w, http.StatusNotFound) h.err(err, w, http.StatusNotFound)

View File

@ -1,6 +1,10 @@
// handler_test.go focuses on functional tests that verify that the Handler
// implements the tus protocol correctly.
package http package http
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -49,37 +53,45 @@ func (s *TestSetup) Teardown() {
var Protocol_FileCreation_Tests = []struct { var Protocol_FileCreation_Tests = []struct {
Description string Description string
Method string *TestRequest
Headers map[string]string
ExpectStatusCode int
ExpectHeaders map[string]string
MatchLocation *regexp.Regexp
}{ }{
{ {
Description: "Bad method", Description: "Bad method",
TestRequest: &TestRequest{
Method: "PUT", Method: "PUT",
ExpectStatusCode: http.StatusMethodNotAllowed, ExpectStatusCode: http.StatusMethodNotAllowed,
ExpectHeaders: map[string]string{"Allow": "POST"}, ExpectHeaders: map[string]string{"Allow": "POST"},
}, },
},
{ {
Description: "Missing Final-Length header", Description: "Missing Final-Length header",
TestRequest: &TestRequest{
ExpectStatusCode: http.StatusBadRequest, ExpectStatusCode: http.StatusBadRequest,
}, },
},
{ {
Description: "Invalid Final-Length header", Description: "Invalid Final-Length header",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "fuck"}, Headers: map[string]string{"Final-Length": "fuck"},
ExpectStatusCode: http.StatusBadRequest, ExpectStatusCode: http.StatusBadRequest,
}, },
},
{ {
Description: "Negative Final-Length header", Description: "Negative Final-Length header",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "-10"}, Headers: map[string]string{"Final-Length": "-10"},
ExpectStatusCode: http.StatusBadRequest, ExpectStatusCode: http.StatusBadRequest,
}, },
},
{ {
Description: "Valid Request", Description: "Valid Request",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "1024"}, Headers: map[string]string{"Final-Length": "1024"},
ExpectStatusCode: http.StatusCreated, ExpectStatusCode: http.StatusCreated,
MatchLocation: regexp.MustCompile("^http://.+" + regexp.QuoteMeta(basePath) + "[a-z0-9]{32}$"), MatchHeaders: map[string]*regexp.Regexp{
"Location": regexp.MustCompile("^http://.+" + regexp.QuoteMeta(basePath) + "[a-z0-9]{32}$"),
},
},
}, },
} }
@ -87,56 +99,125 @@ func TestProtocol_FileCreation(t *testing.T) {
setup := Setup() setup := Setup()
defer setup.Teardown() defer setup.Teardown()
Tests:
for _, test := range Protocol_FileCreation_Tests { for _, test := range Protocol_FileCreation_Tests {
t.Logf("test: %s", test.Description) t.Logf("test: %s", test.Description)
method := test.Method test.Url = setup.Server.URL + setup.Handler.config.BasePath
if method == "" { if test.Method == "" {
method = "POST" test.Method = "POST"
} }
req, err := http.NewRequest(method, setup.Server.URL+setup.Handler.config.BasePath, nil) if err := test.Do(); err != nil {
t.Error(err)
continue
}
}
}
// TestRequest is a test helper that performs and validates requests according
// to the struct fields below.
type TestRequest struct {
Method string
Url string
Headers map[string]string
ExpectStatusCode int
ExpectHeaders map[string]string
MatchHeaders map[string]*regexp.Regexp
Response *http.Response
}
func (r *TestRequest) Do() error {
req, err := http.NewRequest(r.Method, r.Url, nil)
if err != nil { if err != nil {
t.Fatal(err) return err
} }
for key, val := range test.Headers { for key, val := range r.Headers {
req.Header.Set(key, val) req.Header.Set(key, val)
} }
res, err := http.DefaultClient.Do(req) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
t.Fatal(err) return err
} }
defer res.Body.Close() defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body) if res.StatusCode != r.ExpectStatusCode {
if err != nil { return fmt.Errorf("unexpected status code: %d, expected: %d", res.StatusCode, r.ExpectStatusCode)
t.Fatal(err)
} }
if test.ExpectStatusCode != 0 && test.ExpectStatusCode != res.StatusCode { for key, val := range r.ExpectHeaders {
t.Errorf("bad status: %d, expected: %d - %s", res.StatusCode, test.ExpectStatusCode, body)
continue Tests
}
for key, val := range test.ExpectHeaders {
if got := res.Header.Get(key); got != val { if got := res.Header.Get(key); got != val {
t.Errorf("expected \"%s: %s\" header, but got: \"%s: %s\"", key, val, key, got) return fmt.Errorf("expected \"%s: %s\" header, but got: \"%s: %s\"", key, val, key, got)
continue Tests
} }
} }
if test.MatchLocation != nil { for key, matcher := range r.MatchHeaders {
location := res.Header.Get("Location") got := res.Header.Get(key)
if !test.MatchLocation.MatchString(location) { if !matcher.MatchString(got) {
t.Errorf("location \"%s\" did not match: \"%s\"", location, test.MatchLocation.String()) return fmt.Errorf("expected %s header to match: %s but got: %s", key, matcher.String(), got)
continue Tests
}
} }
} }
r.Response = res
return nil
} }
var Protocol_Core_Tests = []struct { var Protocol_Core_Tests = []struct {
}{} Description string
FinalLength int64
Requests []TestRequest
}{
{
Description: "Bad method",
FinalLength: 1024,
Requests: []TestRequest{
{
Method: "PUT",
ExpectStatusCode: http.StatusMethodNotAllowed,
ExpectHeaders: map[string]string{"Allow": "PATCH"},
},
},
},
}
func TestProtocol_Core(t *testing.T) {
setup := Setup()
defer setup.Teardown()
Tests:
for _, test := range Protocol_Core_Tests {
t.Logf("test: %s", test.Description)
location := createFile(setup, test.FinalLength)
for _, request := range test.Requests {
request.Url = location
if err := request.Do(); err != nil {
t.Error(err)
continue Tests
}
}
}
}
// createFile is a test helper that creates a new file and returns the url.
func createFile(setup *TestSetup, finalLength int64) (url string) {
req := TestRequest{
Method: "POST",
Url: setup.Server.URL + setup.Handler.config.BasePath,
Headers: map[string]string{"Final-Length": fmt.Sprintf("%d", finalLength)},
ExpectStatusCode: http.StatusCreated,
}
if err := req.Do(); err != nil {
panic(err)
}
location := req.Response.Header.Get("Location")
if location == "" {
panic("empty Location header")
}
return location
}