refactor: convert postman case

This commit is contained in:
debugtalk
2022-12-23 12:28:29 +08:00
parent 22a300d9b3
commit ea014c0734
17 changed files with 371 additions and 459 deletions
+67 -47
View File
@@ -1,7 +1,6 @@
package cmd package cmd
import ( import (
"errors"
"fmt" "fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -13,65 +12,86 @@ import (
) )
var convertCmd = &cobra.Command{ var convertCmd = &cobra.Command{
Use: "convert $path...", Use: "convert $path...",
Short: "convert to JSON/YAML/gotest/pytest testcases", Short: "convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
SilenceUsage: false,
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
setLogLevel(logLevel) 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 ( var (
outputDir string
profilePath string
fromJSONFlag bool
fromYAMLFlag bool
fromPostmanFlag bool
fromHARFlag bool
toJSONFlag bool toJSONFlag bool
toYAMLFlag bool toYAMLFlag bool
toGoTestFlag bool
toPyTestFlag bool toPyTestFlag bool
outputDir string
profilePath string
outputType convert.OutputType
) )
func init() { func init() {
rootCmd.AddCommand(convertCmd) 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(&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().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory")
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(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") 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
}
-98
View File
@@ -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
View File
@@ -0,0 +1 @@
package hrp
+17 -14
View File
@@ -4,32 +4,36 @@
```shell ```shell
$ hrp convert -h $ hrp convert -h
convert to JSON/YAML/gotest/pytest testcases convert multiple source format to HttpRunner JSON/YAML/gotest/pytest cases
Usage: Usage:
hrp convert $path... [flags] hrp convert $path... [flags]
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 -h, --help help for convert
-d, --output-dir string specify output directory, default to the same dir with har file -d, --output-dir string specify output directory
-p, --profile string specify profile path to override headers (except for auto-generated headers) and cookies -p, --profile string specify profile path to override headers and cookies
--to-gotest convert to gotest scripts (TODO) --to-json convert to JSON case scripts (default true)
--to-json convert to JSON scripts (default true)
--to-pytest convert to pytest scripts --to-pytest convert to pytest scripts
--to-yaml convert to YAML scripts --to-yaml convert to YAML case scripts
Global Flags: Global Flags:
--log-json set log to json format --log-json set log to json format
-l, --log-level string set log level (default "INFO") -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 形态的测试用例 - `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的 HttpRunner 测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例
2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹 - `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹;默认输出的文件夹为源文件所在的文件夹
3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers``Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: - `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers``Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例:
- 根据 profile 替换指定的 `Headers``Cookies` 信息 - 根据 profile 替换指定的 `Headers``Cookies` 信息
@@ -52,10 +56,9 @@ cookies:
## 注意事项 ## 注意事项
1. 输出的测试用例文件名格式为 `Postman 工程文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖 1. 输出的测试用例文件名格式为 `文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖
2. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行 2. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式
3. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 3. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格
4. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格
## 转换流程图 ## 转换流程图
-278
View File
@@ -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
View File
@@ -0,0 +1 @@
package convert
+2 -2
View File
@@ -31,9 +31,9 @@ func convert2GoTestScripts(paths ...string) error {
for _, testCase := range testCases { for _, testCase := range testCases {
tc := testCase.ToTCase() tc := testCase.ToTCase()
converter := TCaseConverter{ converter := TCaseConverter{
TCase: tc, tCase: tc,
} }
pytestPath, err := converter.ToPyTest() pytestPath, err := converter.toPyTest()
if err != nil { if err != nil {
log.Error().Err(err). log.Error().Err(err).
Str("originPath", tc.Config.Path). Str("originPath", tc.Config.Path).
+1
View File
@@ -0,0 +1 @@
package convert
+2 -1
View File
@@ -2,13 +2,14 @@ package convert
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
) )
func LoadJSONCase(path string) (*hrp.TCase, error) { 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) caseJSON := new(hrp.TCase)
err := builtin.LoadFile(path, caseJSON) err := builtin.LoadFile(path, caseJSON)
if err != nil { if err != nil {
+1 -1
View File
@@ -113,7 +113,7 @@ var contentTypeMap = map[string]string{
} }
func LoadPostmanCase(path string) (*hrp.TCase, error) { func LoadPostmanCase(path string) (*hrp.TCase, error) {
// load postman file log.Info().Str("path", path).Msg("load postman case file")
casePostman, err := loadCasePostman(path) casePostman, err := loadCasePostman(path)
if err != nil { if err != nil {
return nil, err return nil, err
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "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 // load yaml case file
caseJSON := new(hrp.TCase) caseJSON := new(hrp.TCase)
err := builtin.LoadFile(path, caseJSON) err := builtin.LoadFile(path, caseJSON)
+205
View 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" profileOverridePath = "../../../examples/data/profile_override.yml"
) )
var converter *TCaseConverter
func init() {
converter = NewConverter("", "")
}
func TestLoadTCase(t *testing.T) { func TestLoadTCase(t *testing.T) {
tCase, err := LoadTCase(harPath) err := converter.loadCase(harPath, FromTypeHAR)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fatal() t.Fatal()
} }
if !assert.NotEmpty(t, tCase) { if !assert.NotEmpty(t, converter.tCase) {
t.Fatal() t.Fatal()
} }
} }
func TestLoadHARWithProfileOverride(t *testing.T) { func TestLoadHARWithProfileOverride(t *testing.T) {
tCase, err := LoadTCase(harPath) err := converter.loadCase(harPath, FromTypeHAR)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fatal() t.Fatal()
} }
if !assert.NotEmpty(t, tCase) { if !assert.NotEmpty(t, converter.tCase) {
t.Fatal() t.Fatal()
} }
caseConverter := &TCaseConverter{
TCase: tCase,
}
// override TCase with profile // override TCase with profile
err = caseConverter.overrideWithProfile(profileOverridePath) err = converter.overrideWithProfile(profileOverridePath)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fatal() t.Fatal()
} }
@@ -45,12 +47,12 @@ func TestLoadHARWithProfileOverride(t *testing.T) {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if !assert.Equal(t, if !assert.Equal(t,
map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
caseConverter.TCase.TestSteps[i].Request.Headers) { converter.tCase.TestSteps[i].Request.Headers) {
t.FailNow() t.FailNow()
} }
if !assert.Equal(t, if !assert.Equal(t,
map[string]string{"UserName": "debugtalk"}, map[string]string{"UserName": "debugtalk"},
caseConverter.TCase.TestSteps[i].Request.Cookies) { converter.tCase.TestSteps[i].Request.Cookies) {
t.FailNow() t.FailNow()
} }
} }
@@ -58,7 +60,7 @@ func TestLoadHARWithProfileOverride(t *testing.T) {
func TestMakeRequestWithProfile(t *testing.T) { func TestMakeRequestWithProfile(t *testing.T) {
caseConverter := &TCaseConverter{ caseConverter := &TCaseConverter{
TCase: &hrp.TCase{ tCase: &hrp.TCase{
TestSteps: []*hrp.TStep{ TestSteps: []*hrp.TStep{
{ {
Request: &hrp.Request{ Request: &hrp.Request{
@@ -87,19 +89,19 @@ func TestMakeRequestWithProfile(t *testing.T) {
if !assert.Equal(t, map[string]string{ if !assert.Equal(t, map[string]string{
"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "hrp", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "hrp",
}, caseConverter.TCase.TestSteps[0].Request.Headers) { }, caseConverter.tCase.TestSteps[0].Request.Headers) {
t.Fatal() t.Fatal()
} }
if !assert.Equal(t, map[string]string{ if !assert.Equal(t, map[string]string{
"UserName": "debugtalk", "abc": "123", "UserName": "debugtalk", "abc": "123",
}, caseConverter.TCase.TestSteps[0].Request.Cookies) { }, caseConverter.tCase.TestSteps[0].Request.Cookies) {
t.Fatal() t.Fatal()
} }
} }
func TestMakeRequestWithProfileOverride(t *testing.T) { func TestMakeRequestWithProfileOverride(t *testing.T) {
caseConverter := &TCaseConverter{ caseConverter := &TCaseConverter{
TCase: &hrp.TCase{ tCase: &hrp.TCase{
TestSteps: []*hrp.TStep{ TestSteps: []*hrp.TStep{
{ {
Request: &hrp.Request{ Request: &hrp.Request{
@@ -129,12 +131,12 @@ func TestMakeRequestWithProfileOverride(t *testing.T) {
if !assert.Equal(t, map[string]string{ if !assert.Equal(t, map[string]string{
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, caseConverter.TCase.TestSteps[0].Request.Headers) { }, caseConverter.tCase.TestSteps[0].Request.Headers) {
t.Fatal() t.Fatal()
} }
if !assert.Equal(t, map[string]string{ if !assert.Equal(t, map[string]string{
"UserName": "debugtalk", "UserName": "debugtalk",
}, caseConverter.TCase.TestSteps[0].Request.Cookies) { }, caseConverter.tCase.TestSteps[0].Request.Cookies) {
t.Fatal() t.Fatal()
} }
} }
+6
View File
@@ -0,0 +1,6 @@
package convert
// TODO: convert TCase to gotest case
func (c *TCaseConverter) toGoTest() (string, error) {
return "", nil
}
+13
View 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
View 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
View 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
}