diff --git a/convert.go b/convert.go index c83b36e7..7bbad0c6 100644 --- a/convert.go +++ b/convert.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" @@ -80,48 +79,40 @@ func convertCompatTestCase(tc *TCase) (err error) { // 2. deal with validators compatible with HttpRunner for i, iValidator := range step.Validators { validatorMap := iValidator.(map[string]interface{}) - // check priority: HRP > HttpRunner validator := Validator{} - if len(validatorMap) == 4 || len(validatorMap) == 3 { + _, checkExisted := validatorMap["check"] + _, assertExisted := validatorMap["assert"] + _, expectExisted := validatorMap["expect"] + // check priority: HRP > HttpRunner + if checkExisted && assertExisted && expectExisted { // HRP validator format validator.Check = validatorMap["check"].(string) validator.Assert = validatorMap["assert"].(string) validator.Expect = validatorMap["expect"] - if msg, exist := validatorMap["msg"]; exist { + if msg, existed := validatorMap["msg"]; existed { validator.Message = msg.(string) } - convertCompatHeader(&validator) step.Validators[i] = validator } else if len(validatorMap) == 1 { // HttpRunner validator format for assertMethod, iValidatorContent := range validatorMap { checkAndExpect := iValidatorContent.([]interface{}) + if len(checkAndExpect) != 2 { + return fmt.Errorf("unexpected validator format: %v", validatorMap) + } validator.Check = checkAndExpect[0].(string) validator.Assert = assertMethod validator.Expect = checkAndExpect[1] } - convertCompatHeader(&validator) step.Validators[i] = validator } else { - log.Error().Msgf("[convert compat testcase] unexpected validator format: %v", validatorMap) - step.Validators[i] = validator + return fmt.Errorf("unexpected validator format: %v", validatorMap) } } } return err } -// convertCompatHeader deals with headers format in HttpRunner -// e.g. headers.Content-Type => headers.\"Content-Type\" -func convertCompatHeader(validator *Validator) { - if strings.Contains(validator.Check, "headers.") && - !strings.Contains(validator.Check, "\"") && - strings.Contains(validator.Check, "-") { - replacedHeader := fmt.Sprintf("headers.\"%s\"", validator.Check[len("headers."):]) - validator.Check = replacedHeader - } -} - func (tc *TCase) ToTestCase() (*TestCase, error) { testCase := &TestCase{ Config: tc.Config, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c8d6ab2a..f00f32c2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v0.6.3 (2022-02-22) +## v0.6.3 (2022-03-03) - feat: support customized setup/teardown hooks (variable assignment not supported) - compat: support testcase generated by HttpRunner diff --git a/examples/compat_test.go b/examples/compat_test.go new file mode 100644 index 00000000..76edc545 --- /dev/null +++ b/examples/compat_test.go @@ -0,0 +1,24 @@ +package examples + +import ( + "testing" + + "github.com/httprunner/hrp" +) + +const demoHttpRunnerJSONPath = "../examples/demo_httprunner.json" +const demoHttpRunnerYAMLPath = "../examples/demo_httprunner.yaml" + +func TestCompatTestCase(t *testing.T) { + testcaseFromJSON := &hrp.TestCasePath{Path: demoHttpRunnerJSONPath} + err := hrp.NewRunner(t).Run(testcaseFromJSON) + if err != nil { + t.Fatalf("run testcase error: %v", err) + } + + testcaseFromYAML := &hrp.TestCasePath{Path: demoHttpRunnerYAMLPath} + err = hrp.NewRunner(t).Run(testcaseFromYAML) + if err != nil { + t.Fatalf("run testcase error: %v", err) + } +} diff --git a/examples/demo_httprunner.json b/examples/demo_httprunner.json new file mode 100644 index 00000000..de017c96 --- /dev/null +++ b/examples/demo_httprunner.json @@ -0,0 +1,135 @@ +{ + "config": { + "name": "testcase description", + "variables": {}, + "verify": false + }, + "teststeps": [ + { + "name": "/get", + "request": { + "url": "https://postman-echo.com/get", + "params": { + "foo1": "HDnY8", + "foo2": "34.5" + }, + "method": "GET", + "headers": { + "Host": "postman-echo.com", + "User-Agent": "HttpRunnerPlus", + "Accept-Encoding": "gzip" + } + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "headers.Content-Type", + "application/json; charset=utf-8" + ] + }, + { + "eq": [ + "body.url", + "https://postman-echo.com/get?foo1=HDnY8&foo2=34.5" + ] + } + ] + }, + { + "name": "/post", + "request": { + "url": "https://postman-echo.com/post", + "method": "POST", + "cookies": { + "sails.sid": "s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk" + }, + "headers": { + "Host": "postman-echo.com", + "User-Agent": "Go-http-client/1.1", + "Content-Length": "28", + "Content-Type": "application/json; charset=UTF-8", + "Cookie": "sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk", + "Accept-Encoding": "gzip" + }, + "json": { + "foo1": "HDnY8", + "foo2": 12.3 + } + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "headers.Content-Type", + "application/json; charset=utf-8" + ] + }, + { + "eq": [ + "body.url", + "https://postman-echo.com/post" + ] + } + ] + }, + { + "name": "/post", + "request": { + "url": "https://postman-echo.com/post", + "method": "POST", + "cookies": { + "sails.sid": "s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw" + }, + "headers": { + "Host": "postman-echo.com", + "User-Agent": "Go-http-client/1.1", + "Content-Length": "20", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "Cookie": "sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw", + "Accept-Encoding": "gzip" + }, + "data": { + "foo1": "HDnY8", + "foo2": "12.3" + } + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "headers.Content-Type", + "application/json; charset=utf-8" + ] + }, + { + "eq": [ + "body.data", + "" + ] + }, + { + "eq": [ + "body.url", + "https://postman-echo.com/post" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo_httprunner.yaml b/examples/demo_httprunner.yaml new file mode 100644 index 00000000..0f39723f --- /dev/null +++ b/examples/demo_httprunner.yaml @@ -0,0 +1,81 @@ +config: + name: testcase description + variables: {} + verify: false +teststeps: +- name: /get + request: + headers: + Accept-Encoding: gzip + Host: postman-echo.com + User-Agent: HttpRunnerPlus + method: GET + params: + foo1: HDnY8 + foo2: '34.5' + url: https://postman-echo.com/get + validate: + - eq: + - status_code + - 200 + - eq: + - headers.Content-Type + - application/json; charset=utf-8 + - eq: + - body.url + - https://postman-echo.com/get?foo1=HDnY8&foo2=34.5 +- name: /post + request: + cookies: + sails.sid: s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk + headers: + Accept-Encoding: gzip + Content-Length: '28' + Content-Type: application/json; charset=UTF-8 + Cookie: sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk + Host: postman-echo.com + User-Agent: Go-http-client/1.1 + json: + foo1: HDnY8 + foo2: 12.3 + method: POST + url: https://postman-echo.com/post + validate: + - eq: + - status_code + - 200 + - eq: + - headers.Content-Type + - application/json; charset=utf-8 + - eq: + - body.url + - https://postman-echo.com/post +- name: /post + request: + cookies: + sails.sid: s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw + data: + foo1: HDnY8 + foo2: '12.3' + headers: + Accept-Encoding: gzip + Content-Length: '20' + Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + Cookie: sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw + Host: postman-echo.com + User-Agent: Go-http-client/1.1 + method: POST + url: https://postman-echo.com/post + validate: + - eq: + - status_code + - 200 + - eq: + - headers.Content-Type + - application/json; charset=utf-8 + - eq: + - body.data + - '' + - eq: + - body.url + - https://postman-echo.com/post diff --git a/internal/har2case/core_test.go b/internal/har2case/core_test.go index 9060893f..db71fbd2 100644 --- a/internal/har2case/core_test.go +++ b/internal/har2case/core_test.go @@ -1,10 +1,11 @@ package har2case import ( - "github.com/httprunner/hrp" "testing" "github.com/stretchr/testify/assert" + + "github.com/httprunner/hrp" ) var ( @@ -99,13 +100,16 @@ func TestMakeTestCase(t *testing.T) { } // make validators - if validator, ok := tCase.TestSteps[0].Validators[0].(hrp.Validator); !ok || !assert.Equal(t, "status_code", validator.Check) { + validator, ok := tCase.TestSteps[0].Validators[0].(hrp.Validator) + if !ok || !assert.Equal(t, "status_code", validator.Check) { t.Fail() } - if validator, ok := tCase.TestSteps[0].Validators[1].(hrp.Validator); !ok || !assert.Equal(t, "headers.\"Content-Type\"", validator.Check) { + validator, ok = tCase.TestSteps[0].Validators[1].(hrp.Validator) + if !ok || !assert.Equal(t, "headers.\"Content-Type\"", validator.Check) { t.Fail() } - if validator, ok := tCase.TestSteps[0].Validators[2].(hrp.Validator); !ok || !assert.Equal(t, "body.url", validator.Check) { + validator, ok = tCase.TestSteps[0].Validators[2].(hrp.Validator) + if !ok || !assert.Equal(t, "body.url", validator.Check) { t.Fail() } } diff --git a/response.go b/response.go index 1085c382..3ed46dc0 100644 --- a/response.go +++ b/response.go @@ -185,6 +185,7 @@ func (v *responseObject) Validate(iValidators []interface{}, variablesMapping ma } func (v *responseObject) searchJmespath(expr string) interface{} { + expr = convertJmespath(expr) checkValue, err := jmespath.Search(expr, v.respObjMeta) if err != nil { log.Error().Str("expr", expr).Err(err).Msg("search jmespath failed") @@ -200,6 +201,20 @@ func (v *responseObject) searchJmespath(expr string) interface{} { return checkValue } +// convertJmespath deals with check expression including hyphen +func convertJmespath(checkExpr string) string { + if strings.Contains(checkExpr, textExtractorSubRegexp) { + return checkExpr + } + checkItems := strings.Split(checkExpr, ".") + for i, checkItem := range checkItems { + if strings.Contains(checkItem, "-") && !strings.Contains(checkItem, "\"") { + checkItems[i] = fmt.Sprintf("\"%s\"", checkItem) + } + } + return strings.Join(checkItems, ".") +} + func (v *responseObject) searchRegexp(expr string) interface{} { respMap, ok := v.respObjMeta.(map[string]interface{}) if !ok { diff --git a/response_test.go b/response_test.go index cfd3143d..89aa9003 100644 --- a/response_test.go +++ b/response_test.go @@ -33,3 +33,30 @@ func TestSearchRegexp(t *testing.T) { } } } + +func Test_convertJmespath(t *testing.T) { + exprs := []struct { + before string + after string + }{ + // normal check expression + {"a.b.c", "a.b.c"}, + {"headers.\"Content-Type\"", "headers.\"Content-Type\""}, + // check expression using regex + {"covering (.*) testing,", "covering (.*) testing,"}, + {" (.*) a-b-c", " (.*) a-b-c"}, + // abnormal check expression + {"-", "\"-\""}, + {"b-c", "\"b-c\""}, + {"a.b-c.d", "a.\"b-c\".d"}, + {"a-b.c-d", "\"a-b\".\"c-d\""}, + {"\"a-b\".c-d", "\"a-b\".\"c-d\""}, + {"headers.Content-Type", "headers.\"Content-Type\""}, + {"body.I-am-a-Key.name", "body.\"I-am-a-Key\".name"}, + } + for _, expr := range exprs { + if !assert.Equal(t, convertJmespath(expr.before), expr.after) { + t.Fail() + } + } +} diff --git a/step_test.go b/step_test.go index 1610cd29..64d03785 100644 --- a/step_test.go +++ b/step_test.go @@ -42,7 +42,8 @@ func TestRunRequestGetToStruct(t *testing.T) { if tStep.Request.Cookies["user"] != "debugtalk" { t.Fatalf("tStep.Request.Cookies mismatch") } - if validator, ok := tStep.Validators[0].(Validator); !ok || validator.Check != "status_code" || validator.Expect != 200 { + validator, ok := tStep.Validators[0].(Validator) + if !ok || validator.Check != "status_code" || validator.Expect != 200 { t.Fatalf("tStep.Validators mismatch") } } @@ -67,7 +68,8 @@ func TestRunRequestPostDataToStruct(t *testing.T) { if tStep.Request.Body != "a=1&b=2" { t.Fatalf("tStep.Request.Data mismatch") } - if validator, ok := tStep.Validators[0].(Validator); !ok || validator.Check != "status_code" || validator.Expect != 200 { + validator, ok := tStep.Validators[0].(Validator) + if !ok || validator.Check != "status_code" || validator.Expect != 200 { t.Fatalf("tStep.Validators mismatch") } }