diff --git a/go.mod b/go.mod index 494bccd..e8aa3a7 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,17 @@ module github.com/steinfletcher/apitest-jsonpath -go 1.13 +go 1.20 require ( github.com/PaesslerAG/jsonpath v0.1.1 - github.com/steinfletcher/apitest v1.5.10 - github.com/stretchr/testify v1.7.0 + github.com/steinfletcher/apitest v1.5.15 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/PaesslerAG/gval v1.2.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 28bc60e..382fc46 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,20 @@ -github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8= github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= -github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= +github.com/PaesslerAG/gval v1.2.2 h1:Y7iBzhgE09IGTt5QgGQ2IdaYYYOU134YGHBThD+wm9E= +github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac= github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/steinfletcher/apitest v1.5.10 h1:uxEm/boegmZI9csm1fLVywB5b07ijcrcHo3PZO6sfns= -github.com/steinfletcher/apitest v1.5.10/go.mod h1:cf7Bneo52IIAgpqhP8xaLlzWgAiQ9fHtsDMjeDnZ3so= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/steinfletcher/apitest v1.5.15 h1:AAdTN0yMbf0VMH/PMt9uB2I7jljepO6i+5uhm1PjH3c= +github.com/steinfletcher/apitest v1.5.15/go.mod h1:mF+KnYaIkuHM0C4JgGzkIIOJAEjo+EA5tTjJ+bHXnQc= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/http/http.go b/http/http.go index 6cf5865..c83e28e 100644 --- a/http/http.go +++ b/http/http.go @@ -2,7 +2,7 @@ package http import ( "bytes" - "io/ioutil" + "io" "net/http" "net/url" ) @@ -14,15 +14,15 @@ func CopyResponse(response *http.Response) *http.Response { var resBodyBytes []byte if response.Body != nil { - resBodyBytes, _ = ioutil.ReadAll(response.Body) - response.Body = ioutil.NopCloser(bytes.NewBuffer(resBodyBytes)) + resBodyBytes, _ = io.ReadAll(response.Body) + response.Body = io.NopCloser(bytes.NewBuffer(resBodyBytes)) } resCopy := &http.Response{ Header: map[string][]string{}, StatusCode: response.StatusCode, Status: response.Status, - Body: ioutil.NopCloser(bytes.NewBuffer(resBodyBytes)), + Body: io.NopCloser(bytes.NewBuffer(resBodyBytes)), Proto: response.Proto, ProtoMinor: response.ProtoMinor, ProtoMajor: response.ProtoMajor, @@ -49,9 +49,9 @@ func CopyRequest(request *http.Request) *http.Request { resCopy = resCopy.WithContext(request.Context()) if request.Body != nil { - bodyBytes, _ := ioutil.ReadAll(request.Body) - resCopy.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) - request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + bodyBytes, _ := io.ReadAll(request.Body) + resCopy.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) } if request.URL != nil { diff --git a/jsonpath.go b/jsonpath.go index bc8d0da..1dd6a7f 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -11,22 +11,32 @@ import ( ) // Contains is a convenience function to assert that a jsonpath expression extracts a value in an array -func Contains(expression string, expected interface{}) func(*http.Response, *http.Request) error { +func Contains(expression string, expected any) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { return jsonpath.Contains(expression, expected, res.Body) } } // Equal is a convenience function to assert that a jsonpath expression extracts a value -func Equal(expression string, expected interface{}) func(*http.Response, *http.Request) error { +func Equal(expression string, expected any) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.Equal(expression, expected, res.Body) } } // NotEqual is a function to check json path expression value is not equal to given value -func NotEqual(expression string, expected interface{}) func(*http.Response, *http.Request) error { +func NotEqual(expression string, expected any) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.NotEqual(expression, expected, res.Body) } } @@ -34,6 +44,11 @@ func NotEqual(expression string, expected interface{}) func(*http.Response, *htt // Len asserts that value is the expected length, determined by reflect.Len func Len(expression string, expectedLength int) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.Length(expression, expectedLength, res.Body) } } @@ -41,6 +56,11 @@ func Len(expression string, expectedLength int) func(*http.Response, *http.Reque // GreaterThan asserts that value is greater than the given length, determined by reflect.Len func GreaterThan(expression string, minimumLength int) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.GreaterThan(expression, minimumLength, res.Body) } } @@ -48,6 +68,11 @@ func GreaterThan(expression string, minimumLength int) func(*http.Response, *htt // LessThan asserts that value is less than the given length, determined by reflect.Len func LessThan(expression string, maximumLength int) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.LessThan(expression, maximumLength, res.Body) } } @@ -55,6 +80,11 @@ func LessThan(expression string, maximumLength int) func(*http.Response, *http.R // Present asserts that value returned by the expression is present func Present(expression string) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.Present(expression, res.Body) } } @@ -62,13 +92,24 @@ func Present(expression string) func(*http.Response, *http.Request) error { // NotPresent asserts that value returned by the expression is not present func NotPresent(expression string) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() return jsonpath.NotPresent(expression, res.Body) } } // Matches asserts that the value matches the given regular expression -func Matches(expression string, regexp string) func(*http.Response, *http.Request) error { +func Matches(expression, regexp string) func(*http.Response, *http.Request) error { return func(res *http.Response, req *http.Request) error { + defer func() { + if resErr := res.Body.Close(); resErr != nil { + panic(resErr) + } + }() + pattern, err := regex.Compile(regexp) if err != nil { return fmt.Errorf("invalid pattern: '%s'", regexp) @@ -94,7 +135,7 @@ func Matches(expression string, regexp string) func(*http.Response, *http.Reques reflect.Float32, reflect.Float64, reflect.String: - if !pattern.Match([]byte(fmt.Sprintf("%v", value))) { + if !pattern.MatchString(fmt.Sprintf("%v", value)) { return fmt.Errorf("value '%v' does not match pattern '%v'", value, regexp) } return nil @@ -121,19 +162,19 @@ type AssertionChain struct { } // Equal adds an Equal assertion to the chain -func (r *AssertionChain) Equal(expression string, expected interface{}) *AssertionChain { +func (r *AssertionChain) Equal(expression string, expected any) *AssertionChain { r.assertions = append(r.assertions, Equal(r.rootExpression+expression, expected)) return r } // NotEqual adds an NotEqual assertion to the chain -func (r *AssertionChain) NotEqual(expression string, expected interface{}) *AssertionChain { +func (r *AssertionChain) NotEqual(expression string, expected any) *AssertionChain { r.assertions = append(r.assertions, NotEqual(r.rootExpression+expression, expected)) return r } // Contains adds an Contains assertion to the chain -func (r *AssertionChain) Contains(expression string, expected interface{}) *AssertionChain { +func (r *AssertionChain) Contains(expression string, expected any) *AssertionChain { r.assertions = append(r.assertions, Contains(r.rootExpression+expression, expected)) return r } diff --git a/jsonpath/jsonpath.go b/jsonpath/jsonpath.go index f835f14..3ff62b9 100644 --- a/jsonpath/jsonpath.go +++ b/jsonpath/jsonpath.go @@ -6,14 +6,13 @@ import ( "errors" "fmt" "io" - "io/ioutil" "reflect" "strings" "github.com/PaesslerAG/jsonpath" ) -func Contains(expression string, expected interface{}, data io.Reader) error { +func Contains(expression string, expected any, data io.Reader) error { value, err := JsonPath(data, expression) if err != nil { return err @@ -28,7 +27,7 @@ func Contains(expression string, expected interface{}, data io.Reader) error { return nil } -func Equal(expression string, expected interface{}, data io.Reader) error { +func Equal(expression string, expected any, data io.Reader) error { value, err := JsonPath(data, expression) if err != nil { return err @@ -39,7 +38,7 @@ func Equal(expression string, expected interface{}, data io.Reader) error { return nil } -func NotEqual(expression string, expected interface{}, data io.Reader) error { +func NotEqual(expression string, expected any, data io.Reader) error { value, err := JsonPath(data, expression) if err != nil { return err @@ -118,9 +117,9 @@ func NotPresent(expression string, data io.Reader) error { return nil } -func JsonPath(reader io.Reader, expression string) (interface{}, error) { - v := interface{}(nil) - b, err := ioutil.ReadAll(reader) +func JsonPath(reader io.Reader, expression string) (any, error) { + v := any(nil) + b, err := io.ReadAll(reader) if err != nil { return nil, err } @@ -138,7 +137,7 @@ func JsonPath(reader io.Reader, expression string) (interface{}, error) { } // courtesy of github.com/stretchr/testify -func IncludesElement(list interface{}, element interface{}) (ok, found bool) { +func IncludesElement(list, element any) (ok, found bool) { listValue := reflect.ValueOf(list) elementValue := reflect.ValueOf(element) defer func() { @@ -170,7 +169,7 @@ func IncludesElement(list interface{}, element interface{}) (ok, found bool) { return true, false } -func ObjectsAreEqual(expected, actual interface{}) bool { +func ObjectsAreEqual(expected, actual any) bool { if expected == nil || actual == nil { return expected == actual } @@ -190,7 +189,7 @@ func ObjectsAreEqual(expected, actual interface{}) bool { return bytes.Equal(exp, act) } -func isEmpty(object interface{}) bool { +func isEmpty(object any) bool { if object == nil { return true } diff --git a/jsonpath_test.go b/jsonpath_test.go index 09e85e2..b17ff26 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "net/http" "testing" @@ -86,7 +86,7 @@ func TestApiTest_Equal_Map(t *testing.T) { Handler(handler). Get("/hello"). Expect(t). - Assert(jsonpath.Equal(`$`, map[string]interface{}{"a": "hello", "b": float64(12345)})). + Assert(jsonpath.Equal(`$`, map[string]any{"a": "hello", "b": float64(12345)})). End() } @@ -143,7 +143,7 @@ func TestApiTest_NotEqual_Map(t *testing.T) { Handler(handler). Get("/hello"). Expect(t). - Assert(jsonpath.NotEqual(`$`, map[string]interface{}{"a": "hello", "b": float64(1)})). + Assert(jsonpath.NotEqual(`$`, map[string]any{"a": "hello", "b": float64(1)})). End() } @@ -341,7 +341,9 @@ func TestApiTest_Chain(t *testing.T) { func TestApiTest_Matches_FailCompile(t *testing.T) { willFailToCompile := jsonpath.Matches(`$.b[? @.key=="c"].value`, `\`) - err := willFailToCompile(nil, nil) + err := willFailToCompile(&http.Response{ + Body: io.NopCloser(bytes.NewBuffer([]byte(`{"anObject":{"aString":"lol"}}`))), + }, nil) assert.EqualError(t, err, `invalid pattern: '\'`) } @@ -350,7 +352,7 @@ func TestApiTest_Matches_FailForObject(t *testing.T) { matcher := jsonpath.Matches(`$.anObject`, `.+`) err := matcher(&http.Response{ - Body: ioutil.NopCloser(bytes.NewBuffer([]byte(`{"anObject":{"aString":"lol"}}`))), + Body: io.NopCloser(bytes.NewBuffer([]byte(`{"anObject":{"aString":"lol"}}`))), }, nil) assert.EqualError(t, err, "unable to match using type: map") @@ -360,7 +362,7 @@ func TestApiTest_Matches_FailForArray(t *testing.T) { matcher := jsonpath.Matches(`$.aSlice`, `.+`) err := matcher(&http.Response{ - Body: ioutil.NopCloser(bytes.NewBuffer([]byte(`{"aSlice":[1,2,3]}`))), + Body: io.NopCloser(bytes.NewBuffer([]byte(`{"aSlice":[1,2,3]}`))), }, nil) assert.EqualError(t, err, "unable to match using type: slice") @@ -370,7 +372,7 @@ func TestApiTest_Matches_FailForNilValue(t *testing.T) { matcher := jsonpath.Matches(`$.nothingHere`, `.+`) err := matcher(&http.Response{ - Body: ioutil.NopCloser(bytes.NewBuffer([]byte(`{"aSlice":[1,2,3]}`))), + Body: io.NopCloser(bytes.NewBuffer([]byte(`{"aSlice":[1,2,3]}`))), }, nil) assert.EqualError(t, err, "no match for pattern: '$.nothingHere'") diff --git a/jwt.go b/jwt.go index 584ab1e..1a7b19f 100644 --- a/jwt.go +++ b/jwt.go @@ -16,15 +16,15 @@ const ( jwtPayloadIndex = 1 ) -func JWTHeaderEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}) func(*http.Response, *http.Request) error { +func JWTHeaderEqual(tokenSelector func(*http.Response) (string, error), expression string, expected any) func(*http.Response, *http.Request) error { return jwtEqual(tokenSelector, expression, expected, jwtHeaderIndex) } -func JWTPayloadEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}) func(*http.Response, *http.Request) error { +func JWTPayloadEqual(tokenSelector func(*http.Response) (string, error), expression string, expected any) func(*http.Response, *http.Request) error { return jwtEqual(tokenSelector, expression, expected, jwtPayloadIndex) } -func jwtEqual(tokenSelector func(*http.Response) (string, error), expression string, expected interface{}, index int) func(*http.Response, *http.Request) error { +func jwtEqual(tokenSelector func(*http.Response) (string, error), expression string, expected any, index int) func(*http.Response, *http.Request) error { return func(response *http.Response, request *http.Request) error { token, err := tokenSelector(response) if err != nil { @@ -48,7 +48,7 @@ func jwtEqual(tokenSelector func(*http.Response) (string, error), expression str } if !jsonpath.ObjectsAreEqual(value, expected) { - return errors.New(fmt.Sprintf("\"%s\" not equal to \"%s\"", value, expected)) + return fmt.Errorf("%q not equal to %q", value, expected) } return nil diff --git a/mocks/mocks.go b/mocks/mocks.go index 009a0fd..9fbb92e 100644 --- a/mocks/mocks.go +++ b/mocks/mocks.go @@ -9,21 +9,21 @@ import ( ) // Contains is a convenience function to assert that a jsonpath expression extracts a value in an array -func Contains(expression string, expected interface{}) apitest.Matcher { +func Contains(expression string, expected any) apitest.Matcher { return func(req *http.Request, mockReq *apitest.MockRequest) error { return jsonpath.Contains(expression, expected, httputil.CopyRequest(req).Body) } } // Equal is a convenience function to assert that a jsonpath expression matches the given value -func Equal(expression string, expected interface{}) apitest.Matcher { +func Equal(expression string, expected any) apitest.Matcher { return func(req *http.Request, mockReq *apitest.MockRequest) error { return jsonpath.Equal(expression, expected, httputil.CopyRequest(req).Body) } } // NotEqual is a function to check json path expression value is not equal to given value -func NotEqual(expression string, expected interface{}) apitest.Matcher { +func NotEqual(expression string, expected any) apitest.Matcher { return func(req *http.Request, mockReq *apitest.MockRequest) error { return jsonpath.NotEqual(expression, expected, httputil.CopyRequest(req).Body) } diff --git a/mocks/mocks_test.go b/mocks/mocks_test.go index 8306ca1..d16eedb 100644 --- a/mocks/mocks_test.go +++ b/mocks/mocks_test.go @@ -3,7 +3,7 @@ package mocks_test import ( "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "strings" "testing" @@ -86,13 +86,13 @@ type userResponse struct { IsContactable bool `json:"is_contactable"` } -func httpGet(path string, response interface{}) error { +func httpGet(path string, response any) error { res, err := http.DefaultClient.Get(fmt.Sprintf("http://localhost:8080%s", path)) if err != nil { return err } - bytes, err := ioutil.ReadAll(res.Body) + bytes, err := io.ReadAll(res.Body) if err != nil { return err } @@ -105,13 +105,13 @@ func httpGet(path string, response interface{}) error { return nil } -func httpPost(path string, requestBody string, response interface{}) error { +func httpPost(path, requestBody string, response any) error { res, err := http.DefaultClient.Post(fmt.Sprintf("http://localhost:8080%s", path), "application/json", strings.NewReader(requestBody)) if err != nil { return err } - bytes, err := ioutil.ReadAll(res.Body) + bytes, err := io.ReadAll(res.Body) if err != nil { return err }