diff --git a/hrp/compat.go b/hrp/compat.go new file mode 100644 index 00000000..7be46871 --- /dev/null +++ b/hrp/compat.go @@ -0,0 +1,173 @@ +package hrp + +import ( + "fmt" + "strings" + + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" + "github.com/pkg/errors" +) + +// ConvertCaseCompatibility converts TestCase compatible with Golang engine style +func ConvertCaseCompatibility(tc *TestCaseDef) (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p) + } + }() + for _, step := range tc.Steps { + // 1. deal with request body compatibility + convertCompatRequestBody(step.Request) + + // 2. deal with validators compatibility + err = convertCompatValidator(step.Validators) + if err != nil { + return err + } + + // 3. deal with extract expr including hyphen + convertExtract(step.Extract) + + // 4. deal with mobile step compatibility + if step.Android != nil { + convertCompatMobileStep(step.Android) + } else if step.IOS != nil { + convertCompatMobileStep(step.IOS) + } else if step.Harmony != nil { + convertCompatMobileStep(step.Harmony) + } + } + return nil +} + +func convertCompatRequestBody(request *Request) { + if request != nil && request.Body == nil { + if request.Json != nil { + if request.Headers == nil { + request.Headers = make(map[string]string) + } + request.Headers["Content-Type"] = "application/json; charset=utf-8" + request.Body = request.Json + request.Json = nil + } else if request.Data != nil { + request.Body = request.Data + request.Data = nil + } + } +} + +func convertCompatValidator(Validators []interface{}) (err error) { + for i, iValidator := range Validators { + if _, ok := iValidator.(Validator); ok { + continue + } + + var validatorMap map[string]interface{} + if v, ok := iValidator.(map[string]interface{}); ok { + validatorMap = v + } else if v, ok := iValidator.(map[interface{}]interface{}); ok { + // convert map[interface{}]interface{} to map[string]interface{} + validatorMap = make(map[string]interface{}) + for key, value := range v { + strKey := fmt.Sprintf("%v", key) + validatorMap[strKey] = value + } + } else { + return errors.Wrap(code.InvalidCaseError, + fmt.Sprintf("unexpected validator format: %v", iValidator)) + } + + validator := Validator{} + iCheck, checkExisted := validatorMap["check"] + iAssert, assertExisted := validatorMap["assert"] + iExpect, expectExisted := validatorMap["expect"] + // validator check priority: Golang > Python engine style + if checkExisted && assertExisted && expectExisted { + // Golang engine style + validator.Check = iCheck.(string) + validator.Assert = iAssert.(string) + validator.Expect = iExpect + if iMsg, msgExisted := validatorMap["msg"]; msgExisted { + validator.Message = iMsg.(string) + } + validator.Check = convertJmespathExpr(validator.Check) + Validators[i] = validator + continue + } + if len(validatorMap) == 1 { + // Python engine style + for assertMethod, iValidatorContent := range validatorMap { + validatorContent := iValidatorContent.([]interface{}) + if len(validatorContent) > 3 { + return errors.Wrap(code.InvalidCaseError, + fmt.Sprintf("unexpected validator format: %v", validatorMap)) + } + validator.Check = validatorContent[0].(string) + validator.Assert = assertMethod + validator.Expect = validatorContent[1] + if len(validatorContent) == 3 { + validator.Message = validatorContent[2].(string) + } + } + validator.Check = convertJmespathExpr(validator.Check) + Validators[i] = validator + continue + } + return errors.Wrap(code.InvalidCaseError, + fmt.Sprintf("unexpected validator format: %v", validatorMap)) + } + return nil +} + +// convertExtract deals with extract expr including hyphen +func convertExtract(extract map[string]string) { + for key, value := range extract { + extract[key] = convertJmespathExpr(value) + } +} + +func convertCompatMobileStep(mobileUI *MobileUI) { + if mobileUI == nil { + return + } + for i := 0; i < len(mobileUI.Actions); i++ { + ma := mobileUI.Actions[i] + actionOptions := uixt.NewActionOptions(ma.GetOptions()...) + // append tap_cv params to screenshot_with_ui_types option + if ma.Method == uixt.ACTION_TapByCV { + uiTypes, _ := builtin.ConvertToStringSlice(ma.Params) + ma.ActionOptions.ScreenShotWithUITypes = append(ma.ActionOptions.ScreenShotWithUITypes, uiTypes...) + ma.ActionOptions.ScreenShotWithUpload = true + } + // set default max_retry_times to 10 for swipe_to_tap_texts + if ma.Method == uixt.ACTION_SwipeToTapTexts && actionOptions.MaxRetryTimes == 0 { + ma.ActionOptions.MaxRetryTimes = 10 + } + // set default max_retry_times to 10 for swipe_to_tap_text + if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 { + ma.ActionOptions.MaxRetryTimes = 10 + } + if ma.Method == uixt.ACTION_Swipe { + ma.ActionOptions.Direction = ma.Params + } + mobileUI.Actions[i] = ma + } +} + +// convertJmespathExpr deals with limited jmespath expression conversion +func convertJmespathExpr(checkExpr string) string { + if strings.Contains(checkExpr, textExtractorSubRegexp) { + return checkExpr + } + checkItems := strings.Split(checkExpr, ".") + for i, checkItem := range checkItems { + checkItem = strings.Trim(checkItem, "\"") + lowerItem := strings.ToLower(checkItem) + if strings.HasPrefix(lowerItem, "content-") || lowerItem == "user-agent" { + checkItems[i] = fmt.Sprintf("\"%s\"", checkItem) + } + } + return strings.Join(checkItems, ".") +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 783c7ca0..4e808fb4 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v5.0.0+2411092306 +v5.0.0+2411092321 diff --git a/hrp/pkg/convert/from_curl.go b/hrp/pkg/convert/from_curl.go index c18a6a9c..3f2a4dc9 100644 --- a/hrp/pkg/convert/from_curl.go +++ b/hrp/pkg/convert/from_curl.go @@ -111,7 +111,7 @@ func LoadCurlCase(path string) (*hrp.TestCaseDef, error) { } tCase.Steps = append(tCase.Steps, tSteps...) } - err = tCase.MakeCompat() + err = hrp.ConvertCaseCompatibility(tCase) if err != nil { return nil, err } diff --git a/hrp/pkg/convert/from_har.go b/hrp/pkg/convert/from_har.go index 89696eae..948947b7 100644 --- a/hrp/pkg/convert/from_har.go +++ b/hrp/pkg/convert/from_har.go @@ -391,7 +391,7 @@ func (c *CaseHar) ToTestCase() (*hrp.TestCaseDef, error) { Config: c.prepareConfig(), Steps: teststeps, } - err = tCase.MakeCompat() + err = hrp.ConvertCaseCompatibility(tCase) if err != nil { return nil, err } diff --git a/hrp/pkg/convert/from_json.go b/hrp/pkg/convert/from_json.go index 0cf69573..c57b5577 100644 --- a/hrp/pkg/convert/from_json.go +++ b/hrp/pkg/convert/from_json.go @@ -19,7 +19,7 @@ func LoadJSONCase(path string) (*hrp.TestCaseDef, error) { return nil, errors.New("invalid json case file, missing teststeps") } - err = caseJSON.MakeCompat() + err = hrp.ConvertCaseCompatibility(caseJSON) if err != nil { return nil, err } diff --git a/hrp/pkg/convert/from_postman.go b/hrp/pkg/convert/from_postman.go index 89774aa2..a43b2e37 100644 --- a/hrp/pkg/convert/from_postman.go +++ b/hrp/pkg/convert/from_postman.go @@ -144,7 +144,7 @@ func (c *CasePostman) ToTestCase() (*hrp.TestCaseDef, error) { Config: c.prepareConfig(), Steps: teststeps, } - err = tCase.MakeCompat() + err = hrp.ConvertCaseCompatibility(tCase) if err != nil { return nil, err } diff --git a/hrp/pkg/convert/from_yaml.go b/hrp/pkg/convert/from_yaml.go index 02b04f34..25c8a767 100644 --- a/hrp/pkg/convert/from_yaml.go +++ b/hrp/pkg/convert/from_yaml.go @@ -19,7 +19,7 @@ func LoadYAMLCase(path string) (*hrp.TestCaseDef, error) { return nil, errors.New("invalid yaml file") } - err = caseJSON.MakeCompat() + err = hrp.ConvertCaseCompatibility(caseJSON) if err != nil { return nil, err } diff --git a/hrp/testcase.go b/hrp/testcase.go index 5f00ca58..dc675497 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -3,7 +3,6 @@ package hrp import ( "fmt" "path/filepath" - "strings" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -11,15 +10,8 @@ import ( "github.com/httprunner/httprunner/v4/hrp/code" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" ) -// define struct for testcase -type TestCaseDef struct { - Config *TConfig `json:"config" yaml:"config"` - Steps []*TStep `json:"teststeps" yaml:"teststeps"` -} - // ITestCase represents interface for testcases, // includes TestCase and TestCasePath. type ITestCase interface { @@ -60,36 +52,6 @@ func (tc *TestCase) GetTestCase() (*TestCase, error) { return tc, nil } -// MakeCompat converts TestCase compatible with Golang engine style -func (tc *TestCaseDef) MakeCompat() (err error) { - defer func() { - if p := recover(); p != nil { - err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p) - } - }() - for _, step := range tc.Steps { - // 1. deal with request body compatibility - convertCompatRequestBody(step.Request) - - // 2. deal with validators compatibility - err = convertCompatValidator(step.Validators) - if err != nil { - return err - } - - // 3. deal with extract expr including hyphen - convertExtract(step.Extract) - - // 4. deal with mobile step compatibility - if step.Android != nil { - convertCompatMobileStep(step.Android) - } else if step.IOS != nil { - convertCompatMobileStep(step.IOS) - } - } - return nil -} - func (tc *TestCase) Dump2JSON(targetPath string) error { err := builtin.Dump2JSON(tc, targetPath) if err != nil { @@ -106,13 +68,19 @@ func (tc *TestCase) Dump2YAML(targetPath string) error { return nil } +// define struct for testcase +type TestCaseDef struct { + Config *TConfig `json:"config" yaml:"config"` + Steps []*TStep `json:"teststeps" yaml:"teststeps"` +} + // loadISteps loads TestSteps([]IStep) from TSteps structs func (tc *TestCaseDef) loadISteps() (*TestCase, error) { testCase := &TestCase{ Config: tc.Config, } - err := tc.MakeCompat() + err := ConvertCaseCompatibility(tc) if err != nil { return nil, err } @@ -280,133 +248,3 @@ func (tc *TestCaseDef) loadISteps() (*TestCase, error) { } return testCase, nil } - -func convertCompatRequestBody(request *Request) { - if request != nil && request.Body == nil { - if request.Json != nil { - if request.Headers == nil { - request.Headers = make(map[string]string) - } - request.Headers["Content-Type"] = "application/json; charset=utf-8" - request.Body = request.Json - request.Json = nil - } else if request.Data != nil { - request.Body = request.Data - request.Data = nil - } - } -} - -func convertCompatValidator(Validators []interface{}) (err error) { - for i, iValidator := range Validators { - if _, ok := iValidator.(Validator); ok { - continue - } - - var validatorMap map[string]interface{} - if v, ok := iValidator.(map[string]interface{}); ok { - validatorMap = v - } else if v, ok := iValidator.(map[interface{}]interface{}); ok { - // convert map[interface{}]interface{} to map[string]interface{} - validatorMap = make(map[string]interface{}) - for key, value := range v { - strKey := fmt.Sprintf("%v", key) - validatorMap[strKey] = value - } - } else { - return errors.Wrap(code.InvalidCaseError, - fmt.Sprintf("unexpected validator format: %v", iValidator)) - } - - validator := Validator{} - iCheck, checkExisted := validatorMap["check"] - iAssert, assertExisted := validatorMap["assert"] - iExpect, expectExisted := validatorMap["expect"] - // validator check priority: Golang > Python engine style - if checkExisted && assertExisted && expectExisted { - // Golang engine style - validator.Check = iCheck.(string) - validator.Assert = iAssert.(string) - validator.Expect = iExpect - if iMsg, msgExisted := validatorMap["msg"]; msgExisted { - validator.Message = iMsg.(string) - } - validator.Check = convertJmespathExpr(validator.Check) - Validators[i] = validator - continue - } - if len(validatorMap) == 1 { - // Python engine style - for assertMethod, iValidatorContent := range validatorMap { - validatorContent := iValidatorContent.([]interface{}) - if len(validatorContent) > 3 { - return errors.Wrap(code.InvalidCaseError, - fmt.Sprintf("unexpected validator format: %v", validatorMap)) - } - validator.Check = validatorContent[0].(string) - validator.Assert = assertMethod - validator.Expect = validatorContent[1] - if len(validatorContent) == 3 { - validator.Message = validatorContent[2].(string) - } - } - validator.Check = convertJmespathExpr(validator.Check) - Validators[i] = validator - continue - } - return errors.Wrap(code.InvalidCaseError, - fmt.Sprintf("unexpected validator format: %v", validatorMap)) - } - return nil -} - -// convertExtract deals with extract expr including hyphen -func convertExtract(extract map[string]string) { - for key, value := range extract { - extract[key] = convertJmespathExpr(value) - } -} - -func convertCompatMobileStep(mobileUI *MobileUI) { - if mobileUI == nil { - return - } - for i := 0; i < len(mobileUI.Actions); i++ { - ma := mobileUI.Actions[i] - actionOptions := uixt.NewActionOptions(ma.GetOptions()...) - // append tap_cv params to screenshot_with_ui_types option - if ma.Method == uixt.ACTION_TapByCV { - uiTypes, _ := builtin.ConvertToStringSlice(ma.Params) - ma.ActionOptions.ScreenShotWithUITypes = append(ma.ActionOptions.ScreenShotWithUITypes, uiTypes...) - ma.ActionOptions.ScreenShotWithUpload = true - } - // set default max_retry_times to 10 for swipe_to_tap_texts - if ma.Method == uixt.ACTION_SwipeToTapTexts && actionOptions.MaxRetryTimes == 0 { - ma.ActionOptions.MaxRetryTimes = 10 - } - // set default max_retry_times to 10 for swipe_to_tap_text - if ma.Method == uixt.ACTION_SwipeToTapText && actionOptions.MaxRetryTimes == 0 { - ma.ActionOptions.MaxRetryTimes = 10 - } - if ma.Method == uixt.ACTION_Swipe { - ma.ActionOptions.Direction = ma.Params - } - mobileUI.Actions[i] = ma - } -} - -// convertJmespathExpr deals with limited jmespath expression conversion -func convertJmespathExpr(checkExpr string) string { - if strings.Contains(checkExpr, textExtractorSubRegexp) { - return checkExpr - } - checkItems := strings.Split(checkExpr, ".") - for i, checkItem := range checkItems { - checkItem = strings.Trim(checkItem, "\"") - lowerItem := strings.ToLower(checkItem) - if strings.HasPrefix(lowerItem, "content-") || lowerItem == "user-agent" { - checkItems[i] = fmt.Sprintf("\"%s\"", checkItem) - } - } - return strings.Join(checkItems, ".") -}