From 96e431cfda505a6cd95daba9007970c6367985bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisendo=CC=88rfer?= Date: Tue, 7 May 2013 18:50:44 +0200 Subject: [PATCH] Start with core protocol tests --- src/http/handler.go | 18 ++++ src/http/handler_test.go | 191 ++++++++++++++++++++++++++++----------- 2 files changed, 154 insertions(+), 55 deletions(-) diff --git a/src/http/handler.go b/src/http/handler.go index 1a09f61..17709ef 100644 --- a/src/http/handler.go +++ b/src/http/handler.go @@ -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) diff --git a/src/http/handler_test.go b/src/http/handler_test.go index a252e5d..ed869b8 100644 --- a/src/http/handler_test.go +++ b/src/http/handler_test.go @@ -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 +}