Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
22 changes: 10 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
14 changes: 7 additions & 7 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package http

import (
"bytes"
"io/ioutil"
"io"
"net/http"
"net/url"
)
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
57 changes: 49 additions & 8 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,105 @@ 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)
}
}

// 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)
}
}

// 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)
}
}

// 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)
}
}

// 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)
}
}

// 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)
Expand All @@ -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
Expand All @@ -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
}
Expand Down
19 changes: 9 additions & 10 deletions jsonpath/jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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() {
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
16 changes: 9 additions & 7 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"testing"

Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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: '\'`)
}
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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'")
Expand Down
Loading