From 60accfb8eb1b3d0708aea603b5d373f75c469814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 29 Dec 2021 14:46:05 +0800 Subject: [PATCH 1/3] add interface request verification method. --- internal/builtin/assertion.go | 85 +++++++++++++++++++++++++++++++++-- parser_test.go | 38 ++++++++++++---- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/internal/builtin/assertion.go b/internal/builtin/assertion.go index 31110221..a6395566 100644 --- a/internal/builtin/assertion.go +++ b/internal/builtin/assertion.go @@ -2,6 +2,7 @@ package builtin import ( "fmt" + "reflect" "strings" "github.com/stretchr/testify/assert" @@ -18,10 +19,14 @@ var Assertions = map[string]func(t assert.TestingT, expected interface{}, actual "contains": assert.Contains, "regex_match": assert.Regexp, // custom assertions - "startswith": StartsWith, // check if string starts with substring - "endswith": EndsWith, // check if string ends with substring - "length_equals": EqualLength, - "length_equal": EqualLength, // alias for length_equals + "startswith": StartsWith, // check if string starts with substring + "endswith": EndsWith, // check if string ends with substring + "length_equals": EqualLength, + "length_equal": EqualLength, // alias for length_equals + "length_less_than": LessThanLength, + "length_less_or_equals": LessOrEqualsLength, + "length_greater_than": GreaterThanLength, + "length_greater_or_equals": GreaterOrEqualsLength, } func StartsWith(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { @@ -57,6 +62,66 @@ 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 { + length, err := convertInt(expected) + if err != nil { + return assert.Fail(t, fmt.Sprintf("expected type is not int, got %#v", expected), msgAndArgs...) + } + ok, l := getLen(actual) + if !ok { + return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) + } + if l > length { + return assert.Fail(t, fmt.Sprintf("\"%s\" should be more than %d item(s), but has %d", actual, length, l), msgAndArgs...) + } + return true +} + +func GreaterOrEqualsLength(t assert.TestingT, expected, actual 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...) + } + ok, l := getLen(actual) + if !ok { + return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) + } + if l >= length { + return assert.Fail(t, fmt.Sprintf("\"%s\" should be no less than %d item(s), but has %d", actual, length, l), msgAndArgs...) + } + return true +} + +func LessThanLength(t assert.TestingT, expected, actual 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...) + } + ok, l := getLen(actual) + if !ok { + return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) + } + if l < length { + return assert.Fail(t, fmt.Sprintf("\"%s\" should be less than %d item(s), but has %d", actual, length, l), msgAndArgs...) + } + return true +} + +func LessOrEqualsLength(t assert.TestingT, expected, actual 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...) + } + ok, l := getLen(actual) + if !ok { + return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) + } + if l <= length { + return assert.Fail(t, fmt.Sprintf("\"%s\" should be no more than %d item(s), but has %d", actual, length, l), msgAndArgs...) + } + return true +} + func convertInt(value interface{}) (int, error) { switch v := value.(type) { case int: @@ -83,3 +148,15 @@ func convertInt(value interface{}) (int, error) { return 0, fmt.Errorf("unsupported int convertion for %v(%T)", v, v) } } + +// getLen try to get length of object. +// return (false, 0) if impossible. +func getLen(x interface{}) (ok bool, length int) { + v := reflect.ValueOf(x) + defer func() { + if e := recover(); e != nil { + ok = false + } + }() + return true, v.Len() +} diff --git a/parser_test.go b/parser_test.go index 194f70f1..cb1bcd8f 100644 --- a/parser_test.go +++ b/parser_test.go @@ -625,8 +625,11 @@ func TestParseParameters(t *testing.T) { expectVars []map[string]interface{} }{ { - map[string]interface{}{"username-password": "${parameterize(examples/account.csv)}", "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, - []map[string]interface{}{{"username": "test1", "password": "111111", "user_agent": "IOS/10.1"}, + map[string]interface{}{ + "username-password": "${parameterize(examples/account.csv)}", + "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + []map[string]interface{}{ + {"username": "test1", "password": "111111", "user_agent": "IOS/10.1"}, {"username": "test1", "password": "111111", "user_agent": "IOS/10.2"}, {"username": "test2", "password": "222222", "user_agent": "IOS/10.1"}, {"username": "test2", "password": "222222", "user_agent": "IOS/10.2"}, @@ -634,12 +637,23 @@ func TestParseParameters(t *testing.T) { {"username": "test3", "password": "333333", "user_agent": "IOS/10.2"}}, }, { - map[string]interface{}{}, - nil, + map[string]interface{}{ + "username-password": [][]interface{}{{"test1", "111111"}, {"test2", "222222"}, {"test3", "333333"}}, + "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}, + "app_version": []interface{}{0.3}}, + []map[string]interface{}{ + {"username": "test1", "password": "111111", "user_agent": "IOS/10.1", "app_version": 0.3}, + {"username": "test1", "password": "111111", "user_agent": "IOS/10.2", "app_version": 0.3}, + {"username": "test2", "password": "222222", "user_agent": "IOS/10.1", "app_version": 0.3}, + {"username": "test2", "password": "222222", "user_agent": "IOS/10.2", "app_version": 0.3}, + {"username": "test3", "password": "333333", "user_agent": "IOS/10.1", "app_version": 0.3}, + {"username": "test3", "password": "333333", "user_agent": "IOS/10.2", "app_version": 0.3}}, }, { - nil, - nil, + map[string]interface{}{}, nil, + }, + { + nil, nil, }, } for _, data := range testData { @@ -655,13 +669,19 @@ func TestParseParametersError(t *testing.T) { rawVars map[string]interface{} }{ { - map[string]interface{}{"username_password": "${parameterize(examples/account.csv)}", "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + map[string]interface{}{ + "username_password": "${parameterize(examples/account.csv)}", + "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, { - map[string]interface{}{"username-password": "${parameterize(examples/account.csv)}", "user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + map[string]interface{}{ + "username-password": "${parameterize(examples/account.csv)}", + "user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, { - map[string]interface{}{"username-password": "${param(examples/account.csv)}", "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, + map[string]interface{}{ + "username-password": "${param(examples/account.csv)}", + "user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}}, }, } for _, data := range testData { From 86b50e6d0d6dd8ccc589bc07281c5c934684165b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 29 Dec 2021 15:23:36 +0800 Subject: [PATCH 2/3] add interface request verification method. --- convert.go | 37 +------------- internal/builtin/assertion.go | 8 ++-- internal/builtin/assertion_test.go | 77 ++++++++++++++++++++++++++++++ internal/builtin/function.go | 40 ++++++++++++++-- validate.go | 44 +++++++++++++++++ 5 files changed, 164 insertions(+), 42 deletions(-) diff --git a/convert.go b/convert.go index 3d9880eb..7cdedfae 100644 --- a/convert.go +++ b/convert.go @@ -2,15 +2,12 @@ package hrp import ( "bytes" - "encoding/csv" "encoding/json" "fmt" - "io/ioutil" - "path/filepath" - "strings" - "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" + "io/ioutil" + "path/filepath" ) func (tc *TCase) Dump2JSON(path string) error { @@ -96,36 +93,6 @@ func loadFromYAML(path string) (*TCase, error) { return tc, err } -func LoadFromCSV(path string) []map[string]string { - path, err := filepath.Abs(path) - if err != nil { - log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") - panic(err) - } - log.Info().Str("path", path).Msg("load csv file") - - file, err := ioutil.ReadFile(path) - if err != nil { - log.Error().Err(err).Msg("load csv file failed") - panic(err) - } - r := csv.NewReader(strings.NewReader(string(file))) - content, err := r.ReadAll() - if err != nil { - log.Error().Err(err).Msg("parse csv file failed") - panic(err) - } - var result []map[string]string - for i := 1; i < len(content); i++ { - row := make(map[string]string) - for j := 0; j < len(content[i]); j++ { - row[content[0][j]] = content[i][j] - } - result = append(result, row) - } - return result -} - func (tc *TCase) ToTestCase() (*TestCase, error) { testCase := &TestCase{ Config: &Config{cfg: tc.Config}, diff --git a/internal/builtin/assertion.go b/internal/builtin/assertion.go index a6395566..c995e26e 100644 --- a/internal/builtin/assertion.go +++ b/internal/builtin/assertion.go @@ -71,7 +71,7 @@ func GreaterThanLength(t assert.TestingT, expected, actual interface{}, msgAndAr if !ok { return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) } - if l > length { + if l <= length { return assert.Fail(t, fmt.Sprintf("\"%s\" should be more than %d item(s), but has %d", actual, length, l), msgAndArgs...) } return true @@ -86,7 +86,7 @@ func GreaterOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgA if !ok { return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) } - if l >= length { + if l < length { return assert.Fail(t, fmt.Sprintf("\"%s\" should be no less than %d item(s), but has %d", actual, length, l), msgAndArgs...) } return true @@ -101,7 +101,7 @@ func LessThanLength(t assert.TestingT, expected, actual interface{}, msgAndArgs if !ok { return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) } - if l < length { + if l >= length { return assert.Fail(t, fmt.Sprintf("\"%s\" should be less than %d item(s), but has %d", actual, length, l), msgAndArgs...) } return true @@ -116,7 +116,7 @@ func LessOrEqualsLength(t assert.TestingT, expected, actual interface{}, msgAndA if !ok { return assert.Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", actual), msgAndArgs...) } - if l <= length { + if l > length { return assert.Fail(t, fmt.Sprintf("\"%s\" should be no more than %d item(s), but has %d", actual, length, l), msgAndArgs...) } return true diff --git a/internal/builtin/assertion_test.go b/internal/builtin/assertion_test.go index 632dad30..52d8d5d0 100644 --- a/internal/builtin/assertion_test.go +++ b/internal/builtin/assertion_test.go @@ -61,3 +61,80 @@ func TestEqualLength(t *testing.T) { } } } + +func TestLessThanLength(t *testing.T) { + testData := []struct { + raw interface{} + expected int + }{ + {"", 1}, + {[]string{}, 1}, + {map[string]interface{}{}, 1}, + {"a", 2}, + {[]string{"a"}, 2}, + {map[string]interface{}{"a": 123}, 2}, + } + + for _, data := range testData { + if !assert.True(t, LessThanLength(t, data.expected, data.raw)) { + t.Fail() + } + } +} + +func TestLessOrEqualsLength(t *testing.T) { + testData := []struct { + raw interface{} + expected int + }{ + {"", 1}, + {[]string{}, 1}, + {map[string]interface{}{"A": 111}, 1}, + {"a", 1}, + {[]string{"a"}, 2}, + {map[string]interface{}{"a": 123}, 2}, + } + + for _, data := range testData { + if !assert.True(t, LessOrEqualsLength(t, data.expected, data.raw)) { + t.Fail() + } + } +} + +func TestGreaterThanLength(t *testing.T) { + testData := []struct { + raw interface{} + expected int + }{ + {"abcd", 3}, + {[]string{"a", "b", "c"}, 2}, + {map[string]interface{}{"a": 123, "b": 223, "c": 323}, 2}, + } + + for _, data := range testData { + if !assert.True(t, GreaterThanLength(t, data.expected, data.raw)) { + t.Fail() + } + } +} + +func TestGreaterOrEqualsLength(t *testing.T) { + testData := []struct { + raw interface{} + expected int + }{ + {"abcd", 3}, + {[]string{"w"}, 1}, + {map[string]interface{}{"A": 111}, 1}, + {"a", 1}, + {[]string{"a", "b", "c"}, 2}, + {map[string]interface{}{"a": 123, "b": 223, "c": 323}, 2}, + } + + for _, data := range testData { + if !assert.True(t, GreaterOrEqualsLength(t, data.expected, data.raw)) { + t.Fail() + } + } +} diff --git a/internal/builtin/function.go b/internal/builtin/function.go index be7c129e..3d5c96de 100644 --- a/internal/builtin/function.go +++ b/internal/builtin/function.go @@ -2,10 +2,14 @@ package builtin import ( "crypto/md5" + "encoding/csv" "encoding/hex" - "github.com/httprunner/hrp" + "github.com/rs/zerolog/log" + "io/ioutil" "math" "math/rand" + "path/filepath" + "strings" "time" ) @@ -15,8 +19,8 @@ var Functions = map[string]interface{}{ "gen_random_string": genRandomString, // call with one argument "max": math.Max, // call with two arguments "md5": MD5, - "parameterize": hrp.LoadFromCSV, - "P": hrp.LoadFromCSV, + "parameterize": LoadFromCSV, + "P": LoadFromCSV, } func init() { @@ -47,3 +51,33 @@ func MD5(str string) string { hasher.Write([]byte(str)) return hex.EncodeToString(hasher.Sum(nil)) } + +func LoadFromCSV(path string) []map[string]string { + path, err := filepath.Abs(path) + if err != nil { + log.Error().Str("path", path).Err(err).Msg("convert absolute path failed") + panic(err) + } + log.Info().Str("path", path).Msg("load csv file") + + file, err := ioutil.ReadFile(path) + if err != nil { + log.Error().Err(err).Msg("load csv file failed") + panic(err) + } + r := csv.NewReader(strings.NewReader(string(file))) + content, err := r.ReadAll() + if err != nil { + log.Error().Err(err).Msg("parse csv file failed") + panic(err) + } + var result []map[string]string + for i := 1; i < len(content); i++ { + row := make(map[string]string) + for j := 0; j < len(content[i]); j++ { + row[content[0][j]] = content[i][j] + } + result = append(result, row) + } + return result +} diff --git a/validate.go b/validate.go index f5d67a8a..f8113779 100644 --- a/validate.go +++ b/validate.go @@ -67,3 +67,47 @@ func (s *StepRequestValidation) AssertLengthEqual(jmesPath string, expected inte s.step.Validators = append(s.step.Validators, v) return s } + +func (s *StepRequestValidation) AssertLengthLessThan(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "length_less_than", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertLengthLessOrEquals(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "length_less_or_equals", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertLengthGreaterThan(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "length_greater_than", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertLengthGreaterOrEquals(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "length_greater_or_equals", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} From c1d1d060de1a5f6d957d6bef04754cad3cad3d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E8=81=AA?= Date: Wed, 29 Dec 2021 16:43:43 +0800 Subject: [PATCH 3/3] add interface request verification method. --- validate.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/validate.go b/validate.go index f8113779..9464ea1b 100644 --- a/validate.go +++ b/validate.go @@ -35,6 +35,83 @@ func (s *StepRequestValidation) AssertEqual(jmesPath string, expected interface{ return s } +func (s *StepRequestValidation) AssertGreater(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "greater_than", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertLess(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "less_than", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertGreaterOrEqual(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "greater_or_equals", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertLessOrEqual(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "less_or_equals", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertNotEqual(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "not_equal", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertContains(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "contains", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + +func (s *StepRequestValidation) AssertRegexp(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "regex_match", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + func (s *StepRequestValidation) AssertStartsWith(jmesPath string, expected interface{}, msg string) *StepRequestValidation { v := Validator{ Check: jmesPath,