Start with core protocol tests
This commit is contained in:
parent
11343caaae
commit
96e431cfda
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -48,38 +52,46 @@ 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",
|
||||||
Method: "PUT",
|
TestRequest: &TestRequest{
|
||||||
ExpectStatusCode: http.StatusMethodNotAllowed,
|
Method: "PUT",
|
||||||
ExpectHeaders: map[string]string{"Allow": "POST"},
|
ExpectStatusCode: http.StatusMethodNotAllowed,
|
||||||
|
ExpectHeaders: map[string]string{"Allow": "POST"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Missing Final-Length header",
|
Description: "Missing Final-Length header",
|
||||||
ExpectStatusCode: http.StatusBadRequest,
|
TestRequest: &TestRequest{
|
||||||
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Invalid Final-Length header",
|
Description: "Invalid Final-Length header",
|
||||||
Headers: map[string]string{"Final-Length": "fuck"},
|
TestRequest: &TestRequest{
|
||||||
ExpectStatusCode: http.StatusBadRequest,
|
Headers: map[string]string{"Final-Length": "fuck"},
|
||||||
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Negative Final-Length header",
|
Description: "Negative Final-Length header",
|
||||||
Headers: map[string]string{"Final-Length": "-10"},
|
TestRequest: &TestRequest{
|
||||||
ExpectStatusCode: http.StatusBadRequest,
|
Headers: map[string]string{"Final-Length": "-10"},
|
||||||
|
ExpectStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Description: "Valid Request",
|
Description: "Valid Request",
|
||||||
Headers: map[string]string{"Final-Length": "1024"},
|
TestRequest: &TestRequest{
|
||||||
ExpectStatusCode: http.StatusCreated,
|
Headers: map[string]string{"Final-Length": "1024"},
|
||||||
MatchLocation: regexp.MustCompile("^http://.+" + regexp.QuoteMeta(basePath) + "[a-z0-9]{32}$"),
|
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()
|
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 {
|
||||||
if err != nil {
|
t.Error(err)
|
||||||
t.Fatal(err)
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for key, val := range test.Headers {
|
// TestRequest is a test helper that performs and validates requests according
|
||||||
req.Header.Set(key, val)
|
// 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)
|
for key, matcher := range r.MatchHeaders {
|
||||||
if err != nil {
|
got := res.Header.Get(key)
|
||||||
t.Fatal(err)
|
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)
|
r.Response = res
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.ExpectStatusCode != 0 && test.ExpectStatusCode != res.StatusCode {
|
return nil
|
||||||
t.Errorf("bad status: %d, expected: %d - %s", res.StatusCode, test.ExpectStatusCode, body)
|
}
|
||||||
continue Tests
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range test.ExpectHeaders {
|
var Protocol_Core_Tests = []struct {
|
||||||
if got := res.Header.Get(key); got != val {
|
Description string
|
||||||
t.Errorf("expected \"%s: %s\" header, but got: \"%s: %s\"", key, val, key, got)
|
FinalLength int64
|
||||||
continue Tests
|
Requests []TestRequest
|
||||||
}
|
}{
|
||||||
}
|
{
|
||||||
|
Description: "Bad method",
|
||||||
|
FinalLength: 1024,
|
||||||
|
Requests: []TestRequest{
|
||||||
|
{
|
||||||
|
Method: "PUT",
|
||||||
|
ExpectStatusCode: http.StatusMethodNotAllowed,
|
||||||
|
ExpectHeaders: map[string]string{"Allow": "PATCH"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if test.MatchLocation != nil {
|
func TestProtocol_Core(t *testing.T) {
|
||||||
location := res.Header.Get("Location")
|
setup := Setup()
|
||||||
if !test.MatchLocation.MatchString(location) {
|
defer setup.Teardown()
|
||||||
t.Errorf("location \"%s\" did not match: \"%s\"", location, test.MatchLocation.String())
|
|
||||||
|
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
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue