diff --git a/README.md b/README.md index 0f8b26e2..40a162ce 100644 --- a/README.md +++ b/README.md @@ -57,16 +57,18 @@ Note that you can also add environment variables in the your configuration file Here are some examples of conditions you can use: -| Condition | Description | Passing values | Failing values | -| ------------------------------------- | ----------------------------------------- | ------------------------ | ----------------------- | -| `[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] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 | -| `[STATUS] > 400` | Status must be greater than 400 | 401, 402, 403, 404 | 200, 201, 300, 400 | -| `[RESPONSE_TIME] < 500` | Response time must be below 500ms | 100ms, 200ms, 300ms | 500ms, 1500ms | -| `[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[0].id == 1` | The jsonpath `$.data[0].id` is equal to 1 | `{"data":[{"id":1}]}` | literally anything else | +| Condition | Description | Passing values | Failing values | +| -----------------------------| ------------------------------------------------------- | ------------------------ | ----------------------- | +| `[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] <= 299` | Status must be less than or equal to 299 | 200, 201, 299 | 301, 302, 400, 500 | +| `[STATUS] > 400` | Status must be greater than 400 | 401, 402, 403, 404 | 200, 201, 300, 400 | +| `[RESPONSE_TIME] < 500` | Response time must be below 500ms | 100ms, 200ms, 300ms | 500ms, 1500ms | +| `[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[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. diff --git a/config.yaml b/config.yaml index b577ef2c..660f9ad2 100644 --- a/config.yaml +++ b/config.yaml @@ -14,8 +14,4 @@ services: - "[STATUS] == 200" - "[BODY].id == 24" - "[BODY].tags[0] == spring" - - name: example - url: https://example.org/ - interval: 30s - conditions: - - "[STATUS] == 200" + - "len([BODY].tags) > 0" \ No newline at end of file diff --git a/core/condition.go b/core/condition.go index a403e685..8c50db53 100644 --- a/core/condition.go +++ b/core/condition.go @@ -41,10 +41,10 @@ func (c *Condition) evaluate(result *Result) bool { return false } 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 { 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}) return success diff --git a/core/condition_test.go b/core/condition_test.go index 7d90ef92..845690b2 100644 --- a/core/condition_test.go +++ b/core/condition_test.go @@ -166,3 +166,21 @@ func TestCondition_evaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *tes 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) + } +} diff --git a/core/util.go b/core/util.go index 9d8007c7..96455f3a 100644 --- a/core/util.go +++ b/core/util.go @@ -32,13 +32,22 @@ func sanitizeAndResolve(list []string, result *Result) []string { element = body default: // if starts with BodyPlaceHolder, then evaluate json path - if strings.HasPrefix(element, BodyPlaceHolder) { - resolvedElement, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body) + if strings.Contains(element, BodyPlaceHolder) { + 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 { result.Errors = append(result.Errors, err.Error()) element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix) } else { - element = resolvedElement + if wantLength { + element = fmt.Sprintf("%d", resolvedElementLength) + } else { + element = resolvedElement + } } } } diff --git a/jsonpath/jsonpath.go b/jsonpath/jsonpath.go index 3dc400b3..2bbf00fa 100644 --- a/jsonpath/jsonpath.go +++ b/jsonpath/jsonpath.go @@ -8,26 +8,30 @@ import ( ) // 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{} err := json.Unmarshal(b, &object) if err != nil { // Try to unmarshal it into an array instead - return "", err + return "", 0, err } 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, ".") currentKey := keys[0] switch value := extractValue(currentKey, object).(type) { case map[string]interface{}: 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{}: - return fmt.Sprintf("%v", value), nil + return fmt.Sprintf("%v", value), 1, nil 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) } } diff --git a/jsonpath/jsonpath_test.go b/jsonpath/jsonpath_test.go index 1c9e944c..fd40d4e8 100644 --- a/jsonpath/jsonpath_test.go +++ b/jsonpath/jsonpath_test.go @@ -8,10 +8,13 @@ func TestEval(t *testing.T) { expectedOutput := "value" - output, err := Eval(path, []byte(data)) + output, outputLength, err := Eval(path, []byte(data)) if err != nil { 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 { t.Errorf("Expected output to be %v, but was %v", expectedOutput, output) } @@ -23,7 +26,7 @@ func TestEvalWithLongSimpleWalk(t *testing.T) { expectedOutput := "value" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -38,10 +41,11 @@ func TestEvalWithArrayOfMaps(t *testing.T) { expectedOutput := "2" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } + if output != expectedOutput { t.Errorf("Expected output to be %v, but was %v", expectedOutput, output) } @@ -53,7 +57,7 @@ func TestEvalWithArrayOfValues(t *testing.T) { expectedOutput := "1" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -68,7 +72,7 @@ func TestEvalWithRootArrayOfValues(t *testing.T) { expectedOutput := "2" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -83,7 +87,7 @@ func TestEvalWithRootArrayOfMaps(t *testing.T) { expectedOutput := "1" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -96,7 +100,7 @@ func TestEvalWithRootArrayOfMapsUsingInvalidArrayIndex(t *testing.T) { path := "[5].id" data := `[{"id": 1}, {"id": 2}]` - _, err := Eval(path, []byte(data)) + _, _, err := Eval(path, []byte(data)) if err == nil { t.Error("Should've returned an error, but didn't") } @@ -108,7 +112,7 @@ func TestEvalWithLongWalkAndArray(t *testing.T) { expectedOutput := "1" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -123,7 +127,7 @@ func TestEvalWithNestedArray(t *testing.T) { expectedOutput := "7" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) } @@ -138,7 +142,7 @@ func TestEvalWithMapOfNestedArray(t *testing.T) { expectedOutput := "e" - output, err := Eval(path, []byte(data)) + output, _, err := Eval(path, []byte(data)) if err != nil { t.Error("Didn't expect any error, but got", err) }