diff --git a/docs/BUILTIN.md b/docs/BUILTIN.md index 19c6bff5..899e822c 100644 --- a/docs/BUILTIN.md +++ b/docs/BUILTIN.md @@ -16,13 +16,13 @@ HttpRunner+ validation should follow the following format. `check`, `assert` and 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,14 +31,14 @@ 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' | +| `regex_match` | regex matches | re.match(B, A) | 'abcdef' regex_match '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' | diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 66744592..4c68900e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v0.6.1 (2022-02-11) + +- fix: assertion function errors and json number parse rule + ## v0.6.0 (2022-02-08) - feat: implement `rendezvous` mechanism for data driven diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 688e0c52..9d12a34e 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -33,4 +33,4 @@ Copyright 2021 debugtalk * [hrp run](hrp_run.md) - run API test * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 8-Feb-2022 +###### Auto generated by spf13/cobra on 15-Feb-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index e867df88..a0dcf982 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -39,4 +39,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Feb-2022 +###### Auto generated by spf13/cobra on 15-Feb-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index fd05af09..a24d244b 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -23,4 +23,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Feb-2022 \ No newline at end of file +###### Auto generated by spf13/cobra on 15-Feb-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 334cd4fd..2c781641 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -33,4 +33,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Feb-2022 +###### Auto generated by spf13/cobra on 15-Feb-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 6bfcd978..7921fc50 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -16,4 +16,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 8-Feb-2022 +###### Auto generated by spf13/cobra on 15-Feb-2022 diff --git a/examples/extract_test.go b/examples/extract_test.go index ec72277d..0d1184bd 100644 --- a/examples/extract_test.go +++ b/examples/extract_test.go @@ -62,7 +62,6 @@ func TestCaseExtractStepAssociation(t *testing.T) { WithJmesPath("body.args.foo1", "varFoo1"). Validate(). AssertEqual("$statusCode", 200, "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("$contentType", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("$varFoo1", "bar1", "check args foo1"). AssertEqual("body.args.foo2", "bar2", "check args foo2"). diff --git a/examples/request_test.go b/examples/request_test.go index 6312df07..2a8a2383 100644 --- a/examples/request_test.go +++ b/examples/request_test.go @@ -20,7 +20,6 @@ func TestCaseBasicRequest(t *testing.T) { }). Validate(). AssertEqual("status_code", 200, "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("body.args.foo1", "bar1", "check args foo1"). AssertEqual("body.args.foo2", "bar2", "check args foo2"), diff --git a/examples/validate_test.go b/examples/validate_test.go index 24d60e25..95ff040b 100644 --- a/examples/validate_test.go +++ b/examples/validate_test.go @@ -25,7 +25,6 @@ func TestCaseValidateStep(t *testing.T) { WithJmesPath("body.args.foo1", "varFoo1"). Validate(). AssertEqual("status_code", "$expectedStatusCode", "check status code"). // assert status code - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). // assert response header AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). // assert response header, with double quotes AssertEqual("body.args.foo1", "bar1", "check args foo1"). // assert response json body with jmespath AssertEqual("body.args.foo2", "bar2", "check args foo2"). diff --git a/examples/variables_test.go b/examples/variables_test.go index 9fb4c0fb..d1f856fc 100644 --- a/examples/variables_test.go +++ b/examples/variables_test.go @@ -22,7 +22,6 @@ func TestCaseConfigVariables(t *testing.T) { WithHeaders(map[string]string{"User-Agent": "$agent"}). Validate(). AssertEqual("status_code", "$expectedStatusCode", "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("body.args.foo1", "bar1", "check args foo1"). AssertEqual("body.args.foo2", "bar2", "check args foo2"). @@ -53,7 +52,6 @@ func TestCaseStepVariables(t *testing.T) { WithHeaders(map[string]string{"User-Agent": "$agent"}). Validate(). AssertEqual("status_code", "$expectedStatusCode", "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("body.args.foo1", "bar1", "check args foo1"). AssertEqual("body.args.foo2", "bar2", "check args foo2"). @@ -88,7 +86,6 @@ func TestCaseOverrideConfigVariables(t *testing.T) { WithHeaders(map[string]string{"User-Agent": "$agent"}). Validate(). AssertEqual("status_code", "$expectedStatusCode", "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("body.args.foo1", "bar1", "check args foo1"). AssertEqual("body.args.foo2", "bar2", "check args foo2"). diff --git a/internal/builtin/assertion.go b/internal/builtin/assertion.go index 6acb06ed..01eb3157 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.Greater, + "equal": assert.EqualValues, + "lt": assert.Less, "less_than": assert.Less, - "greater_or_equals": assert.GreaterOrEqual, + "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 + "startswith": StartsWith, + "endswith": 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: @@ -147,28 +186,15 @@ func convertInt(value interface{}) (int, error) { return int(v), nil case uint64: return int(v), nil + case float32: + return int(v), nil + case float64: + return int(v), nil default: return 0, fmt.Errorf("unsupported int convertion for %v(%T)", v, v) } } -// 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/parser.go b/parser.go index ce2a8fe3..692faf75 100644 --- a/parser.go +++ b/parser.go @@ -69,7 +69,8 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac case reflect.String: // json.Number if rawValue, ok := raw.(json.Number); ok { - return parseJSONNumber(rawValue) + // use the same rule as json.Unmarshal (float64, for JSON numbers) + return rawValue.Float64() } // other string value := rawValue.String() @@ -108,16 +109,6 @@ func (p *parser) parseData(raw interface{}, variablesMapping map[string]interfac } } -func parseJSONNumber(raw json.Number) (interface{}, error) { - if strings.Contains(raw.String(), ".") { - // float64 - return raw.Float64() - } else { - // int64 - return raw.Int64() - } -} - const ( regexVariable = `[a-zA-Z_]\w*` // variable name should start with a letter or underscore regexFunctionName = `[a-zA-Z_]\w*` // function name should start with a letter or underscore diff --git a/response.go b/response.go index 94a27d5b..29619160 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 assertMethod: %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" } @@ -148,10 +151,10 @@ func (v *responseObject) Validate(validators []Validator, variablesMapping map[s if !result { v.t.Fail() return errors.New(fmt.Sprintf( - "do assertion failed, assertMethod: %v, expectValue: %v, checkValue: %v", + "do assertion failed, assertMethod: %v, checkValue: %v, expectValue: %v", assertMethod, - expectValue, checkValue, + expectValue, )) } } diff --git a/step_test.go b/step_test.go index 5eb6427f..b81b48d3 100644 --- a/step_test.go +++ b/step_test.go @@ -12,7 +12,6 @@ var ( WithCookies(map[string]string{"user": "debugtalk"}). Validate(). AssertEqual("status_code", 200, "check status code"). - AssertEqual("headers.Connection", "keep-alive", "check header Connection"). AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type"). AssertEqual("body.args.foo1", "bar1", "check param foo1"). AssertEqual("body.args.foo2", "bar2", "check param foo2")