Add support for getting the length of the string or the slice of a json path

This commit is contained in:
TwinProduction 2020-08-12 21:42:13 -04:00
parent 937b136e60
commit da92907873
7 changed files with 68 additions and 35 deletions

View File

@ -58,7 +58,7 @@ Note that you can also add environment variables in the your configuration file
Here are some examples of conditions you can use: Here are some examples of conditions you can use:
| Condition | Description | Passing values | Failing values | | Condition | Description | Passing values | Failing values |
| ------------------------------------- | ----------------------------------------- | ------------------------ | ----------------------- | | -----------------------------| ------------------------------------------------------- | ------------------------ | ----------------------- |
| `[STATUS] == 200` | Status must be equal to 200 | 200 | 201, 404, 500 | | `[STATUS] == 200` | Status must be equal to 200 | 200 | 201, 404, 500 |
| `[STATUS] < 300` | Status must lower than 300 | 200, 201, 299 | 301, 302, 400, 500 | | `[STATUS] < 300` | Status must lower than 300 | 200, 201, 299 | 301, 302, 400, 500 |
| `[STATUS] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 | | `[STATUS] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 |
@ -67,6 +67,8 @@ Here are some examples of conditions you can use:
| `[BODY] == 1` | The body must be equal to 1 | 1 | literally anything else | | `[BODY] == 1` | The body must be equal to 1 | 1 | literally anything else |
| `[BODY].data.id == 1` | The jsonpath `$.data.id` is equal to 1 | `{"data":{"id":1}}` | literally anything else | | `[BODY].data.id == 1` | The jsonpath `$.data.id` is equal to 1 | `{"data":{"id":1}}` | literally anything else |
| `[BODY].data[0].id == 1` | The jsonpath `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | literally anything else | | `[BODY].data[0].id == 1` | The jsonpath `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | literally anything else |
| `len([BODY].data) > 0` | Array at jsonpath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | `{"data":[{"id":1}]}` |
| `len([BODY].name) == 8` | String at jsonpath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` |
**NOTE**: `[BODY]` with JSON path (i.e. `[BODY].id == 1`) is currently in BETA. For the most part, the only thing that doesn't work is arrays. **NOTE**: `[BODY]` with JSON path (i.e. `[BODY].id == 1`) is currently in BETA. For the most part, the only thing that doesn't work is arrays.

View File

@ -14,8 +14,4 @@ services:
- "[STATUS] == 200" - "[STATUS] == 200"
- "[BODY].id == 24" - "[BODY].id == 24"
- "[BODY].tags[0] == spring" - "[BODY].tags[0] == spring"
- name: example - "len([BODY].tags) > 0"
url: https://example.org/
interval: 30s
conditions:
- "[STATUS] == 200"

View File

@ -41,10 +41,10 @@ func (c *Condition) evaluate(result *Result) bool {
return false return false
} }
conditionToDisplay := condition conditionToDisplay := condition
// If the condition isn't a success, return the resolved condition // If the condition isn't a success, return what the resolved condition was too
if !success { if !success {
log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, resolvedCondition) log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, resolvedCondition)
conditionToDisplay = resolvedCondition conditionToDisplay = fmt.Sprintf("%s (%s)", condition, resolvedCondition)
} }
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success}) result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success})
return success return success

View File

@ -166,3 +166,21 @@ func TestCondition_evaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *tes
t.Errorf("Condition '%s' should have been a failure", condition) t.Errorf("Condition '%s' should have been a failure", condition)
} }
} }
func TestCondition_evaluateWithBodySliceLength(t *testing.T) {
condition := Condition("len([BODY].data) == 3")
result := &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
}
}
func TestCondition_evaluateWithBodyStringLength(t *testing.T) {
condition := Condition("len([BODY].name) == 8")
result := &Result{Body: []byte("{\"name\": \"john.doe\"}")}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
}
}

View File

@ -32,16 +32,25 @@ func sanitizeAndResolve(list []string, result *Result) []string {
element = body element = body
default: default:
// if starts with BodyPlaceHolder, then evaluate json path // if starts with BodyPlaceHolder, then evaluate json path
if strings.HasPrefix(element, BodyPlaceHolder) { if strings.Contains(element, BodyPlaceHolder) {
resolvedElement, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body) wantLength := false
if strings.HasPrefix(element, "len(") && strings.HasSuffix(element, ")") {
wantLength = true
element = strings.TrimSuffix(strings.TrimPrefix(element, "len("), ")")
}
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
if err != nil { if err != nil {
result.Errors = append(result.Errors, err.Error()) result.Errors = append(result.Errors, err.Error())
element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix) element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix)
} else {
if wantLength {
element = fmt.Sprintf("%d", resolvedElementLength)
} else { } else {
element = resolvedElement element = resolvedElement
} }
} }
} }
}
sanitizedList = append(sanitizedList, element) sanitizedList = append(sanitizedList, element)
} }
return sanitizedList return sanitizedList

View File

@ -8,26 +8,30 @@ import (
) )
// Eval is a half-baked json path implementation that needs some love // Eval is a half-baked json path implementation that needs some love
func Eval(path string, b []byte) (string, error) { func Eval(path string, b []byte) (string, int, error) {
var object interface{} var object interface{}
err := json.Unmarshal(b, &object) err := json.Unmarshal(b, &object)
if err != nil { if err != nil {
// Try to unmarshal it into an array instead // Try to unmarshal it into an array instead
return "", err return "", 0, err
} }
return walk(path, object) return walk(path, object)
} }
func walk(path string, object interface{}) (string, error) { func walk(path string, object interface{}) (string, int, error) {
keys := strings.Split(path, ".") keys := strings.Split(path, ".")
currentKey := keys[0] currentKey := keys[0]
switch value := extractValue(currentKey, object).(type) { switch value := extractValue(currentKey, object).(type) {
case map[string]interface{}: case map[string]interface{}:
return walk(strings.Replace(path, fmt.Sprintf("%s.", currentKey), "", 1), value) return walk(strings.Replace(path, fmt.Sprintf("%s.", currentKey), "", 1), value)
case string:
return value, len(value), nil
case []interface{}:
return fmt.Sprintf("%v", value), len(value), nil
case interface{}: case interface{}:
return fmt.Sprintf("%v", value), nil return fmt.Sprintf("%v", value), 1, nil
default: default:
return "", fmt.Errorf("couldn't walk through '%s' because type was '%T', but expected 'map[string]interface{}'", currentKey, value) return "", 0, fmt.Errorf("couldn't walk through '%s' because type was '%T', but expected 'map[string]interface{}'", currentKey, value)
} }
} }

View File

@ -8,10 +8,13 @@ func TestEval(t *testing.T) {
expectedOutput := "value" expectedOutput := "value"
output, err := Eval(path, []byte(data)) output, outputLength, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
if outputLength != len(expectedOutput) {
t.Errorf("Expected output length to be %v, but was %v", len(expectedOutput), outputLength)
}
if output != expectedOutput { if output != expectedOutput {
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output) t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
} }
@ -23,7 +26,7 @@ func TestEvalWithLongSimpleWalk(t *testing.T) {
expectedOutput := "value" expectedOutput := "value"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -38,10 +41,11 @@ func TestEvalWithArrayOfMaps(t *testing.T) {
expectedOutput := "2" expectedOutput := "2"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
if output != expectedOutput { if output != expectedOutput {
t.Errorf("Expected output to be %v, but was %v", expectedOutput, output) t.Errorf("Expected output to be %v, but was %v", expectedOutput, output)
} }
@ -53,7 +57,7 @@ func TestEvalWithArrayOfValues(t *testing.T) {
expectedOutput := "1" expectedOutput := "1"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -68,7 +72,7 @@ func TestEvalWithRootArrayOfValues(t *testing.T) {
expectedOutput := "2" expectedOutput := "2"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -83,7 +87,7 @@ func TestEvalWithRootArrayOfMaps(t *testing.T) {
expectedOutput := "1" expectedOutput := "1"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -96,7 +100,7 @@ func TestEvalWithRootArrayOfMapsUsingInvalidArrayIndex(t *testing.T) {
path := "[5].id" path := "[5].id"
data := `[{"id": 1}, {"id": 2}]` data := `[{"id": 1}, {"id": 2}]`
_, err := Eval(path, []byte(data)) _, _, err := Eval(path, []byte(data))
if err == nil { if err == nil {
t.Error("Should've returned an error, but didn't") t.Error("Should've returned an error, but didn't")
} }
@ -108,7 +112,7 @@ func TestEvalWithLongWalkAndArray(t *testing.T) {
expectedOutput := "1" expectedOutput := "1"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -123,7 +127,7 @@ func TestEvalWithNestedArray(t *testing.T) {
expectedOutput := "7" expectedOutput := "7"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }
@ -138,7 +142,7 @@ func TestEvalWithMapOfNestedArray(t *testing.T) {
expectedOutput := "e" expectedOutput := "e"
output, err := Eval(path, []byte(data)) output, _, err := Eval(path, []byte(data))
if err != nil { if err != nil {
t.Error("Didn't expect any error, but got", err) t.Error("Didn't expect any error, but got", err)
} }