mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-08 09:19:41 +08:00
refactor: convert postman case
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -13,65 +12,86 @@ import (
|
||||
)
|
||||
|
||||
var convertCmd = &cobra.Command{
|
||||
Use: "convert $path...",
|
||||
Short: "convert to JSON/YAML/gotest/pytest testcases",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Use: "convert $path...",
|
||||
Short: "convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
SilenceUsage: false,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: convertRun,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
caseConverter := convert.NewConverter(outputDir, profilePath)
|
||||
|
||||
var fromType convert.FromType
|
||||
if fromYAMLFlag {
|
||||
fromType = convert.FromTypeYAML
|
||||
} else if fromPostmanFlag {
|
||||
fromType = convert.FromTypePostman
|
||||
} else if fromHARFlag {
|
||||
fromType = convert.FromTypeHAR
|
||||
} else {
|
||||
fromType = convert.FromTypeJSON
|
||||
log.Info().Str("fromType", fromType.String()).Msg("set default")
|
||||
}
|
||||
|
||||
var outputType convert.OutputType
|
||||
if toYAMLFlag {
|
||||
outputType = convert.OutputTypeYAML
|
||||
} else if toPyTestFlag {
|
||||
packages := []string{
|
||||
fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion),
|
||||
}
|
||||
_, err := myexec.EnsurePython3Venv(venv, packages...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("python3 venv is not ready")
|
||||
return err
|
||||
}
|
||||
|
||||
outputType = convert.OutputTypePyTest
|
||||
} else {
|
||||
outputType = convert.OutputTypeJSON
|
||||
log.Info().Str("outputType", outputType.String()).Msg("set default")
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if err := caseConverter.Convert(arg, fromType, outputType); err != nil {
|
||||
log.Error().Err(err).Str("path", arg).
|
||||
Str("outputType", outputType.String()).
|
||||
Msg("convert case failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
outputDir string
|
||||
profilePath string
|
||||
|
||||
fromJSONFlag bool
|
||||
fromYAMLFlag bool
|
||||
fromPostmanFlag bool
|
||||
fromHARFlag bool
|
||||
|
||||
toJSONFlag bool
|
||||
toYAMLFlag bool
|
||||
toGoTestFlag bool
|
||||
toPyTestFlag bool
|
||||
outputDir string
|
||||
profilePath string
|
||||
|
||||
outputType convert.OutputType
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(convertCmd)
|
||||
|
||||
convertCmd.Flags().BoolVar(&fromJSONFlag, "from-json", true, "load from json case format")
|
||||
convertCmd.Flags().BoolVar(&fromYAMLFlag, "from-yaml", false, "load from yaml case format")
|
||||
convertCmd.Flags().BoolVar(&fromHARFlag, "from-har", false, "load from HAR format")
|
||||
convertCmd.Flags().BoolVar(&fromPostmanFlag, "from-postman", false, "load from postman format")
|
||||
|
||||
convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", true, "convert to JSON case scripts")
|
||||
convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML case scripts")
|
||||
convertCmd.Flags().BoolVar(&toPyTestFlag, "to-pytest", false, "convert to pytest scripts")
|
||||
convertCmd.Flags().BoolVar(&toGoTestFlag, "to-gotest", false, "convert to gotest scripts (TODO)")
|
||||
convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", false, "convert to JSON scripts (default)")
|
||||
convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML scripts")
|
||||
convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file")
|
||||
|
||||
convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory")
|
||||
convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies")
|
||||
}
|
||||
|
||||
func convertRun(cmd *cobra.Command, args []string) error {
|
||||
var flagCount int
|
||||
if toJSONFlag {
|
||||
flagCount++
|
||||
}
|
||||
if toYAMLFlag {
|
||||
flagCount++
|
||||
outputType = convert.OutputTypeYAML
|
||||
}
|
||||
if toGoTestFlag {
|
||||
flagCount++
|
||||
outputType = convert.OutputTypeGoTest
|
||||
}
|
||||
if toPyTestFlag {
|
||||
flagCount++
|
||||
outputType = convert.OutputTypePyTest
|
||||
|
||||
packages := []string{
|
||||
fmt.Sprintf("httprunner==%s", version.HttpRunnerMinimumVersion),
|
||||
}
|
||||
_, err := myexec.EnsurePython3Venv(venv, packages...)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("python3 venv is not ready")
|
||||
return err
|
||||
}
|
||||
}
|
||||
if flagCount > 1 {
|
||||
return errors.New("please specify at most one conversion flag")
|
||||
}
|
||||
convert.Run(outputType, outputDir, profilePath, args)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/boomer"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/convert"
|
||||
)
|
||||
|
||||
var runCurlCmd = &cobra.Command{
|
||||
Use: "curl URLs",
|
||||
Short: "run API test with curl command",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
DisableFlagParsing: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
runner := makeHRPRunner()
|
||||
return runner.Run(makeCurlTestCase(args))
|
||||
},
|
||||
}
|
||||
|
||||
var boomCurlCmd = &cobra.Command{
|
||||
Use: "curl URLs",
|
||||
Short: "run load test with curl command",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
DisableFlagParsing: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
boomer.SetUlimit(10240)
|
||||
if !strings.EqualFold(logLevel, "DEBUG") {
|
||||
logLevel = "WARN" // disable info logs for load testing
|
||||
}
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
boomer, err := makeHRPBoomer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
boomer.Run(makeCurlTestCase(args))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var convertCurlCmd = &cobra.Command{
|
||||
Use: "curl URLs",
|
||||
Short: "convert curl command to httprunner testcase",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
DisableFlagParsing: true,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
setLogLevel(logLevel)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
curlCommand := makeCurlCommand(args)
|
||||
return convertRun(cmd, []string{curlCommand})
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
runCmd.AddCommand(runCurlCmd)
|
||||
boomCmd.AddCommand(boomCurlCmd)
|
||||
convertCmd.AddCommand(convertCurlCmd)
|
||||
}
|
||||
|
||||
func makeCurlTestCase(args []string) *hrp.TestCase {
|
||||
curlCommand := makeCurlCommand(args)
|
||||
tCase, err := convert.LoadSingleCurlCase(curlCommand)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert curl command failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
testCase, err := tCase.ToTestCase(env.RootDir)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert testcase to failed")
|
||||
os.Exit(1)
|
||||
}
|
||||
return testCase
|
||||
}
|
||||
|
||||
func makeCurlCommand(args []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
if !strings.HasPrefix(args[i], "-") {
|
||||
args[i] = fmt.Sprintf("\"%s\"", args[i])
|
||||
}
|
||||
}
|
||||
var curlCmd []string
|
||||
curlCmd = append(curlCmd, "curl")
|
||||
curlCmd = append(curlCmd, args...)
|
||||
return strings.Join(curlCmd, " ")
|
||||
}
|
||||
1
hrp/convert.go
Normal file
1
hrp/convert.go
Normal file
@@ -0,0 +1 @@
|
||||
package hrp
|
||||
@@ -4,32 +4,36 @@
|
||||
|
||||
```shell
|
||||
$ hrp convert -h
|
||||
convert to JSON/YAML/gotest/pytest testcases
|
||||
convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
|
||||
|
||||
Usage:
|
||||
hrp convert $path... [flags]
|
||||
|
||||
Flags:
|
||||
--from-har load from HAR format
|
||||
--from-json load from json case format (default true)
|
||||
--from-postman load from postman format
|
||||
--from-yaml load from yaml case format
|
||||
-h, --help help for convert
|
||||
-d, --output-dir string specify output directory, default to the same dir with har file
|
||||
-p, --profile string specify profile path to override headers (except for auto-generated headers) and cookies
|
||||
--to-gotest convert to gotest scripts (TODO)
|
||||
--to-json convert to JSON scripts (default true)
|
||||
-d, --output-dir string specify output directory
|
||||
-p, --profile string specify profile path to override headers and cookies
|
||||
--to-json convert to JSON case scripts (default true)
|
||||
--to-pytest convert to pytest scripts
|
||||
--to-yaml convert to YAML scripts
|
||||
--to-yaml convert to YAML case scripts
|
||||
|
||||
Global Flags:
|
||||
--log-json set log to json format
|
||||
-l, --log-level string set log level (default "INFO")
|
||||
--venv string specify python3 venv path
|
||||
```
|
||||
|
||||
`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 文件或 curl/Apache ab 指令转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化。
|
||||
`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 文件或 curl/Apache ab 指令转化为 HttpRunner JSON/YAML/gotest/pytest 形态的测试用例,同时也支持 HttpRunner 测试用例各个形态之间的相互转化。
|
||||
|
||||
该指令所有选项的详细说明如下:
|
||||
|
||||
1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例
|
||||
2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹
|
||||
3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例:
|
||||
- `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的 HttpRunner 测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例
|
||||
- `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹;默认输出的文件夹为源文件所在的文件夹
|
||||
- `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例:
|
||||
|
||||
- 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息
|
||||
|
||||
@@ -52,10 +56,9 @@ cookies:
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 输出的测试用例文件名格式为 `Postman 工程文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖
|
||||
2. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行
|
||||
3. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式
|
||||
4. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格
|
||||
1. 输出的测试用例文件名格式为 `源文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖
|
||||
2. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式
|
||||
3. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格
|
||||
|
||||
|
||||
## 转换流程图
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||
)
|
||||
|
||||
// target testcase format extensions
|
||||
const (
|
||||
suffixJSON = ".json"
|
||||
suffixYAML = ".yaml"
|
||||
suffixGoTest = ".go"
|
||||
suffixPyTest = ".py"
|
||||
)
|
||||
|
||||
type OutputType int
|
||||
|
||||
const (
|
||||
OutputTypeJSON OutputType = iota // default output type: JSON
|
||||
OutputTypeYAML
|
||||
OutputTypeGoTest
|
||||
OutputTypePyTest
|
||||
)
|
||||
|
||||
func (outputType OutputType) String() string {
|
||||
switch outputType {
|
||||
case OutputTypeYAML:
|
||||
return "yaml"
|
||||
case OutputTypeGoTest:
|
||||
return "gotest"
|
||||
case OutputTypePyTest:
|
||||
return "pytest"
|
||||
default:
|
||||
return "json"
|
||||
}
|
||||
}
|
||||
|
||||
// Profile is used to override or update(create if not existed) original headers and cookies
|
||||
type Profile struct {
|
||||
Override bool `json:"override" yaml:"override"`
|
||||
Headers map[string]string `json:"headers" yaml:"headers"`
|
||||
Cookies map[string]string `json:"cookies" yaml:"cookies"`
|
||||
}
|
||||
|
||||
func Run(outputType OutputType, outputDir, profilePath string, args []string) {
|
||||
// report event
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
Category: "ConvertTests",
|
||||
Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()),
|
||||
})
|
||||
|
||||
var outputFiles []string
|
||||
for _, inputSample := range args {
|
||||
// loads source file and convert to TCase format
|
||||
tCase, err := LoadTCase(inputSample)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("input sample", inputSample).Msg("convert input sample failed")
|
||||
continue
|
||||
}
|
||||
|
||||
caseConverter := &TCaseConverter{
|
||||
InputSample: inputSample,
|
||||
OutputDir: outputDir,
|
||||
TCase: tCase,
|
||||
}
|
||||
|
||||
// override TCase with profile
|
||||
if profilePath != "" {
|
||||
caseConverter.overrideWithProfile(profilePath)
|
||||
}
|
||||
|
||||
// convert TCase format to target case format
|
||||
var outputFile string
|
||||
switch outputType {
|
||||
case OutputTypeYAML:
|
||||
outputFile, err = caseConverter.ToYAML()
|
||||
case OutputTypeGoTest:
|
||||
outputFile, err = caseConverter.ToGoTest()
|
||||
case OutputTypePyTest:
|
||||
outputFile, err = caseConverter.ToPyTest()
|
||||
default:
|
||||
outputFile, err = caseConverter.ToJSON()
|
||||
}
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("input sample", caseConverter.InputSample).
|
||||
Msg("convert case failed")
|
||||
continue
|
||||
}
|
||||
outputFiles = append(outputFiles, outputFile)
|
||||
}
|
||||
log.Info().Strs("output files", outputFiles).Msg("conversion completed")
|
||||
}
|
||||
|
||||
// LoadTCase loads source file and convert to TCase type
|
||||
func LoadTCase(inputSample string) (*hrp.TCase, error) {
|
||||
if strings.HasPrefix(inputSample, "curl ") {
|
||||
// 'path' contains curl command
|
||||
curlCase, err := LoadSingleCurlCase(inputSample)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return curlCase, nil
|
||||
}
|
||||
extName := filepath.Ext(inputSample)
|
||||
if extName == "" {
|
||||
return nil, errors.New("file extension is not specified")
|
||||
}
|
||||
switch extName {
|
||||
case ".har":
|
||||
tCase, err := LoadHARCase(inputSample)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tCase, nil
|
||||
case ".json":
|
||||
// priority: hrp JSON case > postman > swagger
|
||||
// check if hrp JSON case
|
||||
tCase, err := LoadJSONCase(inputSample)
|
||||
if err == nil {
|
||||
return tCase, nil
|
||||
}
|
||||
|
||||
// check if postman format
|
||||
casePostman, err := LoadPostmanCase(inputSample)
|
||||
if err == nil {
|
||||
return casePostman, nil
|
||||
}
|
||||
|
||||
// check if swagger format
|
||||
caseSwagger, err := LoadSwaggerCase(inputSample)
|
||||
if err == nil {
|
||||
return caseSwagger, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unexpected JSON format")
|
||||
case ".yaml", ".yml":
|
||||
// priority: hrp YAML case > swagger
|
||||
// check if hrp YAML case
|
||||
tCase, err := NewYAMLCase(inputSample)
|
||||
if err == nil {
|
||||
return tCase, nil
|
||||
}
|
||||
|
||||
// check if swagger format
|
||||
caseSwagger, err := LoadSwaggerCase(inputSample)
|
||||
if err == nil {
|
||||
return caseSwagger, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unexpected YAML format")
|
||||
case ".go": // TODO
|
||||
return nil, errors.New("convert gotest is not implemented")
|
||||
case ".py": // TODO
|
||||
return nil, errors.New("convert pytest is not implemented")
|
||||
case ".jmx": // TODO
|
||||
return nil, errors.New("convert JMeter jmx is not implemented")
|
||||
case ".txt":
|
||||
curlCase, err := LoadCurlCase(inputSample)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return curlCase, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported file type: %v", extName)
|
||||
}
|
||||
|
||||
// TCaseConverter holds the common properties of case converter
|
||||
type TCaseConverter struct {
|
||||
InputSample string
|
||||
OutputDir string
|
||||
TCase *hrp.TCase
|
||||
}
|
||||
|
||||
func (c *TCaseConverter) genOutputPath(suffix string) string {
|
||||
var outFileFullName string
|
||||
if curlCmd := strings.TrimSpace(c.InputSample); strings.HasPrefix(curlCmd, "curl ") {
|
||||
outFileFullName = fmt.Sprintf("curl_%v_test%v", env.StartTimeStr, suffix)
|
||||
if c.OutputDir != "" {
|
||||
return filepath.Join(c.OutputDir, outFileFullName)
|
||||
} else {
|
||||
return filepath.Join(env.RootDir, outFileFullName)
|
||||
}
|
||||
}
|
||||
outFileFullName = builtin.GetFileNameWithoutExtension(c.InputSample) + "_test" + suffix
|
||||
if c.OutputDir != "" {
|
||||
return filepath.Join(c.OutputDir, outFileFullName)
|
||||
} else {
|
||||
return filepath.Join(filepath.Dir(c.InputSample), outFileFullName)
|
||||
}
|
||||
// TODO avoid outFileFullName conflict?
|
||||
}
|
||||
|
||||
// convert TCase to pytest case
|
||||
func (c *TCaseConverter) ToPyTest() (string, error) {
|
||||
jsonPath, err := c.ToJSON()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "convert to JSON case failed")
|
||||
}
|
||||
|
||||
args := append([]string{"make"}, jsonPath)
|
||||
err = myexec.ExecPython3Command("httprunner", args...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.genOutputPath(suffixPyTest), nil
|
||||
}
|
||||
|
||||
// TODO: convert TCase to gotest case
|
||||
func (c *TCaseConverter) ToGoTest() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// convert TCase to JSON case
|
||||
func (c *TCaseConverter) ToJSON() (string, error) {
|
||||
jsonPath := c.genOutputPath(suffixJSON)
|
||||
err := builtin.Dump2JSON(c.TCase, jsonPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsonPath, nil
|
||||
}
|
||||
|
||||
// convert TCase to YAML case
|
||||
func (c *TCaseConverter) ToYAML() (string, error) {
|
||||
yamlPath := c.genOutputPath(suffixYAML)
|
||||
err := builtin.Dump2YAML(c.TCase, yamlPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return yamlPath, nil
|
||||
}
|
||||
|
||||
func (c *TCaseConverter) overrideWithProfile(path string) error {
|
||||
log.Info().Str("path", path).Msg("load profile")
|
||||
profile := new(Profile)
|
||||
err := builtin.LoadFile(path, profile)
|
||||
if err != nil {
|
||||
log.Warn().Str("path", path).
|
||||
Msg("failed to load profile, ignore!")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Interface("profile", profile).Msg("override with profile")
|
||||
for _, step := range c.TCase.TestSteps {
|
||||
// override original headers and cookies
|
||||
if profile.Override {
|
||||
step.Request.Headers = make(map[string]string)
|
||||
step.Request.Cookies = make(map[string]string)
|
||||
}
|
||||
// update (create if not existed) original headers and cookies
|
||||
if step.Request.Headers == nil {
|
||||
step.Request.Headers = make(map[string]string)
|
||||
}
|
||||
if step.Request.Cookies == nil {
|
||||
step.Request.Cookies = make(map[string]string)
|
||||
}
|
||||
for k, v := range profile.Headers {
|
||||
step.Request.Headers[k] = v
|
||||
}
|
||||
for k, v := range profile.Cookies {
|
||||
step.Request.Cookies[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1
hrp/pkg/convert/from_ab.go
Normal file
1
hrp/pkg/convert/from_ab.go
Normal file
@@ -0,0 +1 @@
|
||||
package convert
|
||||
@@ -31,9 +31,9 @@ func convert2GoTestScripts(paths ...string) error {
|
||||
for _, testCase := range testCases {
|
||||
tc := testCase.ToTCase()
|
||||
converter := TCaseConverter{
|
||||
TCase: tc,
|
||||
tCase: tc,
|
||||
}
|
||||
pytestPath, err := converter.ToPyTest()
|
||||
pytestPath, err := converter.toPyTest()
|
||||
if err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("originPath", tc.Config.Path).
|
||||
|
||||
1
hrp/pkg/convert/from_jmeter.go
Normal file
1
hrp/pkg/convert/from_jmeter.go
Normal file
@@ -0,0 +1 @@
|
||||
package convert
|
||||
@@ -2,13 +2,14 @@ package convert
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
func LoadJSONCase(path string) (*hrp.TCase, error) {
|
||||
// load json case file
|
||||
log.Info().Str("path", path).Msg("load json case file")
|
||||
caseJSON := new(hrp.TCase)
|
||||
err := builtin.LoadFile(path, caseJSON)
|
||||
if err != nil {
|
||||
|
||||
@@ -113,7 +113,7 @@ var contentTypeMap = map[string]string{
|
||||
}
|
||||
|
||||
func LoadPostmanCase(path string) (*hrp.TCase, error) {
|
||||
// load postman file
|
||||
log.Info().Str("path", path).Msg("load postman case file")
|
||||
casePostman, err := loadCasePostman(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
func NewYAMLCase(path string) (*hrp.TCase, error) {
|
||||
func LoadYAMLCase(path string) (*hrp.TCase, error) {
|
||||
// load yaml case file
|
||||
caseJSON := new(hrp.TCase)
|
||||
err := builtin.LoadFile(path, caseJSON)
|
||||
|
||||
205
hrp/pkg/convert/main.go
Normal file
205
hrp/pkg/convert/main.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||
)
|
||||
|
||||
// target testcase format extensions
|
||||
const (
|
||||
suffixJSON = ".json"
|
||||
suffixYAML = ".yaml"
|
||||
suffixGoTest = ".go"
|
||||
suffixPyTest = ".py"
|
||||
)
|
||||
|
||||
type FromType int
|
||||
|
||||
const (
|
||||
FromTypeJSON FromType = iota
|
||||
FromTypeYAML
|
||||
FromTypeHAR
|
||||
FromTypePostman
|
||||
FromTypeCurl
|
||||
FromTypeSwagger
|
||||
FromTypePyest
|
||||
FromTypeGotest
|
||||
)
|
||||
|
||||
func (fromType FromType) String() string {
|
||||
switch fromType {
|
||||
case FromTypeYAML:
|
||||
return "yaml"
|
||||
case FromTypeHAR:
|
||||
return "har"
|
||||
case FromTypePostman:
|
||||
return "postman"
|
||||
case FromTypeSwagger:
|
||||
return "swagger"
|
||||
case FromTypeCurl:
|
||||
return "curl"
|
||||
case FromTypeGotest:
|
||||
return "gotest"
|
||||
case FromTypePyest:
|
||||
return "pytest"
|
||||
default:
|
||||
return "json"
|
||||
}
|
||||
}
|
||||
|
||||
type OutputType int
|
||||
|
||||
const (
|
||||
OutputTypeJSON OutputType = iota // default output type: JSON
|
||||
OutputTypeYAML
|
||||
OutputTypeGoTest
|
||||
OutputTypePyTest
|
||||
)
|
||||
|
||||
func (outputType OutputType) String() string {
|
||||
switch outputType {
|
||||
case OutputTypeYAML:
|
||||
return "yaml"
|
||||
case OutputTypeGoTest:
|
||||
return "gotest"
|
||||
case OutputTypePyTest:
|
||||
return "pytest"
|
||||
default:
|
||||
return "json"
|
||||
}
|
||||
}
|
||||
|
||||
// Profile is used to override or update(create if not existed) original headers and cookies
|
||||
type Profile struct {
|
||||
Override bool `json:"override" yaml:"override"`
|
||||
Headers map[string]string `json:"headers" yaml:"headers"`
|
||||
Cookies map[string]string `json:"cookies" yaml:"cookies"`
|
||||
}
|
||||
|
||||
func NewConverter(outputDir, profilePath string) *TCaseConverter {
|
||||
return &TCaseConverter{
|
||||
profilePath: profilePath,
|
||||
outputDir: outputDir,
|
||||
}
|
||||
}
|
||||
|
||||
// TCaseConverter holds the common properties of case converter
|
||||
type TCaseConverter struct {
|
||||
fromFile string
|
||||
profilePath string
|
||||
outputDir string
|
||||
tCase *hrp.TCase
|
||||
}
|
||||
|
||||
// LoadCase loads source file and convert to TCase type
|
||||
func (c *TCaseConverter) loadCase(casePath string, fromType FromType) error {
|
||||
c.fromFile = casePath
|
||||
var err error
|
||||
switch fromType {
|
||||
case FromTypeJSON:
|
||||
c.tCase, err = LoadJSONCase(casePath)
|
||||
case FromTypeYAML:
|
||||
c.tCase, err = LoadYAMLCase(casePath)
|
||||
case FromTypeHAR:
|
||||
c.tCase, err = LoadHARCase(casePath)
|
||||
case FromTypePostman:
|
||||
c.tCase, err = LoadPostmanCase(casePath)
|
||||
case FromTypeSwagger:
|
||||
c.tCase, err = LoadSwaggerCase(casePath)
|
||||
case FromTypeCurl:
|
||||
c.tCase, err = LoadCurlCase(casePath)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *TCaseConverter) Convert(casePath string, fromType FromType, outputType OutputType) error {
|
||||
// report event
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
Category: "ConvertTests",
|
||||
Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()),
|
||||
})
|
||||
log.Info().Str("path", casePath).
|
||||
Str("fromType", fromType.String()).
|
||||
Str("outputType", outputType.String()).
|
||||
Msg("convert testcase")
|
||||
|
||||
// load source file
|
||||
err := c.loadCase(casePath, fromType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// override TCase with profile
|
||||
if c.profilePath != "" {
|
||||
c.overrideWithProfile(c.profilePath)
|
||||
}
|
||||
|
||||
// convert to target format
|
||||
var outputFile string
|
||||
switch outputType {
|
||||
case OutputTypeYAML:
|
||||
outputFile, err = c.toYAML()
|
||||
case OutputTypeGoTest:
|
||||
outputFile, err = c.toGoTest()
|
||||
case OutputTypePyTest:
|
||||
outputFile, err = c.toPyTest()
|
||||
default:
|
||||
outputFile, err = c.toJSON()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Str("outputFile", outputFile).Msg("conversion completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TCaseConverter) genOutputPath(suffix string) string {
|
||||
outFileFullName := builtin.GetFileNameWithoutExtension(c.fromFile) + "_test" + suffix
|
||||
if c.outputDir != "" {
|
||||
return filepath.Join(c.outputDir, outFileFullName)
|
||||
} else {
|
||||
return filepath.Join(filepath.Dir(c.fromFile), outFileFullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TCaseConverter) overrideWithProfile(path string) error {
|
||||
log.Info().Str("path", path).Msg("load profile")
|
||||
profile := new(Profile)
|
||||
err := builtin.LoadFile(path, profile)
|
||||
if err != nil {
|
||||
log.Warn().Str("path", path).
|
||||
Msg("failed to load profile, ignore!")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Interface("profile", profile).Msg("override with profile")
|
||||
for _, step := range c.tCase.TestSteps {
|
||||
// override original headers and cookies
|
||||
if profile.Override {
|
||||
step.Request.Headers = make(map[string]string)
|
||||
step.Request.Cookies = make(map[string]string)
|
||||
}
|
||||
// update (create if not existed) original headers and cookies
|
||||
if step.Request.Headers == nil {
|
||||
step.Request.Headers = make(map[string]string)
|
||||
}
|
||||
if step.Request.Cookies == nil {
|
||||
step.Request.Cookies = make(map[string]string)
|
||||
}
|
||||
for k, v := range profile.Headers {
|
||||
step.Request.Headers[k] = v
|
||||
}
|
||||
for k, v := range profile.Cookies {
|
||||
step.Request.Cookies[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -13,31 +13,33 @@ const (
|
||||
profileOverridePath = "../../../examples/data/profile_override.yml"
|
||||
)
|
||||
|
||||
var converter *TCaseConverter
|
||||
|
||||
func init() {
|
||||
converter = NewConverter("", "")
|
||||
}
|
||||
|
||||
func TestLoadTCase(t *testing.T) {
|
||||
tCase, err := LoadTCase(harPath)
|
||||
err := converter.loadCase(harPath, FromTypeHAR)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
if !assert.NotEmpty(t, tCase) {
|
||||
if !assert.NotEmpty(t, converter.tCase) {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadHARWithProfileOverride(t *testing.T) {
|
||||
tCase, err := LoadTCase(harPath)
|
||||
err := converter.loadCase(harPath, FromTypeHAR)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
if !assert.NotEmpty(t, tCase) {
|
||||
if !assert.NotEmpty(t, converter.tCase) {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
caseConverter := &TCaseConverter{
|
||||
TCase: tCase,
|
||||
}
|
||||
|
||||
// override TCase with profile
|
||||
err = caseConverter.overrideWithProfile(profileOverridePath)
|
||||
err = converter.overrideWithProfile(profileOverridePath)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
@@ -45,12 +47,12 @@ func TestLoadHARWithProfileOverride(t *testing.T) {
|
||||
for i := 0; i < 3; i++ {
|
||||
if !assert.Equal(t,
|
||||
map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
caseConverter.TCase.TestSteps[i].Request.Headers) {
|
||||
converter.tCase.TestSteps[i].Request.Headers) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
map[string]string{"UserName": "debugtalk"},
|
||||
caseConverter.TCase.TestSteps[i].Request.Cookies) {
|
||||
converter.tCase.TestSteps[i].Request.Cookies) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
@@ -58,7 +60,7 @@ func TestLoadHARWithProfileOverride(t *testing.T) {
|
||||
|
||||
func TestMakeRequestWithProfile(t *testing.T) {
|
||||
caseConverter := &TCaseConverter{
|
||||
TCase: &hrp.TCase{
|
||||
tCase: &hrp.TCase{
|
||||
TestSteps: []*hrp.TStep{
|
||||
{
|
||||
Request: &hrp.Request{
|
||||
@@ -87,19 +89,19 @@ func TestMakeRequestWithProfile(t *testing.T) {
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "hrp",
|
||||
}, caseConverter.TCase.TestSteps[0].Request.Headers) {
|
||||
}, caseConverter.tCase.TestSteps[0].Request.Headers) {
|
||||
t.Fatal()
|
||||
}
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"UserName": "debugtalk", "abc": "123",
|
||||
}, caseConverter.TCase.TestSteps[0].Request.Cookies) {
|
||||
}, caseConverter.tCase.TestSteps[0].Request.Cookies) {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestWithProfileOverride(t *testing.T) {
|
||||
caseConverter := &TCaseConverter{
|
||||
TCase: &hrp.TCase{
|
||||
tCase: &hrp.TCase{
|
||||
TestSteps: []*hrp.TStep{
|
||||
{
|
||||
Request: &hrp.Request{
|
||||
@@ -129,12 +131,12 @@ func TestMakeRequestWithProfileOverride(t *testing.T) {
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}, caseConverter.TCase.TestSteps[0].Request.Headers) {
|
||||
}, caseConverter.tCase.TestSteps[0].Request.Headers) {
|
||||
t.Fatal()
|
||||
}
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"UserName": "debugtalk",
|
||||
}, caseConverter.TCase.TestSteps[0].Request.Cookies) {
|
||||
}, caseConverter.tCase.TestSteps[0].Request.Cookies) {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
6
hrp/pkg/convert/to_gotest.go
Normal file
6
hrp/pkg/convert/to_gotest.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package convert
|
||||
|
||||
// TODO: convert TCase to gotest case
|
||||
func (c *TCaseConverter) toGoTest() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
13
hrp/pkg/convert/to_json.go
Normal file
13
hrp/pkg/convert/to_json.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package convert
|
||||
|
||||
import "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
|
||||
// convert TCase to JSON case
|
||||
func (c *TCaseConverter) toJSON() (string, error) {
|
||||
jsonPath := c.genOutputPath(suffixJSON)
|
||||
err := builtin.Dump2JSON(c.tCase, jsonPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsonPath, nil
|
||||
}
|
||||
22
hrp/pkg/convert/to_pytest.go
Normal file
22
hrp/pkg/convert/to_pytest.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/myexec"
|
||||
)
|
||||
|
||||
// convert TCase to pytest case
|
||||
func (c *TCaseConverter) toPyTest() (string, error) {
|
||||
jsonPath, err := c.toJSON()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "convert to JSON case failed")
|
||||
}
|
||||
|
||||
args := append([]string{"make"}, jsonPath)
|
||||
err = myexec.ExecPython3Command("httprunner", args...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.genOutputPath(suffixPyTest), nil
|
||||
}
|
||||
13
hrp/pkg/convert/to_yaml.go
Normal file
13
hrp/pkg/convert/to_yaml.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package convert
|
||||
|
||||
import "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||
|
||||
// convert TCase to YAML case
|
||||
func (c *TCaseConverter) toYAML() (string, error) {
|
||||
yamlPath := c.genOutputPath(suffixYAML)
|
||||
err := builtin.Dump2YAML(c.tCase, yamlPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return yamlPath, nil
|
||||
}
|
||||
Reference in New Issue
Block a user