diff --git a/docs/BUILTIN.md b/docs/BUILTIN.md index 19c6bff5..a4b9305b 100644 --- a/docs/BUILTIN.md +++ b/docs/BUILTIN.md @@ -6,23 +6,27 @@ HttpRunner+ validation should follow the following format. `check`, `assert` and ```json { - "check": "status_code", // target field, usually used with jmespath - "assert": "equals", // assertion method, you can use builtin method or custom defined function - "expect": 200, // expected value - "msg": "check response status code" // optional, print this message if assertion failed + "check": "status_code", + // target field, usually used with jmespath + "assert": "equals", + // assertion method, you can use builtin method or custom defined function + "expect": 200, + // expected value + "msg": "check response status code" + // optional, print this message if assertion failed } ``` The `assert` method name will be mapped to a built-in function with the following function signature. ```go -func(t assert.TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool +func(t assert.TestingT, actual interface{}, expected interface{}, msgAndArgs ...interface{}) bool ``` Currently, HttpRunner+ has the following built-in assertion functions. | `assert` | Description | A(check), B(expect) | examples | -| -- | -- | -- | -- | +| --- | --- | --- | --- | | `eq`, `equals`, `equal` | value is equal | A == B | 9 eq 9 | | `lt`, `less_than` | less than | A < B | 7 lt 8 | | `le`, `less_or_equals` | less than or equals | A <= B | 7 le 8, 8 le 8 | @@ -31,16 +35,16 @@ Currently, HttpRunner+ has the following built-in assertion functions. | `ne`, `not_equal` | not equals | A != B | 6 ne 9 | | `str_eq`, `string_equals` | string equals | str(A) == str(B) | 123 str_eq '123' | | `len_eq`, `length_equals`, `length_equal` | length equals | len(A) == B | 'abc' len_eq 3, [1,2] len_eq 2 | -| `len_gt`, `count_gt` | length greater than | len(A) > B | 'abc' len_gt 2, [1,2,3] len_gt 2 | -| `len_ge`, `count_ge` | length greater than or equals | len(A) >= B | 'abc' len_ge 3, [1,2,3] len_gt 3 | -| `len_lt`, `count_lt` | length less than | len(A) < B | 'abc' len_lt 4, [1,2,3] len_lt 4 | -| `len_le`, `count_le` | length less than or equals | len(A) <= B | 'abc' len_le 3, [1,2,3] len_le 3 | +| `len_gt`, `count_gt`, `length_greater_than` | length greater than | len(A) > B | 'abc' len_gt 2, [1,2,3] len_gt 2 | +| `len_ge`, `count_ge`, `length_greater_or_equals` | length greater than or equals | len(A) >= B | 'abc' len_ge 3, [1,2,3] len_gt 3 | +| `len_lt`, `count_lt`, `length_less_than` | length less than | len(A) < B | 'abc' len_lt 4, [1,2,3] len_lt 4 | +| `len_le`, `count_le`, `length_less_or_equals` | length less than or equals | len(A) <= B | 'abc' len_le 3, [1,2,3] len_le 3 | | `contains` | contains | [1, 2] contains 1 | 'abc' contains 'a', [1,2,3] len_lt 4 | | `contained_by` | contained by | A in B | 'a' contained_by 'abc', 1 contained_by [1,2] | | `type_match` | A and B are in the same type | type(A) == type(B) | 123 type_match 1 | | `regex_match` | regex matches | re.match(B, A) | 'abcdef' regex 'a\w+d' | -| `startswith` | starts with | A.startswith(B) is True | 'abc' startswith 'ab' | -| `endswith` | ends with | A.endswith(B) is True | 'abc' endswith 'bc' | +| `starts_with` | starts with | A.startswith(B) is True | 'abc' startswith 'ab' | +| `ends_with` | ends with | A.endswith(B) is True | 'abc' endswith 'bc' | ## Builtin functions diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 66744592..1cada300 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v0.6.1 (2022-02-11) + +- fix: assertion function and json number parse rule + ## v0.6.0 (2022-02-08) - feat: implement `rendezvous` mechanism for data driven diff --git a/internal/builtin/assertion.go b/internal/builtin/assertion.go index 189347c2..d7de8482 100644 --- a/internal/builtin/assertion.go +++ b/internal/builtin/assertion.go @@ -8,31 +8,48 @@ import ( "github.com/stretchr/testify/assert" ) -var Assertions = map[string]func(t assert.TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool{ +var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected interface{}, msgAndArgs ...interface{}) bool{ + "eq": assert.EqualValues, "equals": assert.EqualValues, - "equal": assert.EqualValues, // alias for equals - "greater_than": assert.Less, - "less_than": assert.Greater, - "greater_or_equals": assert.LessOrEqual, - "less_or_equals": assert.GreaterOrEqual, + "equal": assert.EqualValues, + "lt": assert.Less, + "less_than": assert.Less, + "le": assert.LessOrEqual, + "less_or_equals": assert.LessOrEqual, + "gt": assert.Greater, + "greater_than": assert.Greater, + "ge": assert.GreaterOrEqual, + "greater_or_equals": assert.GreaterOrEqual, + "ne": assert.NotEqual, "not_equal": assert.NotEqual, - "contained_by": assert.Contains, - "regex_match": assert.Regexp, + "contains": assert.Contains, "type_match": assert.IsType, // custom assertions - "startswith": StartsWith, // check if string starts with substring - "endswith": EndsWith, // check if string ends with substring + "starts_with": StartsWith, + "ends_with": EndsWith, + "len_eq": EqualLength, "length_equals": EqualLength, - "length_equal": EqualLength, // alias for length_equals + "length_equal": EqualLength, + "len_lt": LessThanLength, + "count_lt": LessThanLength, "length_less_than": LessThanLength, + "len_le": LessOrEqualsLength, + "count_le": LessOrEqualsLength, "length_less_or_equals": LessOrEqualsLength, + "len_gt": GreaterThanLength, + "count_gt": GreaterThanLength, "length_greater_than": GreaterThanLength, + "len_ge": GreaterOrEqualsLength, + "count_ge": GreaterOrEqualsLength, "length_greater_or_equals": GreaterOrEqualsLength, - "contains": Contains, - "string_equals": EqualString, + "contained_by": ContainedBy, + "str_eq": StringEqual, + "string_equals": StringEqual, + "regex_match": RegexMatch, } -func StartsWith(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +// StartsWith check if string starts with substring +func StartsWith(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { if !assert.IsType(t, "string", actual, fmt.Sprintf("actual is %v", actual)) { return false } @@ -44,7 +61,8 @@ func StartsWith(t assert.TestingT, expected, actual interface{}, msgAndArgs ...i return assert.True(t, strings.HasPrefix(actualString, expectedString), msgAndArgs...) } -func EndsWith(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +// EndsWith check if string ends with substring +func EndsWith(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { if !assert.IsType(t, "string", actual, fmt.Sprintf("actual is %v", actual)) { return false } @@ -56,7 +74,7 @@ func EndsWith(t assert.TestingT, expected, actual interface{}, msgAndArgs ...int return assert.True(t, strings.HasSuffix(actualString, expectedString), msgAndArgs...) } -func EqualLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +func EqualLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { length, err := convertInt(expected) if err != nil { return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) @@ -65,7 +83,7 @@ func EqualLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ... return assert.Len(t, actual, length, msgAndArgs...) } -func GreaterThanLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +func GreaterThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { length, err := convertInt(expected) if err != nil { return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) @@ -80,7 +98,7 @@ func GreaterThanLength(t assert.TestingT, expected, actual interface{}, msgAndAr return true } -func GreaterOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +func GreaterOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { length, err := convertInt(expected) if err != nil { return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) @@ -95,7 +113,7 @@ func GreaterOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgA return true } -func LessThanLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +func LessThanLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { length, err := convertInt(expected) if err != nil { return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) @@ -110,7 +128,7 @@ func LessThanLength(t assert.TestingT, expected, actual interface{}, msgAndArgs return true } -func LessOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { +func LessOrEqualsLength(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { length, err := convertInt(expected) if err != nil { return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) @@ -125,6 +143,27 @@ func LessOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgAndA return true } +// ContainedBy assert whether actual element contains expected element +func ContainedBy(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { + return assert.Contains(t, expected, actual, msgAndArgs) +} + +func StringEqual(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { + if !assert.IsType(t, "string", actual, msgAndArgs) { + return false + } + if !assert.IsType(t, "string", expected, msgAndArgs) { + return false + } + actualString := actual.(string) + expectedString := expected.(string) + return assert.True(t, strings.EqualFold(actualString, expectedString), msgAndArgs) +} + +func RegexMatch(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { + return assert.Regexp(t, expected, actual, msgAndArgs) +} + func convertInt(value interface{}) (int, error) { switch v := value.(type) { case int: @@ -152,23 +191,6 @@ func convertInt(value interface{}) (int, error) { } } -// Contains assert whether actual element contains expected element -func Contains(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - return assert.Contains(t, actual, expected, msgAndArgs) -} - -func EqualString(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { - if !assert.IsType(t, "string", actual, msgAndArgs) { - return false - } - if !assert.IsType(t, "string", expected, msgAndArgs) { - return false - } - actualString := actual.(string) - expectedString := expected.(string) - return assert.True(t, strings.EqualFold(actualString, expectedString), msgAndArgs) -} - // getLen try to get length of object. // return (false, 0) if impossible. func getLen(x interface{}) (ok bool, length int) { diff --git a/internal/builtin/assertion_test.go b/internal/builtin/assertion_test.go index 52d8d5d0..d919464e 100644 --- a/internal/builtin/assertion_test.go +++ b/internal/builtin/assertion_test.go @@ -1,6 +1,7 @@ package builtin import ( + "regexp" "testing" "github.com/stretchr/testify/assert" @@ -18,7 +19,7 @@ func TestStartsWith(t *testing.T) { } for _, data := range testData { - if !assert.True(t, StartsWith(t, data.expected, data.raw)) { + if !assert.True(t, StartsWith(t, data.raw, data.expected)) { t.Fail() } } @@ -36,7 +37,7 @@ func TestEndsWith(t *testing.T) { } for _, data := range testData { - if !assert.True(t, EndsWith(t, data.expected, data.raw)) { + if !assert.True(t, EndsWith(t, data.raw, data.expected)) { t.Fail() } } @@ -56,7 +57,7 @@ func TestEqualLength(t *testing.T) { } for _, data := range testData { - if !assert.True(t, EqualLength(t, data.expected, data.raw)) { + if !assert.True(t, EqualLength(t, data.raw, data.expected)) { t.Fail() } } @@ -76,7 +77,7 @@ func TestLessThanLength(t *testing.T) { } for _, data := range testData { - if !assert.True(t, LessThanLength(t, data.expected, data.raw)) { + if !assert.True(t, LessThanLength(t, data.raw, data.expected)) { t.Fail() } } @@ -96,7 +97,7 @@ func TestLessOrEqualsLength(t *testing.T) { } for _, data := range testData { - if !assert.True(t, LessOrEqualsLength(t, data.expected, data.raw)) { + if !assert.True(t, LessOrEqualsLength(t, data.raw, data.expected)) { t.Fail() } } @@ -113,7 +114,7 @@ func TestGreaterThanLength(t *testing.T) { } for _, data := range testData { - if !assert.True(t, GreaterThanLength(t, data.expected, data.raw)) { + if !assert.True(t, GreaterThanLength(t, data.raw, data.expected)) { t.Fail() } } @@ -133,7 +134,57 @@ func TestGreaterOrEqualsLength(t *testing.T) { } for _, data := range testData { - if !assert.True(t, GreaterOrEqualsLength(t, data.expected, data.raw)) { + if !assert.True(t, GreaterOrEqualsLength(t, data.raw, data.expected)) { + t.Fail() + } + } +} + +func TestContainedBy(t *testing.T) { + testData := []struct { + raw interface{} + expected interface{} + }{ + {"abcd", "abcdefg"}, + {"a", []string{"a", "b", "c"}}, + {"A", map[string]interface{}{"A": 111, "B": 222}}, + } + + for _, data := range testData { + if !assert.True(t, ContainedBy(t, data.raw, data.expected)) { + t.Fail() + } + } +} + +func TestStringEqual(t *testing.T) { + testData := []struct { + raw interface{} + expected interface{} + }{ + {"abcd", "abcd"}, + {"abcd", "ABCD"}, + {"ABcd", "abCD"}, + } + + for _, data := range testData { + if !assert.True(t, StringEqual(t, data.raw, data.expected)) { + t.Fail() + } + } +} + +func TestRegexMatch(t *testing.T) { + testData := []struct { + raw interface{} + expected interface{} + }{ + {"it's starting...", regexp.MustCompile("start")}, + {"it's not starting", "starting$"}, + } + + for _, data := range testData { + if !assert.True(t, RegexMatch(t, data.raw, data.expected)) { t.Fail() } } diff --git a/response.go b/response.go index 94a27d5b..8dc2ebba 100644 --- a/response.go +++ b/response.go @@ -115,7 +115,10 @@ func (v *responseObject) Validate(validators []Validator, variablesMapping map[s // get assert method assertMethod := validator.Assert - assertFunc := builtin.Assertions[assertMethod] + assertFunc, ok := builtin.Assertions[assertMethod] + if !ok { + return errors.New(fmt.Sprintf("unexpected assertion method: %v", assertMethod)) + } // parse expected value expectValue, err := v.parser.parseData(validator.Expect, variablesMapping) @@ -134,7 +137,7 @@ func (v *responseObject) Validate(validators []Validator, variablesMapping map[s } // do assertion - result := assertFunc(v.t, expectValue, checkValue) + result := assertFunc(v.t, checkValue, expectValue) if result { validResult.CheckResult = "pass" }