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"
"os"
"path"
"regexp"
"strconv"
"strings"
)
var fileUrlMatcher = regexp.MustCompilePOSIX("^/([a-z0-9]{32})$")
// HandlerConfig holds the configuration for a tus Handler.
type HandlerConfig struct {
// Dir points to a filesystem path used by tus to store uploaded and partial
@ -57,6 +60,7 @@ type Handler struct {
sendError chan<- error
}
// ServeHTTP processes an incoming request according to the tus protocol.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Verify that url matches BasePath
absPath := r.URL.Path
@ -83,6 +87,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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
err := errors.New("unknown url: " + absPath + " - does not match file pattern")
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
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -48,38 +52,46 @@ func (s *TestSetup) Teardown() {
}
var Protocol_FileCreation_Tests = []struct {
Description string
Method string
Headers map[string]string
ExpectStatusCode int
ExpectHeaders map[string]string
MatchLocation *regexp.Regexp
Description string
*TestRequest
}{
{
Description: "Bad method",
Method: "PUT",
ExpectStatusCode: http.StatusMethodNotAllowed,
ExpectHeaders: map[string]string{"Allow": "POST"},
Description: "Bad method",
TestRequest: &TestRequest{
Method: "PUT",
ExpectStatusCode: http.StatusMethodNotAllowed,
ExpectHeaders: map[string]string{"Allow": "POST"},
},
},
{
Description: "Missing Final-Length header",
ExpectStatusCode: http.StatusBadRequest,
Description: "Missing Final-Length header",
TestRequest: &TestRequest{
ExpectStatusCode: http.StatusBadRequest,
},
},
{
Description: "Invalid Final-Length header",
Headers: map[string]string{"Final-Length": "fuck"},
ExpectStatusCode: http.StatusBadRequest,
Description: "Invalid Final-Length header",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "fuck"},
ExpectStatusCode: http.StatusBadRequest,
},
},
{
Description: "Negative Final-Length header",
Headers: map[string]string{"Final-Length": "-10"},
ExpectStatusCode: http.StatusBadRequest,
Description: "Negative Final-Length header",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "-10"},
ExpectStatusCode: http.StatusBadRequest,
},
},
{
Description: "Valid Request",
Headers: map[string]string{"Final-Length": "1024"},
ExpectStatusCode: http.StatusCreated,
MatchLocation: regexp.MustCompile("^http://.+" + regexp.QuoteMeta(basePath) + "[a-z0-9]{32}$"),
Description: "Valid Request",
TestRequest: &TestRequest{
Headers: map[string]string{"Final-Length": "1024"},
ExpectStatusCode: http.StatusCreated,
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()
defer setup.Teardown()
Tests:
for _, test := range Protocol_FileCreation_Tests {
t.Logf("test: %s", test.Description)
method := test.Method
if method == "" {
method = "POST"
test.Url = setup.Server.URL + setup.Handler.config.BasePath
if test.Method == "" {
test.Method = "POST"
}
req, err := http.NewRequest(method, setup.Server.URL+setup.Handler.config.BasePath, nil)
if err != nil {
t.Fatal(err)
if err := test.Do(); err != nil {
t.Error(err)
continue
}
}
}
for key, val := range test.Headers {
req.Header.Set(key, val)
// 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 {
return err
}
for key, val := range r.Headers {
req.Header.Set(key, val)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != r.ExpectStatusCode {
return fmt.Errorf("unexpected status code: %d, expected: %d", res.StatusCode, r.ExpectStatusCode)
}
for key, val := range r.ExpectHeaders {
if got := res.Header.Get(key); got != val {
return fmt.Errorf("expected \"%s: %s\" header, but got: \"%s: %s\"", key, val, key, got)
}
}
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
for key, matcher := range r.MatchHeaders {
got := res.Header.Get(key)
if !matcher.MatchString(got) {
return fmt.Errorf("expected %s header to match: %s but got: %s", key, matcher.String(), got)
}
defer res.Body.Close()
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
r.Response = res
if test.ExpectStatusCode != 0 && test.ExpectStatusCode != res.StatusCode {
t.Errorf("bad status: %d, expected: %d - %s", res.StatusCode, test.ExpectStatusCode, body)
continue Tests
}
return nil
}
for key, val := range test.ExpectHeaders {
if got := res.Header.Get(key); got != val {
t.Errorf("expected \"%s: %s\" header, but got: \"%s: %s\"", key, val, key, got)
continue Tests
}
}
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"},
},
},
},
}
if test.MatchLocation != nil {
location := res.Header.Get("Location")
if !test.MatchLocation.MatchString(location) {
t.Errorf("location \"%s\" did not match: \"%s\"", location, test.MatchLocation.String())
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
}
}
}
}
var Protocol_Core_Tests = []struct {
}{}
// 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
}