diff --git a/go.mod b/go.mod index 72e79f7d80..c220dcc223 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6 github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368 github.com/jessevdk/go-flags v1.4.0 - github.com/jsteenb2/testttp v0.0.0-20191106182320-3a9029951f41 github.com/jsternberg/zap-logfmt v1.2.0 github.com/julienschmidt/httprouter v1.2.0 github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef diff --git a/go.sum b/go.sum index 9ad9de5e28..f1460462b6 100644 --- a/go.sum +++ b/go.sum @@ -239,10 +239,10 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/changelog v1.1.0 h1:HXhmLZDrbuC+Ca5YX7g8B8cH5DmJpaOjd844d9Y7aTQ= github.com/influxdata/changelog v1.1.0/go.mod h1:uzpGWE/qehT8L426YuXwpMQub+a63vIINhIeEI9mnSM= -github.com/influxdata/flux v0.54.0 h1:DjAkGoPkgHLDPEn1jSuOpsH4QgcjmSBkRuxSAaQCj1Q= -github.com/influxdata/flux v0.54.0/go.mod h1:ZFf4F0c8ACFP/5BkfCwk9I/vUwcByr0vMdLxwgOk57E= github.com/influxdata/cron v0.0.0-20191112133922-ad5847cfab62 h1:YipnPuvJKPAzyBhr7eXIMA49L2Eooga/NSytWdLLI8U= github.com/influxdata/cron v0.0.0-20191112133922-ad5847cfab62/go.mod h1:XabtPPW2qsCg0tl+kjaPU+cFS+CjQXEXbT1VJvHT4og= +github.com/influxdata/flux v0.54.0 h1:DjAkGoPkgHLDPEn1jSuOpsH4QgcjmSBkRuxSAaQCj1Q= +github.com/influxdata/flux v0.54.0/go.mod h1:ZFf4F0c8ACFP/5BkfCwk9I/vUwcByr0vMdLxwgOk57E= github.com/influxdata/goreleaser v0.97.0-influx h1:jT5OrcW7WfS0e2QxfwmTBjhLvpIC9CDLRhNgZJyhj8s= github.com/influxdata/goreleaser v0.97.0-influx/go.mod h1:MnjA0e0Uq6ISqjG1WxxMAl+3VS1QYjILSWVnMYDxasE= github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6 h1:CFx+pP90q/qg3spoiZjf8donE4WpAdjeJfPOcoNqkWo= @@ -262,8 +262,6 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jsteenb2/testttp v0.0.0-20191106182320-3a9029951f41 h1:x4CmjEFDJd2LVNwfd35xxBsNHOySHAeTzdZuwV/CuXs= -github.com/jsteenb2/testttp v0.0.0-20191106182320-3a9029951f41/go.mod h1:Bpl/IMLYUekm9vMu/EA8SwzUtmH+rGBPKeY4xLswom8= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= diff --git a/http/pkger_http_server_test.go b/http/pkger_http_server_test.go index 75faf12f19..371906608b 100644 --- a/http/pkger_http_server_test.go +++ b/http/pkger_http_server_test.go @@ -12,8 +12,8 @@ import ( "github.com/influxdata/influxdb" fluxTTP "github.com/influxdata/influxdb/http" "github.com/influxdata/influxdb/mock" + "github.com/influxdata/influxdb/pkg/testttp" "github.com/influxdata/influxdb/pkger" - "github.com/jsteenb2/testttp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" diff --git a/pkg/testttp/http.go b/pkg/testttp/http.go new file mode 100644 index 0000000000..853253aa4f --- /dev/null +++ b/pkg/testttp/http.go @@ -0,0 +1,127 @@ +package testttp + +import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "testing" +) + +// Req is a request builder. +type Req struct { + addr string + method string + body io.Reader + headers []string +} + +// HTTP runs creates a request for an http call. +func HTTP(method, addr string, body io.Reader) *Req { + return &Req{ + addr: addr, + method: method, + body: body, + } +} + +// Delete creates a DELETE request. +func Delete(addr string) *Req { + return HTTP(http.MethodDelete, addr, nil) +} + +// Get creates a GET request. +func Get(addr string) *Req { + return HTTP(http.MethodGet, addr, nil) +} + +// Patch creates a PATCH request. +func Patch(addr string, body io.Reader) *Req { + return HTTP(http.MethodPatch, addr, body) +} + +// Post creates a POST request. +func Post(addr string, body io.Reader) *Req { + return HTTP(http.MethodPost, addr, body) +} + +// Put creates a PUT request. +func Put(addr string, body io.Reader) *Req { + return HTTP(http.MethodPut, addr, body) +} + +// Headers allows the user to set headers on the http request. +func (r *Req) Headers(k, v string, rest ...string) *Req { + r.headers = append(r.headers, k, v) + r.headers = append(r.headers, rest...) + return r +} + +// Do runs the request against the provided handler. +func (r *Req) Do(handler http.Handler) *Resp { + req := httptest.NewRequest(r.method, r.addr, r.body) + rec := httptest.NewRecorder() + + for i := 0; i < len(r.headers); i += 2 { + if i+1 >= len(r.headers) { + break + } + k, v := r.headers[i], r.headers[i+1] + req.Header.Add(k, v) + } + + handler.ServeHTTP(rec, req) + + return &Resp{ + Req: req, + Rec: rec, + } +} + +// Resp is a http recorder wrapper. +type Resp struct { + Req *http.Request + Rec *httptest.ResponseRecorder +} + +// Expect allows the assertions against the raw Resp. +func (r *Resp) Expect(fn func(*Resp)) *Resp { + fn(r) + return r +} + +// ExpectStatus compares the expected status code against the recorded status code. +func (r *Resp) ExpectStatus(t *testing.T, code int) *Resp { + t.Helper() + + if r.Rec.Code != code { + t.Errorf("unexpected status code: expected=%d got=%d", code, r.Rec.Code) + } + return r +} + +// ExpectBody provides an assertion against the recorder body. +func (r *Resp) ExpectBody(fn func(*bytes.Buffer)) *Resp { + fn(r.Rec.Body) + return r +} + +// ExpectHeader asserts that the header is in the recorder. +func (r *Resp) ExpectHeader(t *testing.T, k, v string) *Resp { + t.Helper() + + vals, ok := r.Rec.Header()[k] + if !ok { + t.Errorf("did not find expected header: %q", k) + return r + } + + for _, vv := range vals { + if vv == v { + return r + } + } + t.Errorf("did not find expected value for header %q; got: %v", k, vals) + + return r +} diff --git a/pkg/testttp/http_test.go b/pkg/testttp/http_test.go new file mode 100644 index 0000000000..63e1c656f9 --- /dev/null +++ b/pkg/testttp/http_test.go @@ -0,0 +1,121 @@ +package testttp_test + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "strings" + "testing" + + "github.com/influxdata/influxdb/pkg/testttp" +) + +func TestHTTP(t *testing.T) { + svr := newMux() + t.Run("Get", func(t *testing.T) { + testttp.Get("/"). + Do(svr). + ExpectStatus(t, http.StatusOK). + ExpectBody(assertBody(t, http.MethodGet)) + }) + + t.Run("Post", func(t *testing.T) { + testttp.Post("/", nil).Do(svr). + ExpectStatus(t, http.StatusCreated). + ExpectBody(assertBody(t, http.MethodPost)) + }) + + t.Run("Put", func(t *testing.T) { + testttp.Put("/", nil). + Do(svr). + ExpectStatus(t, http.StatusAccepted). + ExpectBody(assertBody(t, http.MethodPut)) + }) + + t.Run("Patch", func(t *testing.T) { + testttp.Patch("/", nil). + Do(svr). + ExpectStatus(t, http.StatusPartialContent). + ExpectBody(assertBody(t, http.MethodPatch)) + }) + + t.Run("Delete", func(t *testing.T) { + testttp.Delete("/"). + Do(svr). + ExpectStatus(t, http.StatusNoContent) + }) + + t.Run("Headers", func(t *testing.T) { + testttp.Post("/", strings.NewReader(`a: foo`)). + Headers("Content-Type", "text/yml"). + Do(svr). + Expect(func(resp *testttp.Resp) { + equals(t, "text/yml", resp.Req.Header.Get("Content-Type")) + }) + }) +} + +type foo struct { + Name, Thing, Method string +} + +func newMux() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + writeFn(w, req.Method, http.StatusOK) + case http.MethodPost: + writeFn(w, req.Method, http.StatusCreated) + case http.MethodPut: + writeFn(w, req.Method, http.StatusAccepted) + case http.MethodPatch: + writeFn(w, req.Method, http.StatusPartialContent) + case http.MethodDelete: + w.WriteHeader(http.StatusNoContent) + } + }) + return mux +} + +func assertBody(t *testing.T, method string) func(*bytes.Buffer) { + return func(buf *bytes.Buffer) { + var f foo + if err := json.NewDecoder(buf).Decode(&f); err != nil { + t.Fatal(err) + } + expected := foo{Name: "name", Thing: "thing", Method: method} + equals(t, expected, f) + } +} + +func writeFn(w http.ResponseWriter, method string, statusCode int) { + f := foo{Name: "name", Thing: "thing", Method: method} + r, err := encodeBuf(f) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.WriteHeader(statusCode) + if _, err := io.Copy(w, r); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func equals(t *testing.T, expected, actual interface{}) { + t.Helper() + if expected == actual { + return + } + t.Errorf("expected: %v\tactual: %v", expected, actual) +} + +func encodeBuf(v interface{}) (io.Reader, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(v); err != nil { + return nil, err + } + return &buf, nil +}