Files
httprunner/convert.go
buyuxiang 6aedf35dad compat: convert testcase generated by HttpRunner
Change-Id: Iabc58b6796b7cd88f7b93e415b3d88dca85b288d
2022-02-24 22:32:16 +08:00

205 lines
5.4 KiB
Go

package hrp
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
func loadFromJSON(path string) (*TCase, error) {
path, err := filepath.Abs(path)
if err != nil {
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
return nil, err
}
log.Info().Str("path", path).Msg("load json testcase")
file, err := os.ReadFile(path)
if err != nil {
log.Error().Err(err).Msg("load json path failed")
return nil, err
}
tc := &TCase{}
decoder := json.NewDecoder(bytes.NewReader(file))
decoder.UseNumber()
err = decoder.Decode(tc)
if err != nil {
return tc, err
}
err = convertCompatTestCase(tc)
return tc, err
}
func loadFromYAML(path string) (*TCase, error) {
path, err := filepath.Abs(path)
if err != nil {
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
return nil, err
}
log.Info().Str("path", path).Msg("load yaml testcase")
file, err := os.ReadFile(path)
if err != nil {
log.Error().Err(err).Msg("load yaml path failed")
return nil, err
}
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,
}
for _, step := range tc.TestSteps {
if step.Request != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepRequestWithOptionalArgs{
step: step,
})
} else if step.TestCase != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepTestCaseWithOptionalArgs{
step: step,
})
} else if step.Transaction != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepTransaction{
step: step,
})
} else if step.Rendezvous != nil {
testCase.TestSteps = append(testCase.TestSteps, &StepRendezvous{
step: step,
})
} else {
log.Warn().Interface("step", step).Msg("[convertTestCase] unexpected step")
}
}
return testCase, nil
}
var ErrUnsupportedFileExt = fmt.Errorf("unsupported testcase file extension")
// TestCasePath implements ITestCase interface.
type TestCasePath struct {
Path string
}
func (path *TestCasePath) ToTestCase() (*TestCase, error) {
var tc *TCase
var err error
casePath := path.Path
ext := filepath.Ext(casePath)
switch ext {
case ".json":
tc, err = loadFromJSON(casePath)
case ".yaml", ".yml":
tc, err = loadFromYAML(casePath)
default:
err = ErrUnsupportedFileExt
}
if err != nil {
return nil, err
}
tc.Config.Path = path.Path
testcase, err := tc.ToTestCase()
if err != nil {
return nil, err
}
return testcase, nil
}
func (path *TestCasePath) ToTCase() (*TCase, error) {
testcase, err := path.ToTestCase()
if err != nil {
return nil, err
}
return testcase.ToTCase()
}