From 318f25bc2bf7aa3931563a3824074e216b52f58c Mon Sep 17 00:00:00 2001 From: Dave Sheldon Date: Mon, 9 Oct 2023 15:44:34 -0400 Subject: [PATCH] feat: refactor jsonpath stuff a bit, use a new lib --- go.mod | 5 ++--- go.sum | 11 ++++++++++ napassert/assert.go | 24 ++++++++++----------- napcap/cap.go | 9 +++++++- napcap/cap_test.go | 14 ++++++------- napquery/query.go | 43 ++++++++++++++++++++++++++------------ naprunner/requestrunner.go | 8 ++++++- napscript/script.go | 17 ++++++++------- 8 files changed, 87 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 2a73465..9cf9bdd 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,11 @@ require ( ) require ( - github.com/PaesslerAG/gval v1.0.0 // indirect + github.com/AsaiYusuke/jsonpath v1.6.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -26,7 +26,6 @@ require ( ) require ( - github.com/PaesslerAG/jsonpath v0.1.1 github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect diff --git a/go.sum b/go.sum index 90c1803..c27571b 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AsaiYusuke/jsonpath v1.6.0 h1:YagKI8icTdxHujVwsgmL4Cm3DYm36g+ymp9PTMXY67o= +github.com/AsaiYusuke/jsonpath v1.6.0/go.mod h1:XblL8QLThYDIvcQkFJJXDqfry/XAkMYEIOItWRZtz1s= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -240,6 +242,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= +github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -275,6 +281,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -295,6 +303,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/ohler55/ojg v1.19.4 h1:ZIgfyHI83aLx+fi1VoKn4I80HqWo45usWKnnxw94Mro= +github.com/ohler55/ojg v1.19.4/go.mod h1:uHcD1ErbErC27Zhb5Df2jUjbseLLcmOCo6oxSr3jZxo= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -568,6 +578,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= diff --git a/napassert/assert.go b/napassert/assert.go index 78d716d..27b185d 100644 --- a/napassert/assert.go +++ b/napassert/assert.go @@ -57,7 +57,7 @@ func NewAssert(query string, predicate string, expectation string) *Assert { return assert } -func Execute(assert *Assert, actual string) error { +func Execute(assert *Assert, actual interface{}) error { query := assert.Query predicate := assert.Predicate expectation := assert.Expectation @@ -75,7 +75,7 @@ func Execute(assert *Assert, actual string) error { case "==": result = actual == expectation if result != desiredResult { - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -90,7 +90,7 @@ func Execute(assert *Assert, actual string) error { case "!=": result = actual != expectation if result != desiredResult { - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -103,7 +103,7 @@ func Execute(assert *Assert, actual string) error { result = floatActual != floatExpectation } case "<": - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -114,7 +114,7 @@ func Execute(assert *Assert, actual string) error { result = floatActual < floatAssertValue case "<=": - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -125,7 +125,7 @@ func Execute(assert *Assert, actual string) error { result = floatActual <= floatAssertValue case ">": - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -136,7 +136,7 @@ func Execute(assert *Assert, actual string) error { result = floatActual > floatAssertValue case ">=": - floatActual, err := strconv.ParseFloat(actual, 64) + floatActual, err := strconv.ParseFloat(fmt.Sprint(actual), 64) if err != nil { break } @@ -148,13 +148,13 @@ func Execute(assert *Assert, actual string) error { result = floatActual >= floatAssertValue case "matches": re := regexp.MustCompile(expectation) - result = re.MatchString(actual) + result = re.MatchString(fmt.Sprint(actual)) case "contains": - result = strings.Contains(actual, expectation) + result = strings.Contains(fmt.Sprint(actual), expectation) case "startswith": - result = strings.HasPrefix(actual, expectation) + result = strings.HasPrefix(fmt.Sprint(actual), expectation) case "endswith": - result = strings.HasSuffix(actual, expectation) + result = strings.HasSuffix(fmt.Sprint(actual), expectation) case "in": validValues := []interface{}{} data := []byte(expectation) @@ -173,7 +173,7 @@ func Execute(assert *Assert, actual string) error { // string didn't compare, let's parse to float and try again floatVal, err := strconv.ParseFloat(strVal, 64) - floatActual, err2 := strconv.ParseFloat(actual, 64) + floatActual, err2 := strconv.ParseFloat(fmt.Sprint(actual), 64) if err == nil && err2 == nil { in = floatVal == floatActual diff --git a/napcap/cap.go b/napcap/cap.go index ae6fb8e..1b15448 100644 --- a/napcap/cap.go +++ b/napcap/cap.go @@ -18,6 +18,8 @@ capture.go - this file contains logic for evaluating captures package napcap import ( + "fmt" + "github.com/davesheldon/nap/napcontext" "github.com/davesheldon/nap/napquery" "github.com/davesheldon/nap/napscript" @@ -34,6 +36,11 @@ func CaptureQuery(variable string, query string, ctx *napcontext.Context, vmData return err } - ctx.EnvironmentVariables[variable] = actual + if len(actual) > 0 { + ctx.EnvironmentVariables[variable] = fmt.Sprint(actual[0]) + } + + // todo: deal with multiple return values + return nil } diff --git a/napcap/cap_test.go b/napcap/cap_test.go index ffccf9a..a3901a4 100644 --- a/napcap/cap_test.go +++ b/napcap/cap_test.go @@ -13,22 +13,22 @@ func TestCaptures(t *testing.T) { tests := map[string]struct { variable string ctx *napcontext.Context - queryFunc func(query string, vmData *napscript.VmHttpData) (string, error) + queryFunc func(query string, vmData *napscript.VmHttpData) ([]any, error) }{ "set new variable": { variable: "test1", ctx: napcontext.New("", nil, make(map[string]string), nil, true), - queryFunc: mockQuery("value1", nil), + queryFunc: mockQuery([]any{"value1"}, nil), }, "overwrite new variable": { variable: "test1", ctx: napcontext.New("", nil, map[string]string{"test1": "value1"}, nil, true), - queryFunc: mockQuery("value2", nil), + queryFunc: mockQuery([]any{"value2"}, nil), }, "error": { variable: "test1", ctx: napcontext.New("", nil, make(map[string]string), nil, true), - queryFunc: mockQuery("", fmt.Errorf("mock error")), + queryFunc: mockQuery(nil, fmt.Errorf("mock error")), }, } @@ -38,7 +38,7 @@ func TestCaptures(t *testing.T) { queryResult, queryError := test.queryFunc("", nil) err := napcap.CaptureQuery(test.variable, "", test.ctx, nil) - if err == nil && queryError == nil && test.ctx.EnvironmentVariables[test.variable] != queryResult { + if err == nil && queryError == nil && test.ctx.EnvironmentVariables[test.variable] != queryResult[0] { t.Errorf("Expected %s=%s, got %s", test.variable, queryResult, test.ctx.EnvironmentVariables[test.variable]) } else if queryError != nil && err == nil { t.Errorf("Expected error, got nil") @@ -49,8 +49,8 @@ func TestCaptures(t *testing.T) { } } -func mockQuery(mockResult string, mockError error) func(query string, vmData *napscript.VmHttpData) (string, error) { - q := func(query string, vmData *napscript.VmHttpData) (string, error) { +func mockQuery(mockResult []any, mockError error) func(query string, vmData *napscript.VmHttpData) ([]any, error) { + q := func(query string, vmData *napscript.VmHttpData) ([]any, error) { return mockResult, mockError } diff --git a/napquery/query.go b/napquery/query.go index 3d4c848..4b8aa01 100644 --- a/napquery/query.go +++ b/napquery/query.go @@ -22,51 +22,68 @@ import ( "strconv" "strings" - "github.com/PaesslerAG/jsonpath" - + "github.com/AsaiYusuke/jsonpath" "github.com/davesheldon/nap/napscript" + jsoniter "github.com/json-iterator/go" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +func evalJsonPath(expression string, data interface{}) ([]interface{}, error) { + var config = jsonpath.Config{} + config.SetAggregateFunction(`length`, func(params []interface{}) (interface{}, error) { + if params == nil { + return nil, nil + } + return len(params), nil + }) + return jsonpath.Retrieve(expression, data, config) +} + +var ( + EvalJsonPath = evalJsonPath ) -func Eval(query string, vmData *napscript.VmHttpData) (string, error) { +func Eval(query string, vmData *napscript.VmHttpData) ([]any, error) { if vmData == nil || vmData.Response == nil { // return empty here instead of erroring in case this assert is testing for absence of a value - return "", nil + return nil, nil } jsonExpression, isJsonPath := strings.CutPrefix(query, "jsonpath ") if isJsonPath { body := vmData.Response.JsonBody - value, err := jsonpath.Get(jsonExpression, body) + value, err := EvalJsonPath(jsonExpression, body) if err != nil { - return "", err + return nil, err } - return fmt.Sprint(value), nil + return value, nil } header, isHeader := strings.CutPrefix(query, "header ") if isHeader { if vmData.Response.Headers == nil { - return "", nil + return nil, nil } value := vmData.Response.Headers[header] - return strings.Join(value, ","), nil + return value, nil } if query == "status" { - return strconv.Itoa(vmData.Response.StatusCode), nil + return []any{strconv.Itoa(vmData.Response.StatusCode)}, nil } if query == "duration" { - return strconv.FormatInt(vmData.Response.ElapsedMs, 10), nil + return []any{strconv.FormatInt(vmData.Response.ElapsedMs, 10)}, nil } if query == "body" { - return vmData.Response.Body, nil + return []any{vmData.Response.Body}, nil } - return "", fmt.Errorf("Query \"%s\" not recognized.", query) + return nil, fmt.Errorf("Query \"%s\" not recognized.", query) } diff --git a/naprunner/requestrunner.go b/naprunner/requestrunner.go index bc0f38d..2b667f0 100644 --- a/naprunner/requestrunner.go +++ b/naprunner/requestrunner.go @@ -127,7 +127,13 @@ func runRequest(ctx *napcontext.Context, runPath string, request *naprequest.Req return result } - err = napassert.Execute(v, actual) + var testVal interface{} = nil + + if actual != nil && len(actual) > 0 { + testVal = actual[0] + } + + err = napassert.Execute(v, testVal) if err != nil { result.Error = err diff --git a/napscript/script.go b/napscript/script.go index 5d20b2a..096c2fe 100644 --- a/napscript/script.go +++ b/napscript/script.go @@ -156,11 +156,11 @@ type VmHttpRequest struct { } type VmHttpResponse struct { - StatusCode int `json:"statusCode"` - Status string `json:"status"` - Body string `json:"body"` - JsonBody interface{} `json:"jsonBody"` - Headers map[string][]string `json:"headers"` + StatusCode int `json:"statusCode"` + Status string `json:"status"` + Body string `json:"body"` + JsonBody interface{} `json:"jsonBody"` + Headers map[string][]interface{} `json:"headers"` ElapsedMs int64 } @@ -190,11 +190,14 @@ func MapVmHttpData(result *naprequest.RequestResult) (*VmHttpData, error) { defer result.HttpResponse.Body.Close() // TODO: support multiple header values per key - data.Response.Headers = map[string][]string{} + data.Response.Headers = map[string][]any{} for k, v := range result.HttpResponse.Header { if len(v) > 0 { - data.Response.Headers[k] = v + data.Response.Headers[k] = make([]any, len(v)) + for i, val := range v { + data.Response.Headers[k][i] = val + } if k == "Content-Type" && strings.Contains(v[0], "json") { err = json.Unmarshal(bodyBytes, &data.Response.JsonBody)