Skip to content

Commit

Permalink
Support jsonpath matchers for mock request body
Browse files Browse the repository at this point in the history
  • Loading branch information
Stein Fletcher committed Jun 3, 2021
1 parent 49f06f7 commit 6feebb0
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 173 deletions.
181 changes: 10 additions & 171 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,136 +2,69 @@ package jsonpath

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/steinfletcher/apitest-jsonpath/jsonpath"
"io/ioutil"
"net/http"
"net/url"
"reflect"
regex "regexp"
"strings"

"github.com/PaesslerAG/jsonpath"
)

// 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 {
return func(res *http.Response, req *http.Request) error {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

ok, found := includesElement(value, expected)
if !ok {
return errors.New(fmt.Sprintf("\"%s\" could not be applied builtin len()", expected))
}
if !found {
return errors.New(fmt.Sprintf("\"%s\" does not contain \"%s\"", value, expected))
}
return nil
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 {
return func(res *http.Response, req *http.Request) error {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

if !objectsAreEqual(value, expected) {
return errors.New(fmt.Sprintf("\"%s\" not equal to \"%s\"", value, expected))
}
return nil
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 {
return func(res *http.Response, req *http.Request) error {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

if objectsAreEqual(value, expected) {
return errors.New(fmt.Sprintf("\"%s\" value is equal to \"%s\"", expression, expected))
}
return nil
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 {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

v := reflect.ValueOf(value)
if v.Len() != expectedLength {
return errors.New(fmt.Sprintf("\"%d\" not equal to \"%d\"", v.Len(), expectedLength))
}
return nil
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 {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

v := reflect.ValueOf(value)
if v.Len() < minimumLength {
return errors.New(fmt.Sprintf("\"%d\" is greater than \"%d\"", v.Len(), minimumLength))
}
return nil
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 {
value, err := jsonPath(res.Body, expression)
if err != nil {
return err
}

v := reflect.ValueOf(value)
if v.Len() > maximumLength {
return errors.New(fmt.Sprintf("\"%d\" is less than \"%d\"", v.Len(), maximumLength))
}
return nil
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 {
value, _ := jsonPath(res.Body, expression)
if isEmpty(value) {
return errors.New(fmt.Sprintf("value not present for expression: '%s'", expression))
}
return nil
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 {
value, _ := jsonPath(res.Body, expression)
if !isEmpty(value) {
return errors.New(fmt.Sprintf("value present for expression: '%s'", expression))
}
return nil
return jsonpath.NotPresent(expression, res.Body)
}
}

Expand All @@ -142,7 +75,7 @@ func Matches(expression string, regexp string) func(*http.Response, *http.Reques
if err != nil {
return errors.New(fmt.Sprintf("invalid pattern: '%s'", regexp))
}
value, _ := jsonPath(res.Body, expression)
value, _ := jsonpath.JsonPath(res.Body, expression)
if value == nil {
return errors.New(fmt.Sprintf("no match for pattern: '%s'", expression))
}
Expand Down Expand Up @@ -237,100 +170,6 @@ func (r *AssertionChain) End() func(*http.Response, *http.Request) error {
}
}

func isEmpty(object interface{}) bool {
if object == nil {
return true
}

objValue := reflect.ValueOf(object)

switch objValue.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
return objValue.Len() == 0
case reflect.Ptr:
if objValue.IsNil() {
return true
}
deref := objValue.Elem().Interface()
return isEmpty(deref)
default:
zero := reflect.Zero(objValue.Type())
return reflect.DeepEqual(object, zero.Interface())
}
}

func jsonPath(reader io.Reader, expression string) (interface{}, error) {
v := interface{}(nil)
b, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}

err = json.Unmarshal(b, &v)
if err != nil {
return nil, err
}

value, err := jsonpath.Get(expression, v)
if err != nil {
return nil, fmt.Errorf("evaluating '%s' resulted in error: '%s'", expression, err)
}
return value, nil
}

// courtesy of github.com/stretchr/testify
func includesElement(list interface{}, element interface{}) (ok, found bool) {
listValue := reflect.ValueOf(list)
elementValue := reflect.ValueOf(element)
defer func() {
if e := recover(); e != nil {
ok = false
found = false
}
}()

if reflect.TypeOf(list).Kind() == reflect.String {
return true, strings.Contains(listValue.String(), elementValue.String())
}

if reflect.TypeOf(list).Kind() == reflect.Map {
mapKeys := listValue.MapKeys()
for i := 0; i < len(mapKeys); i++ {
if objectsAreEqual(mapKeys[i].Interface(), element) {
return true, true
}
}
return true, false
}

for i := 0; i < listValue.Len(); i++ {
if objectsAreEqual(listValue.Index(i).Interface(), element) {
return true, true
}
}
return true, false
}

func objectsAreEqual(expected, actual interface{}) bool {
if expected == nil || actual == nil {
return expected == actual
}

exp, ok := expected.([]byte)
if !ok {
return reflect.DeepEqual(expected, actual)
}

act, ok := actual.([]byte)
if !ok {
return false
}
if exp == nil || act == nil {
return exp == nil && act == nil
}
return bytes.Equal(exp, act)
}

func copyHttpResponse(response *http.Response) *http.Response {
if response == nil {
return nil
Expand Down
Loading

0 comments on commit 6feebb0

Please sign in to comment.