refactor: convert postman case

This commit is contained in:
debugtalk
2022-12-23 12:03:49 +08:00
parent 22a300d9b3
commit ea014c0734
17 changed files with 371 additions and 459 deletions

View File

@@ -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
}

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
hrp/convert.go Normal file
View File

@@ -0,0 +1 @@
package hrp

View File

@@ -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 引擎的风格
## 转换流程图

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
}

View File

@@ -0,0 +1 @@
package convert

View File

@@ -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).

View File

@@ -0,0 +1 @@
package convert

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
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
}

View File

@@ -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()
}
}

View File

@@ -0,0 +1,6 @@
package convert
// TODO: convert TCase to gotest case
func (c *TCaseConverter) toGoTest() (string, error) {
return "", nil
}

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
}

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
}

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
}