From 6aedf35dad1ce562fdc0e68b7fc9f5e2722614f1 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Thu, 24 Feb 2022 22:32:16 +0800 Subject: [PATCH] compat: convert testcase generated by HttpRunner Change-Id: Iabc58b6796b7cd88f7b93e415b3d88dca85b288d --- convert.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ models.go | 31 +++++++++++--------- 2 files changed, 101 insertions(+), 14 deletions(-) diff --git a/convert.go b/convert.go index 396416f8..94ab969e 100644 --- a/convert.go +++ b/convert.go @@ -6,6 +6,8 @@ import ( "fmt" "os" "path/filepath" + "reflect" + "strings" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" @@ -29,6 +31,10 @@ func loadFromJSON(path string) (*TCase, error) { decoder := json.NewDecoder(bytes.NewReader(file)) decoder.UseNumber() err = decoder.Decode(tc) + if err != nil { + return tc, err + } + err = convertCompatTestCase(tc) return tc, err } @@ -48,9 +54,87 @@ func loadFromYAML(path string) (*TCase, error) { tc := &TCase{} err = yaml.Unmarshal(file, tc) + if err != nil { + return tc, nil + } + err = convertCompatTestCase(tc) return tc, err } +func convertCompatTestCase(tc *TCase) (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("convert compat testcase error: %v", p) + } + }() + for _, step := range tc.TestSteps { + // 1. deal with body compatible with HttpRunner + if step.Request.Body == nil { + if step.Request.Json != nil { + // Content-Type is "application/json; charset=UTF-8" + step.Request.Body = step.Request.Json + } else if step.Request.Data != nil { + dataValue := reflect.ValueOf(step.Request.Data) + switch dataValue.Kind() { + case reflect.String: + // Content-Type is "text/plain" + step.Request.Body = dataValue.String() + case reflect.Map: + // Content-Type is "application/x-www-form-urlencoded" + var paramsList []string + mapRange := dataValue.MapRange() + for mapRange.Next() { + paramsList = append(paramsList, fmt.Sprintf("%s=%s", mapRange.Key(), mapRange.Value())) + } + step.Request.Body = strings.Join(paramsList, "&") + default: + log.Error().Msgf("[convert compat testcase] unexpected body type: %v", dataValue.Kind()) + } + } + } + + // 2. deal with validators compatible with HttpRunner + for _, iValidator := range step.ValidatorsCompat { + validatorMap, ok := iValidator.(map[string]interface{}) + if !ok || len(validatorMap) == 0 { + // pass invalid or empty validator + continue + } + // check priority: HRP > HttpRunner + validator := Validator{} + if len(validatorMap) == 4 { + // HRP validator format + validator.Check = validatorMap["check"].(string) + validator.Assert = validatorMap["assert"].(string) + validator.Expect = validatorMap["expect"] + if msg, exist := validatorMap["msg"]; exist { + validator.Message = msg.(string) + } + } else if len(validatorMap) == 1 { + // HttpRunner validator format + validatorValue := reflect.ValueOf(validatorMap) + assertMethod := validatorValue.MapKeys()[0] + iValidatorContent := validatorValue.MapIndex(assertMethod).Interface().([]interface{}) + validator.Check = iValidatorContent[0].(string) + validator.Assert = assertMethod.String() + validator.Expect = iValidatorContent[1] + } else { + log.Error().Msgf("[convert compat testcase] unexpected validator format: %v", validatorMap) + } + // deal with headers format in HttpRunner + // e.g. headers.Content-Type => headers.\"Content-Type\" + 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 + } + step.Validators = append(step.Validators, validator) + } + } + return err +} + func (tc *TCase) ToTestCase() (*TestCase, error) { testCase := &TestCase{ Config: tc.Config, diff --git a/models.go b/models.go index f5e43cfa..050e2600 100644 --- a/models.go +++ b/models.go @@ -97,6 +97,8 @@ type Request struct { Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` Cookies map[string]string `json:"cookies,omitempty" yaml:"cookies,omitempty"` Body interface{} `json:"body,omitempty" yaml:"body,omitempty"` + Json interface{} `json:"json,omitempty" yaml:"json,omitempty"` + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` Timeout float32 `json:"timeout,omitempty" yaml:"timeout,omitempty"` AllowRedirects bool `json:"allow_redirects,omitempty" yaml:"allow_redirects,omitempty"` Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` @@ -113,17 +115,18 @@ type Validator struct { // TStep represents teststep data structure. // Each step maybe two different type: make one HTTP request or reference another testcase. type TStep struct { - Name string `json:"name" yaml:"name"` // required - Request *Request `json:"request,omitempty" yaml:"request,omitempty"` - TestCase *TestCase `json:"testcase,omitempty" yaml:"testcase,omitempty"` - Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"` - Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"` - Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` - SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"` - TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"` - Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"` - Validators []Validator `json:"validate,omitempty" yaml:"validate,omitempty"` - Export []string `json:"export,omitempty" yaml:"export,omitempty"` + Name string `json:"name" yaml:"name"` // required + Request *Request `json:"request,omitempty" yaml:"request,omitempty"` + TestCase *TestCase `json:"testcase,omitempty" yaml:"testcase,omitempty"` + Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"` + Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"` + Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` + SetupHooks []string `json:"setup_hooks,omitempty" yaml:"setup_hooks,omitempty"` + TeardownHooks []string `json:"teardown_hooks,omitempty" yaml:"teardown_hooks,omitempty"` + Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"` + ValidatorsCompat []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"` + Export []string `json:"export,omitempty" yaml:"export,omitempty"` + Validators []Validator } type stepType string @@ -301,11 +304,11 @@ type testCaseInOut struct { type testCaseSummary struct { Name string `json:"name" yaml:"name"` Success bool `json:"success" yaml:"success"` - CaseId string `json:"case_id,omitempty" yaml:"case_id,omitempty"` //TODO + CaseId string `json:"case_id,omitempty" yaml:"case_id,omitempty"` // TODO Stat *testStepStat `json:"stat" yaml:"stat"` Time *testCaseTime `json:"time" yaml:"time"` InOut *testCaseInOut `json:"in_out" yaml:"in_out"` - Log string `json:"log,omitempty" yaml:"log,omitempty"` //TODO + Log string `json:"log,omitempty" yaml:"log,omitempty"` // TODO Records []*stepData `json:"records" yaml:"records"` } @@ -330,7 +333,7 @@ type address struct { type SessionData struct { Success bool `json:"success" yaml:"success"` ReqResps *reqResps `json:"req_resps" yaml:"req_resps"` - Address *address `json:"address,omitempty" yaml:"address,omitempty"` //TODO + Address *address `json:"address,omitempty" yaml:"address,omitempty"` // TODO Validators []*validationResult `json:"validators,omitempty" yaml:"validators,omitempty"` }