mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
Merge pull request #1389 from httprunner/refactor-converter
refactor: simplify testcase converter
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
- fix: only get the first parameter in referenced testcase
|
- fix: only get the first parameter in referenced testcase
|
||||||
- fix: support variable reference during extraction
|
- fix: support variable reference during extraction
|
||||||
- fix: simplify jmespath compatibility conversion
|
- fix: simplify jmespath compatibility conversion
|
||||||
|
- refactor: simplify testcase converter
|
||||||
|
|
||||||
**python version**
|
**python version**
|
||||||
|
|
||||||
|
|||||||
@@ -37,4 +37,4 @@ Copyright 2017 debugtalk
|
|||||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||||
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ hrp boom [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ hrp build $path ... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ hrp convert $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ hrp pytest $path ... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ hrp run $path... [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ hrp startproject $project_name [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ hrp wiki [flags]
|
|||||||
|
|
||||||
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
* [hrp](hrp.md) - Next-Generation API Testing Solution.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 26-Jun-2022
|
###### Auto generated by spf13/cobra on 27-Jun-2022
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
|||||||
headers:
|
|
||||||
User-Agent: "this header will be created or updated"
|
|
||||||
cookies:
|
|
||||||
Cookie1: "this cookie will be created or updated"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
override: true
|
|
||||||
headers:
|
|
||||||
Header1: "all original headers will be overridden"
|
|
||||||
cookies:
|
|
||||||
Cookie1: "all original cookies will be overridden"
|
|
||||||
4
examples/data/profile.yml
Normal file
4
examples/data/profile.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
headers:
|
||||||
|
Content-Type: "application/x-www-form-urlencoded"
|
||||||
|
cookies:
|
||||||
|
UserName: "debugtalk"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-empty-project",
|
"project_name": "demo-empty-project",
|
||||||
"create_time": "2022-06-26T11:26:24.132921+08:00",
|
"create_time": "2022-06-27T16:52:45.660111+08:00",
|
||||||
"hrp_version": "v4.1.4"
|
"hrp_version": "v4.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-go-plugin",
|
"project_name": "demo-with-go-plugin",
|
||||||
"create_time": "2022-06-26T11:21:39.64233+08:00",
|
"create_time": "2022-06-27T16:51:52.779837+08:00",
|
||||||
"hrp_version": "v4.1.4"
|
"hrp_version": "v4.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-with-py-plugin",
|
"project_name": "demo-with-py-plugin",
|
||||||
"create_time": "2022-06-26T11:21:40.23509+08:00",
|
"create_time": "2022-06-27T16:51:54.32061+08:00",
|
||||||
"hrp_version": "v4.1.4"
|
"hrp_version": "v4.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"project_name": "demo-without-plugin",
|
"project_name": "demo-without-plugin",
|
||||||
"create_time": "2022-06-26T11:26:19.127696+08:00",
|
"create_time": "2022-06-27T16:52:45.363472+08:00",
|
||||||
"hrp_version": "v4.1.4"
|
"hrp_version": "v4.1.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,8 +450,8 @@ func ReadFile(path string) ([]byte, error) {
|
|||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOutputNameWithoutExtension(path string) string {
|
func GetFileNameWithoutExtension(path string) string {
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
ext := filepath.Ext(base)
|
ext := filepath.Ext(base)
|
||||||
return base[0:len(base)-len(ext)] + "_test"
|
return base[0 : len(base)-len(ext)]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,18 @@ Global Flags:
|
|||||||
1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例
|
1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例
|
||||||
2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹
|
2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹
|
||||||
3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例:
|
3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例:
|
||||||
|
|
||||||
- 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息
|
- 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
headers:
|
headers:
|
||||||
Header1: "this header will be created or updated"
|
Header1: "this header will be created or updated"
|
||||||
cookies:
|
cookies:
|
||||||
Cookie1: "this cookie will be created or updated"
|
Cookie1: "this cookie will be created or updated"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 根据 profile 覆盖原有的 `Headers` 和 `Cookies` 信息
|
- 根据 profile 覆盖原有的 `Headers` 和 `Cookies` 信息
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
override: true
|
override: true
|
||||||
headers:
|
headers:
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
@@ -15,6 +13,7 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// target testcase format extensions
|
||||||
const (
|
const (
|
||||||
suffixJSON = ".json"
|
suffixJSON = ".json"
|
||||||
suffixYAML = ".yaml"
|
suffixYAML = ".yaml"
|
||||||
@@ -22,43 +21,6 @@ const (
|
|||||||
suffixPyTest = ".py"
|
suffixPyTest = ".py"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InputType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
InputTypeUnknown InputType = iota // default input type: unknown
|
|
||||||
InputTypeHAR
|
|
||||||
InputTypePostman
|
|
||||||
InputTypeSwagger
|
|
||||||
InputTypeJMeter
|
|
||||||
InputTypeJSON
|
|
||||||
InputTypeYAML
|
|
||||||
InputTypeGoTest
|
|
||||||
InputTypePyTest
|
|
||||||
)
|
|
||||||
|
|
||||||
func (inputType InputType) String() string {
|
|
||||||
switch inputType {
|
|
||||||
case InputTypeHAR:
|
|
||||||
return "har"
|
|
||||||
case InputTypePostman:
|
|
||||||
return "postman"
|
|
||||||
case InputTypeSwagger:
|
|
||||||
return "swagger"
|
|
||||||
case InputTypeJMeter:
|
|
||||||
return "jmeter"
|
|
||||||
case InputTypeJSON:
|
|
||||||
return "json testcase"
|
|
||||||
case InputTypeYAML:
|
|
||||||
return "yaml testcase"
|
|
||||||
case InputTypeGoTest:
|
|
||||||
return "gotest script"
|
|
||||||
case InputTypePyTest:
|
|
||||||
return "pytest script"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputType int
|
type OutputType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -81,19 +43,6 @@ func (outputType OutputType) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TCaseConverter holds the common properties of case converter
|
|
||||||
type TCaseConverter struct {
|
|
||||||
InputPath string
|
|
||||||
OutputDir string
|
|
||||||
Profile *Profile
|
|
||||||
InputType InputType
|
|
||||||
OutputType OutputType
|
|
||||||
CaseHAR *CaseHar
|
|
||||||
CasePostman *CasePostman
|
|
||||||
CaseSwagger *spec.Swagger
|
|
||||||
TCase *hrp.TCase
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profile is used to override or update(create if not existed) original headers and cookies
|
// Profile is used to override or update(create if not existed) original headers and cookies
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Override bool `json:"override" yaml:"override"`
|
Override bool `json:"override" yaml:"override"`
|
||||||
@@ -101,150 +50,6 @@ type Profile struct {
|
|||||||
Cookies map[string]string `json:"cookies" yaml:"cookies"`
|
Cookies map[string]string `json:"cookies" yaml:"cookies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCaseConverter(path string) (tCaseConverter *TCaseConverter) {
|
|
||||||
tCaseConverter = &TCaseConverter{
|
|
||||||
InputPath: path,
|
|
||||||
InputType: InputTypeUnknown,
|
|
||||||
}
|
|
||||||
extName := filepath.Ext(path)
|
|
||||||
if extName == "" {
|
|
||||||
log.Warn().Msg("extension name should be specified")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
switch extName {
|
|
||||||
case ".har":
|
|
||||||
caseHAR := new(CaseHar)
|
|
||||||
err = builtin.LoadFile(path, caseHAR)
|
|
||||||
if err == nil && !reflect.ValueOf(*caseHAR).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypeHAR
|
|
||||||
tCaseConverter.CaseHAR = caseHAR
|
|
||||||
}
|
|
||||||
case ".json":
|
|
||||||
tCase := new(hrp.TCase)
|
|
||||||
err = builtin.LoadFile(path, tCase)
|
|
||||||
if err == nil && !reflect.ValueOf(*tCase).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypeJSON
|
|
||||||
tCaseConverter.TCase = tCase
|
|
||||||
break
|
|
||||||
}
|
|
||||||
casePostman := new(CasePostman)
|
|
||||||
err = builtin.LoadFile(path, casePostman)
|
|
||||||
// deal with postman field name conflict with swagger
|
|
||||||
descriptionBackup := casePostman.Info.Description
|
|
||||||
casePostman.Info.Description = ""
|
|
||||||
if err == nil && !reflect.ValueOf(*casePostman).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypePostman
|
|
||||||
casePostman.Info.Description = descriptionBackup
|
|
||||||
tCaseConverter.CasePostman = casePostman
|
|
||||||
break
|
|
||||||
}
|
|
||||||
caseSwagger := new(spec.Swagger)
|
|
||||||
err = builtin.LoadFile(path, caseSwagger)
|
|
||||||
if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypeSwagger
|
|
||||||
tCaseConverter.CaseSwagger = caseSwagger
|
|
||||||
}
|
|
||||||
case ".yaml", ".yml":
|
|
||||||
tCase := new(hrp.TCase)
|
|
||||||
err = builtin.LoadFile(path, tCase)
|
|
||||||
if err == nil && !reflect.ValueOf(*tCase).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypeYAML
|
|
||||||
tCaseConverter.TCase = tCase
|
|
||||||
break
|
|
||||||
}
|
|
||||||
caseSwagger := new(spec.Swagger)
|
|
||||||
err = builtin.LoadFile(path, caseSwagger)
|
|
||||||
if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() {
|
|
||||||
tCaseConverter.InputType = InputTypeSwagger
|
|
||||||
tCaseConverter.CaseSwagger = caseSwagger
|
|
||||||
}
|
|
||||||
case ".go": // TODO
|
|
||||||
tCaseConverter.InputType = InputTypeGoTest
|
|
||||||
case ".py": // TODO
|
|
||||||
tCaseConverter.InputType = InputTypePyTest
|
|
||||||
case ".jmx": // TODO
|
|
||||||
tCaseConverter.InputType = InputTypeJMeter
|
|
||||||
default:
|
|
||||||
log.Warn().
|
|
||||||
Str("input path", tCaseConverter.InputPath).
|
|
||||||
Msgf("unsupported file type: %v", extName)
|
|
||||||
}
|
|
||||||
if tCaseConverter.InputType != InputTypeUnknown {
|
|
||||||
log.Info().
|
|
||||||
Str("input path", tCaseConverter.InputPath).
|
|
||||||
Msgf("load case as: %s", tCaseConverter.InputType.String())
|
|
||||||
} else {
|
|
||||||
log.Error().Err(err).
|
|
||||||
Str("input path", tCaseConverter.InputPath).
|
|
||||||
Msgf("failed to load case")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TCaseConverter) SetProfile(path string) {
|
|
||||||
log.Info().Str("input path", c.InputPath).Str("profile", path).Msg("set profile")
|
|
||||||
profile := new(Profile)
|
|
||||||
err := builtin.LoadFile(path, profile)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Str("path", path).
|
|
||||||
Msg("failed to load profile, ignore!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Profile = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TCaseConverter) SetOutputDir(dir string) {
|
|
||||||
log.Info().Str("input path", c.InputPath).Str("output directory", dir).Msg("set output directory")
|
|
||||||
c.OutputDir = dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TCaseConverter) genOutputPath(suffix string) string {
|
|
||||||
outFileFullName := builtin.GetOutputNameWithoutExtension(c.InputPath) + suffix
|
|
||||||
if c.OutputDir != "" {
|
|
||||||
return filepath.Join(c.OutputDir, outFileFullName)
|
|
||||||
} else {
|
|
||||||
return filepath.Join(filepath.Dir(c.InputPath), outFileFullName)
|
|
||||||
}
|
|
||||||
// TODO avoid outFileFullName conflict?
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TCaseConverter) ToPyTest() (string, error) {
|
|
||||||
script := convertConfig(c.TCase.Config)
|
|
||||||
println(script)
|
|
||||||
return script, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertConfig(config *hrp.TConfig) string {
|
|
||||||
script := fmt.Sprintf("Config('%s')", config.Name)
|
|
||||||
|
|
||||||
if config.Variables != nil {
|
|
||||||
script += fmt.Sprintf(".variables(**{%v})", config.Variables)
|
|
||||||
}
|
|
||||||
if config.BaseURL != "" {
|
|
||||||
script += fmt.Sprintf(".base_url('%s')", config.BaseURL)
|
|
||||||
}
|
|
||||||
if config.Export != nil {
|
|
||||||
script += fmt.Sprintf(".export(*%v)", config.Export)
|
|
||||||
}
|
|
||||||
script += fmt.Sprintf(".verify(%v)", config.Verify)
|
|
||||||
|
|
||||||
return script
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TCaseConverter) ToGoTest() (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICaseConverter represents all kinds of case converters which could convert case into JSON/YAML/gotest/pytest format
|
|
||||||
type ICaseConverter interface {
|
|
||||||
Struct() *TCaseConverter
|
|
||||||
ToJSON() (string, error)
|
|
||||||
ToYAML() (string, error)
|
|
||||||
ToGoTest() (string, error)
|
|
||||||
ToPyTest() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(outputType OutputType, outputDir, profilePath string, args []string) {
|
func Run(outputType OutputType, outputDir, profilePath string, args []string) {
|
||||||
// report event
|
// report event
|
||||||
sdk.SendEvent(sdk.EventTracking{
|
sdk.SendEvent(sdk.EventTracking{
|
||||||
@@ -252,57 +57,42 @@ func Run(outputType OutputType, outputDir, profilePath string, args []string) {
|
|||||||
Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()),
|
Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()),
|
||||||
})
|
})
|
||||||
|
|
||||||
// identify input and load converters
|
|
||||||
var iCaseConverters []ICaseConverter
|
|
||||||
for _, arg := range args {
|
|
||||||
tCaseConverter := NewTCaseConverter(arg)
|
|
||||||
tCaseConverter.OutputType = outputType
|
|
||||||
if outputDir != "" {
|
|
||||||
tCaseConverter.SetOutputDir(outputDir)
|
|
||||||
}
|
|
||||||
if profilePath != "" {
|
|
||||||
tCaseConverter.SetProfile(profilePath)
|
|
||||||
}
|
|
||||||
switch tCaseConverter.InputType {
|
|
||||||
case InputTypeHAR:
|
|
||||||
iCaseConverters = append(iCaseConverters, NewConverterHAR(tCaseConverter))
|
|
||||||
case InputTypePostman:
|
|
||||||
iCaseConverters = append(iCaseConverters, NewConverterPostman(tCaseConverter))
|
|
||||||
case InputTypeJSON:
|
|
||||||
iCaseConverters = append(iCaseConverters, NewConverterJSON(tCaseConverter))
|
|
||||||
case InputTypeYAML:
|
|
||||||
iCaseConverters = append(iCaseConverters, NewConverterYAML(tCaseConverter))
|
|
||||||
case InputTypeSwagger, InputTypeJMeter, InputTypeGoTest, InputTypePyTest:
|
|
||||||
log.Warn().
|
|
||||||
Str("input path", tCaseConverter.InputPath).
|
|
||||||
Msg("case type not supported yet, ignore!")
|
|
||||||
default:
|
|
||||||
log.Warn().
|
|
||||||
Str("input path", tCaseConverter.InputPath).
|
|
||||||
Msg("unknown case type, ignore!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start converting
|
|
||||||
var outputFiles []string
|
var outputFiles []string
|
||||||
var err error
|
for _, path := range args {
|
||||||
for _, iCaseConverter := range iCaseConverters {
|
// loads source file and convert to TCase format
|
||||||
log.Info().Str("input path", iCaseConverter.Struct().InputPath).Msg("start converting")
|
tCase, err := LoadTCase(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("path", path).Msg("convert source file failed")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
caseConverter := &TCaseConverter{
|
||||||
|
SourcePath: path,
|
||||||
|
OutputDir: outputDir,
|
||||||
|
TCase: tCase,
|
||||||
|
}
|
||||||
|
|
||||||
|
// override TCase with profile
|
||||||
|
if profilePath != "" {
|
||||||
|
caseConverter.overrideWithProfile(profilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert TCase format to target case format
|
||||||
var outputFile string
|
var outputFile string
|
||||||
switch iCaseConverter.Struct().OutputType {
|
switch outputType {
|
||||||
case OutputTypeYAML:
|
case OutputTypeYAML:
|
||||||
outputFile, err = iCaseConverter.ToYAML()
|
outputFile, err = caseConverter.ToYAML()
|
||||||
case OutputTypeGoTest:
|
case OutputTypeGoTest:
|
||||||
outputFile, err = iCaseConverter.ToGoTest()
|
outputFile, err = caseConverter.ToGoTest()
|
||||||
case OutputTypePyTest:
|
case OutputTypePyTest:
|
||||||
outputFile, err = iCaseConverter.ToPyTest()
|
outputFile, err = caseConverter.ToPyTest()
|
||||||
default:
|
default:
|
||||||
outputFile, err = iCaseConverter.ToJSON()
|
outputFile, err = caseConverter.ToJSON()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).
|
log.Error().Err(err).
|
||||||
Str("input path", iCaseConverter.Struct().InputPath).
|
Str("source path", path).
|
||||||
Msg("error occurs during converting")
|
Msg("convert case failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
outputFiles = append(outputFiles, outputFile)
|
outputFiles = append(outputFiles, outputFile)
|
||||||
@@ -310,16 +100,135 @@ func Run(outputType OutputType, outputDir, profilePath string, args []string) {
|
|||||||
log.Info().Strs("output files", outputFiles).Msg("conversion completed")
|
log.Info().Strs("output files", outputFiles).Msg("conversion completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error) {
|
// LoadTCase loads source file and convert to TCase type
|
||||||
tCase := iCaseConverter.Struct().TCase
|
func LoadTCase(path string) (*hrp.TCase, error) {
|
||||||
if tCase == nil {
|
extName := filepath.Ext(path)
|
||||||
return nil, errors.Errorf("empty json/yaml testcase occurs")
|
if extName == "" {
|
||||||
|
return nil, errors.New("file extension is not specified")
|
||||||
}
|
}
|
||||||
profile := iCaseConverter.Struct().Profile
|
switch extName {
|
||||||
if profile == nil {
|
case ".har":
|
||||||
|
tCase, err := LoadHARCase(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return tCase, nil
|
return tCase, nil
|
||||||
|
case ".json":
|
||||||
|
// priority: hrp JSON case > postman > swagger
|
||||||
|
// check if hrp JSON case
|
||||||
|
tCase, err := LoadJSONCase(path)
|
||||||
|
if err == nil {
|
||||||
|
return tCase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if postman format
|
||||||
|
casePostman, err := LoadPostmanCase(path)
|
||||||
|
if err == nil {
|
||||||
|
return casePostman, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if swagger format
|
||||||
|
caseSwagger, err := LoadSwaggerCase(path)
|
||||||
|
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(path)
|
||||||
|
if err == nil {
|
||||||
|
return tCase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if swagger format
|
||||||
|
caseSwagger, err := LoadSwaggerCase(path)
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
for _, step := range tCase.TestSteps {
|
|
||||||
|
return nil, fmt.Errorf("unsupported file type: %v", extName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCaseConverter holds the common properties of case converter
|
||||||
|
type TCaseConverter struct {
|
||||||
|
SourcePath string
|
||||||
|
OutputDir string
|
||||||
|
TCase *hrp.TCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TCaseConverter) genOutputPath(suffix string) string {
|
||||||
|
outFileFullName := builtin.GetFileNameWithoutExtension(c.SourcePath) + "_test" + suffix
|
||||||
|
if c.OutputDir != "" {
|
||||||
|
return filepath.Join(c.OutputDir, outFileFullName)
|
||||||
|
} else {
|
||||||
|
return filepath.Join(filepath.Dir(c.SourcePath), 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 = builtin.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
|
// override original headers and cookies
|
||||||
if profile.Override {
|
if profile.Override {
|
||||||
step.Request.Headers = make(map[string]string)
|
step.Request.Headers = make(map[string]string)
|
||||||
@@ -339,5 +248,5 @@ func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error)
|
|||||||
step.Request.Cookies[k] = v
|
step.Request.Cookies[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tCase, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp"
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConverterJSON(converter *TCaseConverter) *ConverterJSON {
|
|
||||||
return &ConverterJSON{
|
|
||||||
converter: converter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConverterJSON struct {
|
|
||||||
converter *TCaseConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) Struct() *TCaseConverter {
|
|
||||||
return c.converter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) ToJSON() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
jsonPath := c.converter.genOutputPath(suffixJSON)
|
|
||||||
err = builtin.Dump2JSON(testCase, jsonPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return jsonPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) ToYAML() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
yamlPath := c.converter.genOutputPath(suffixYAML)
|
|
||||||
err = builtin.Dump2YAML(testCase, yamlPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return yamlPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) ToGoTest() (string, error) {
|
|
||||||
// TODO implement me
|
|
||||||
return "", errors.New("convert from json testcase to gotest scripts is not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) ToPyTest() (string, error) {
|
|
||||||
return convertToPyTest(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) MakePyTestScript() (string, error) {
|
|
||||||
args := append([]string{"make"}, c.converter.InputPath)
|
|
||||||
err := builtin.ExecPython3Command("httprunner", args...)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return c.converter.genOutputPath(suffixPyTest), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterJSON) makeTestCase() (*hrp.TCase, error) {
|
|
||||||
tCase, err := makeTestCaseFromJSONYAML(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tCase.MakeCompat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tCase, nil
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
collectionPath = "../../../examples/data/postman/postman_collection.json"
|
|
||||||
collectionProfileOverridePath = "../../../examples/data/postman/profile_override.yml"
|
|
||||||
collectionProfilePath = "../../../examples/data/postman/profile.yml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var converterPostman = NewConverterPostman(NewTCaseConverter(collectionPath))
|
|
||||||
|
|
||||||
func TestPostman2JSON(t *testing.T) {
|
|
||||||
jsonPath, err := converterPostman.ToJSON()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.NotEmpty(t, jsonPath) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostman2YAML(t *testing.T) {
|
|
||||||
yamlPath, err := converterPostman.ToYAML()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.NotEmpty(t, yamlPath) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadCollection(t *testing.T) {
|
|
||||||
casePostman, err := converterPostman.load()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "postman collection demo", casePostman.Info.Name) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeTestCaseFromCollection(t *testing.T) {
|
|
||||||
tCase, err := converterPostman.makeTestCase()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check name
|
|
||||||
if !assert.Equal(t, "postman collection demo", tCase.Config.Name) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check method
|
|
||||||
if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check url
|
|
||||||
if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check params
|
|
||||||
if !assert.Equal(t, "v1", tCase.TestSteps[0].Request.Params["k1"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check cookies (pass, postman collection doesn't contain cookies)
|
|
||||||
// check headers
|
|
||||||
if !assert.Equal(t, "application/x-www-form-urlencoded", tCase.TestSteps[2].Request.Headers["Content-Type"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "application/json", tCase.TestSteps[3].Request.Headers["Content-Type"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "text/plain", tCase.TestSteps[4].Request.Headers["Content-Type"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "HttpRunner", tCase.TestSteps[5].Request.Headers["User-Agent"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// check body
|
|
||||||
if !assert.Equal(t, nil, tCase.TestSteps[0].Request.Body) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, tCase.TestSteps[2].Request.Body) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, map[string]interface{}{"k1": "v1", "k2": "v2"}, tCase.TestSteps[3].Request.Body) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "have a nice day", tCase.TestSteps[4].Request.Body) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, nil, tCase.TestSteps[5].Request.Body) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeTestCaseWithProfileOverride(t *testing.T) {
|
|
||||||
tCaseConverter := NewTCaseConverter(collectionPath)
|
|
||||||
tCaseConverter.SetProfile(collectionProfileOverridePath)
|
|
||||||
c := NewConverterPostman(tCaseConverter)
|
|
||||||
tCase, err := c.makeTestCase()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
for _, step := range tCase.TestSteps {
|
|
||||||
if step.Request.Method == "GET" && !assert.Len(t, step.Request.Headers, 1) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "all original headers will be overridden", step.Request.Headers["Header1"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Len(t, step.Request.Cookies, 1) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "all original cookies will be overridden", step.Request.Cookies["Cookie1"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeTestCaseWithProfile(t *testing.T) {
|
|
||||||
tCaseConverter := NewTCaseConverter(collectionPath)
|
|
||||||
tCaseConverter.SetProfile(collectionProfilePath)
|
|
||||||
c := NewConverterPostman(tCaseConverter)
|
|
||||||
tCase, err := c.makeTestCase()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// create cookies Cookie1 indicated in profile
|
|
||||||
if !assert.Equal(t, "this cookie will be created or updated", tCase.TestSteps[0].Request.Cookies["Cookie1"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// update header User-Agent indicated in profile
|
|
||||||
if !assert.Equal(t, "this header will be created or updated", tCase.TestSteps[5].Request.Headers["User-Agent"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// pass header Connection which is not indicated in profile
|
|
||||||
if !assert.Equal(t, "close", tCase.TestSteps[5].Request.Headers["Connection"]) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func convertToPyTest(iCaseConverter ICaseConverter) (string, error) {
|
|
||||||
// convert to temporary json testcase
|
|
||||||
jsonPath, err := iCaseConverter.ToJSON()
|
|
||||||
inputType := iCaseConverter.Struct().InputType
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrapf(err, "(%s -> pytest step 1) failed to convert to temporary json testcase", inputType.String())
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if jsonPath != "" {
|
|
||||||
if err = os.Remove(jsonPath); err != nil {
|
|
||||||
log.Error().Err(err).Msgf("(%s -> pytest step defer) failed to clean temporary json testcase", inputType.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// convert from temporary json testcase to pytest
|
|
||||||
converterJSON := NewConverterJSON(NewTCaseConverter(jsonPath))
|
|
||||||
pyTestPath, err := converterJSON.MakePyTestScript()
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "(json -> pytest step 2) failed to convert from temporary json testcase to pytest ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// rename resultant pytest
|
|
||||||
renamedPyTestPath := iCaseConverter.Struct().genOutputPath(suffixPyTest)
|
|
||||||
err = os.Rename(pyTestPath, renamedPyTestPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("(json -> pytest step 3) failed to rename the resultant pytest file")
|
|
||||||
return pyTestPath, nil
|
|
||||||
}
|
|
||||||
return renamedPyTestPath, nil
|
|
||||||
}
|
|
||||||
140
hrp/internal/convert/converter_test.go
Normal file
140
hrp/internal/convert/converter_test.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
profilePath = "../../../examples/data/profile.yml"
|
||||||
|
profileOverridePath = "../../../examples/data/profile_override.yml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadTCase(t *testing.T) {
|
||||||
|
tCase, err := LoadTCase(harPath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.NotEmpty(t, tCase) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHARWithProfileOverride(t *testing.T) {
|
||||||
|
tCase, err := LoadTCase(harPath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.NotEmpty(t, tCase) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
caseConverter := &TCaseConverter{
|
||||||
|
TCase: tCase,
|
||||||
|
}
|
||||||
|
|
||||||
|
// override TCase with profile
|
||||||
|
err = caseConverter.overrideWithProfile(profileOverridePath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t,
|
||||||
|
map[string]string{"UserName": "debugtalk"},
|
||||||
|
caseConverter.TCase.TestSteps[i].Request.Cookies) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeRequestWithProfile(t *testing.T) {
|
||||||
|
caseConverter := &TCaseConverter{
|
||||||
|
TCase: &hrp.TCase{
|
||||||
|
TestSteps: []*hrp.TStep{
|
||||||
|
{
|
||||||
|
Request: &hrp.Request{
|
||||||
|
Method: hrp.HTTPMethod("POST"),
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
"User-Agent": "hrp",
|
||||||
|
},
|
||||||
|
Cookies: map[string]string{
|
||||||
|
"abc": "123",
|
||||||
|
"UserName": "leolee",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := caseConverter.overrideWithProfile(profilePath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(t, map[string]string{
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "hrp",
|
||||||
|
}, 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) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeRequestWithProfileOverride(t *testing.T) {
|
||||||
|
caseConverter := &TCaseConverter{
|
||||||
|
TCase: &hrp.TCase{
|
||||||
|
TestSteps: []*hrp.TStep{
|
||||||
|
{
|
||||||
|
Request: &hrp.Request{
|
||||||
|
Method: hrp.HTTPMethod("POST"),
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
"User-Agent": "hrp",
|
||||||
|
},
|
||||||
|
Cookies: map[string]string{
|
||||||
|
"abc": "123",
|
||||||
|
"UserName": "leolee",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// override TCase with profile
|
||||||
|
err := caseConverter.overrideWithProfile(profileOverridePath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !assert.Equal(t, map[string]string{
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
}, caseConverter.TCase.TestSteps[0].Request.Headers) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, map[string]string{
|
||||||
|
"UserName": "debugtalk",
|
||||||
|
}, caseConverter.TCase.TestSteps[0].Request.Cookies) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp"
|
|
||||||
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConverterYAML(converter *TCaseConverter) *ConverterYAML {
|
|
||||||
return &ConverterYAML{
|
|
||||||
converter: converter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConverterYAML struct {
|
|
||||||
converter *TCaseConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) Struct() *TCaseConverter {
|
|
||||||
return c.converter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) ToJSON() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
jsonPath := c.converter.genOutputPath(suffixJSON)
|
|
||||||
err = builtin.Dump2JSON(testCase, jsonPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return jsonPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) ToYAML() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
yamlPath := c.converter.genOutputPath(suffixYAML)
|
|
||||||
err = builtin.Dump2YAML(testCase, yamlPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return yamlPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) ToGoTest() (string, error) {
|
|
||||||
//TODO implement me
|
|
||||||
return "", errors.New("convert from yaml testcase to gotest scripts is not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) ToPyTest() (string, error) {
|
|
||||||
return convertToPyTest(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterYAML) makeTestCase() (*hrp.TCase, error) {
|
|
||||||
tCase, err := makeTestCaseFromJSONYAML(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tCase.MakeCompat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tCase, nil
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -357,56 +358,31 @@ type TestResult struct {
|
|||||||
|
|
||||||
// ==================== model definition ends here ====================
|
// ==================== model definition ends here ====================
|
||||||
|
|
||||||
func NewConverterHAR(converter *TCaseConverter) *ConverterHAR {
|
func LoadHARCase(path string) (*hrp.TCase, error) {
|
||||||
return &ConverterHAR{
|
// load har file
|
||||||
converter: converter,
|
caseHAR, err := loadCaseHAR(path)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConverterHAR struct {
|
|
||||||
converter *TCaseConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterHAR) Struct() *TCaseConverter {
|
|
||||||
return c.converter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterHAR) ToJSON() (string, error) {
|
|
||||||
tCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
jsonPath := c.converter.genOutputPath(suffixJSON)
|
|
||||||
err = builtin.Dump2JSON(tCase, jsonPath)
|
// convert to TCase format
|
||||||
|
return caseHAR.ToTCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCaseHAR(path string) (*CaseHar, error) {
|
||||||
|
caseHAR := new(CaseHar)
|
||||||
|
err := builtin.LoadFile(path, caseHAR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, errors.Wrap(err, "load har file failed")
|
||||||
}
|
}
|
||||||
return jsonPath, nil
|
if reflect.ValueOf(*caseHAR).IsZero() {
|
||||||
}
|
return nil, errors.New("invalid har file")
|
||||||
|
|
||||||
func (c *ConverterHAR) ToYAML() (string, error) {
|
|
||||||
tCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
yamlPath := c.converter.genOutputPath(suffixYAML)
|
return caseHAR, nil
|
||||||
err = builtin.Dump2YAML(tCase, yamlPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return yamlPath, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterHAR) ToGoTest() (string, error) {
|
// convert CaseHar to TCase format
|
||||||
//TODO implement me
|
func (c *CaseHar) ToTCase() (*hrp.TCase, error) {
|
||||||
return "", errors.New("convert from har to gotest scripts is not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterHAR) ToPyTest() (string, error) {
|
|
||||||
return convertToPyTest(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) {
|
|
||||||
teststeps, err := c.prepareTestSteps()
|
teststeps, err := c.prepareTestSteps()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -423,27 +399,14 @@ func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) {
|
|||||||
return tCase, nil
|
return tCase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterHAR) load() (*CaseHar, error) {
|
func (c *CaseHar) prepareConfig() *hrp.TConfig {
|
||||||
har := c.converter.CaseHAR
|
|
||||||
if har == nil {
|
|
||||||
return nil, errors.New("empty har case occurs")
|
|
||||||
}
|
|
||||||
return har, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterHAR) prepareConfig() *hrp.TConfig {
|
|
||||||
return hrp.NewConfig("testcase description").
|
return hrp.NewConfig("testcase description").
|
||||||
SetVerifySSL(false)
|
SetVerifySSL(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterHAR) prepareTestSteps() ([]*hrp.TStep, error) {
|
func (c *CaseHar) prepareTestSteps() ([]*hrp.TStep, error) {
|
||||||
har, err := c.load()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var steps []*hrp.TStep
|
var steps []*hrp.TStep
|
||||||
for _, entry := range har.Log.Entries {
|
for _, entry := range c.Log.Entries {
|
||||||
step, err := c.prepareTestStep(&entry)
|
step, err := c.prepareTestStep(&entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -454,7 +417,7 @@ func (c *ConverterHAR) prepareTestSteps() ([]*hrp.TStep, error) {
|
|||||||
return steps, nil
|
return steps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
func (c *CaseHar) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("method", entry.Request.Method).
|
Str("method", entry.Request.Method).
|
||||||
Str("url", entry.Request.URL).
|
Str("url", entry.Request.URL).
|
||||||
@@ -465,7 +428,6 @@ func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
|||||||
Request: &hrp.Request{},
|
Request: &hrp.Request{},
|
||||||
Validators: make([]interface{}, 0),
|
Validators: make([]interface{}, 0),
|
||||||
},
|
},
|
||||||
profile: c.converter.Profile,
|
|
||||||
}
|
}
|
||||||
if err := step.makeRequestMethod(entry); err != nil {
|
if err := step.makeRequestMethod(entry); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -493,7 +455,6 @@ func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
|||||||
|
|
||||||
type stepFromHAR struct {
|
type stepFromHAR struct {
|
||||||
hrp.TStep
|
hrp.TStep
|
||||||
profile *Profile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepFromHAR) makeRequestMethod(entry *Entry) error {
|
func (s *stepFromHAR) makeRequestMethod(entry *Entry) error {
|
||||||
@@ -525,18 +486,6 @@ func (s *stepFromHAR) makeRequestCookies(entry *Entry) error {
|
|||||||
for _, cookie := range entry.Request.Cookies {
|
for _, cookie := range entry.Request.Cookies {
|
||||||
s.Request.Cookies[cookie.Name] = cookie.Value
|
s.Request.Cookies[cookie.Name] = cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.profile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// override all cookies according to the profile
|
|
||||||
if s.profile.Override {
|
|
||||||
s.Request.Cookies = make(map[string]string)
|
|
||||||
}
|
|
||||||
// create or update the cookies according to the profile
|
|
||||||
for k, v := range s.profile.Cookies {
|
|
||||||
s.Request.Cookies[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,18 +498,6 @@ func (s *stepFromHAR) makeRequestHeaders(entry *Entry) error {
|
|||||||
}
|
}
|
||||||
s.Request.Headers[header.Name] = header.Value
|
s.Request.Headers[header.Name] = header.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.profile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// override all headers according to the profile
|
|
||||||
if s.profile.Override {
|
|
||||||
s.Request.Headers = make(map[string]string)
|
|
||||||
}
|
|
||||||
// create or update the headers according to the profile
|
|
||||||
for k, v := range s.profile.Headers {
|
|
||||||
s.Request.Headers[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,71 +8,29 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v4/hrp"
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var harPath = "../../../examples/data/har/demo.har"
|
||||||
harPath = "../../../examples/data/har/demo.har"
|
|
||||||
harPath2 = "../../../examples/data/har/postman-echo.har"
|
|
||||||
harProfileOverridePath = "../../../examples/data/har/profile_override.yml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var converterHAR = NewConverterHAR(NewTCaseConverter(harPath))
|
var caseHar *CaseHar
|
||||||
var converterHAR2 = NewConverterHAR(NewTCaseConverter(harPath2))
|
|
||||||
|
|
||||||
func TestHAR2JSON(t *testing.T) {
|
func init() {
|
||||||
jsonPath, err := converterHAR.ToJSON()
|
caseHar, _ = loadCaseHAR(harPath)
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.NotEmpty(t, jsonPath) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHAR2YAML(t *testing.T) {
|
|
||||||
yamlPath, err := converterHAR2.ToYAML()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.NotEmpty(t, yamlPath) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHAR(t *testing.T) {
|
func TestLoadHAR(t *testing.T) {
|
||||||
h, err := converterHAR.load()
|
caseHAR, err := loadCaseHAR(harPath)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
if !assert.Equal(t, "GET", h.Log.Entries[0].Request.Method) {
|
if !assert.Equal(t, "GET", caseHAR.Log.Entries[0].Request.Method) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
if !assert.Equal(t, "POST", h.Log.Entries[1].Request.Method) {
|
if !assert.Equal(t, "POST", caseHAR.Log.Entries[1].Request.Method) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHARWithProfile(t *testing.T) {
|
func TestLoadTCaseFromHAR(t *testing.T) {
|
||||||
tCaseConverter := NewTCaseConverter(harPath)
|
tCase, err := LoadHARCase(harPath)
|
||||||
tCaseConverter.SetProfile(harProfileOverridePath)
|
|
||||||
h := NewConverterHAR(tCaseConverter)
|
|
||||||
_, err := h.load()
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.Equal(t,
|
|
||||||
map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
|
||||||
h.converter.Profile.Headers) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t,
|
|
||||||
map[string]string{"UserName": "debugtalk"},
|
|
||||||
h.converter.Profile.Cookies) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeTestCaseFromHAR(t *testing.T) {
|
|
||||||
tCase, err := converterHAR.makeTestCase()
|
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -143,7 +101,7 @@ func TestMakeRequestURL(t *testing.T) {
|
|||||||
URL: "http://127.0.0.1:8080/api/login",
|
URL: "http://127.0.0.1:8080/api/login",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -162,7 +120,7 @@ func TestMakeRequestHeaders(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -174,30 +132,6 @@ func TestMakeRequestHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakeRequestHeadersWithProfileOverride(t *testing.T) {
|
|
||||||
tCaseConverter := NewTCaseConverter(harPath)
|
|
||||||
tCaseConverter.SetProfile(harProfileOverridePath)
|
|
||||||
h := NewConverterHAR(tCaseConverter)
|
|
||||||
entry := &Entry{
|
|
||||||
Request: Request{
|
|
||||||
Method: "POST",
|
|
||||||
Headers: []NVP{
|
|
||||||
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
step, err := h.prepareTestStep(entry)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.Equal(t, map[string]string{
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
}, step.Request.Headers) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeRequestCookies(t *testing.T) {
|
func TestMakeRequestCookies(t *testing.T) {
|
||||||
entry := &Entry{
|
entry := &Entry{
|
||||||
Request: Request{
|
Request: Request{
|
||||||
@@ -208,7 +142,7 @@ func TestMakeRequestCookies(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -221,31 +155,6 @@ func TestMakeRequestCookies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakeRequestCookiesWithProfileOverride(t *testing.T) {
|
|
||||||
tCaseConverter := NewTCaseConverter(harPath)
|
|
||||||
tCaseConverter.SetProfile(harProfileOverridePath)
|
|
||||||
h := NewConverterHAR(tCaseConverter)
|
|
||||||
entry := &Entry{
|
|
||||||
Request: Request{
|
|
||||||
Method: "POST",
|
|
||||||
Cookies: []Cookie{
|
|
||||||
{Name: "abc", Value: "123"},
|
|
||||||
{Name: "UserName", Value: "leolee"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
step, err := h.prepareTestStep(entry)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.Equal(t, map[string]string{
|
|
||||||
"UserName": "debugtalk",
|
|
||||||
}, step.Request.Cookies) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeRequestDataParams(t *testing.T) {
|
func TestMakeRequestDataParams(t *testing.T) {
|
||||||
entry := &Entry{
|
entry := &Entry{
|
||||||
Request: Request{
|
Request: Request{
|
||||||
@@ -259,7 +168,7 @@ func TestMakeRequestDataParams(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -279,7 +188,7 @@ func TestMakeRequestDataJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -299,7 +208,7 @@ func TestMakeRequestDataTextEmpty(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -325,7 +234,7 @@ func TestMakeValidate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
step, err := converterHAR.prepareTestStep(entry)
|
step, err := caseHar.prepareTestStep(entry)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
27
hrp/internal/convert/from_json.go
Normal file
27
hrp/internal/convert/from_json.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"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
|
||||||
|
caseJSON := new(hrp.TCase)
|
||||||
|
err := builtin.LoadFile(path, caseJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "load json file failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if caseJSON.TestSteps == nil {
|
||||||
|
return nil, errors.New("invalid json case file, missing teststeps")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = caseJSON.MakeCompat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return caseJSON, nil
|
||||||
|
}
|
||||||
@@ -112,66 +112,37 @@ var contentTypeMap = map[string]string{
|
|||||||
"xml": "application/xml",
|
"xml": "application/xml",
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConverterPostman(converter *TCaseConverter) *ConverterPostman {
|
func LoadPostmanCase(path string) (*hrp.TCase, error) {
|
||||||
return &ConverterPostman{
|
// load postman file
|
||||||
converter: converter,
|
casePostman, err := loadCasePostman(path)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConverterPostman struct {
|
|
||||||
converter *TCaseConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) Struct() *TCaseConverter {
|
|
||||||
return c.converter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) ToJSON() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
jsonPath := c.converter.genOutputPath(suffixJSON)
|
|
||||||
err = builtin.Dump2JSON(testCase, jsonPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return jsonPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) ToYAML() (string, error) {
|
|
||||||
testCase, err := c.makeTestCase()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
yamlPath := c.converter.genOutputPath(suffixYAML)
|
|
||||||
err = builtin.Dump2YAML(testCase, yamlPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return yamlPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) ToGoTest() (string, error) {
|
|
||||||
// TODO implement me
|
|
||||||
return "", errors.New("convert from postman to gotest scripts is not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) ToPyTest() (string, error) {
|
|
||||||
return convertToPyTest(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) {
|
|
||||||
casePostman, err := c.load()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
teststeps, err := c.prepareTestSteps(casePostman)
|
|
||||||
|
// convert to TCase format
|
||||||
|
return casePostman.ToTCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCasePostman(path string) (*CasePostman, error) {
|
||||||
|
casePostman := new(CasePostman)
|
||||||
|
err := builtin.LoadFile(path, casePostman)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "load postman file failed")
|
||||||
|
}
|
||||||
|
if casePostman.Items == nil {
|
||||||
|
return nil, errors.New("invalid postman case file, missing items")
|
||||||
|
}
|
||||||
|
|
||||||
|
return casePostman, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CasePostman) ToTCase() (*hrp.TCase, error) {
|
||||||
|
teststeps, err := c.prepareTestSteps()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tCase := &hrp.TCase{
|
tCase := &hrp.TCase{
|
||||||
Config: c.prepareConfig(casePostman),
|
Config: c.prepareConfig(),
|
||||||
TestSteps: teststeps,
|
TestSteps: teststeps,
|
||||||
}
|
}
|
||||||
err = tCase.MakeCompat()
|
err = tCase.MakeCompat()
|
||||||
@@ -181,23 +152,15 @@ func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) {
|
|||||||
return tCase, nil
|
return tCase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterPostman) load() (*CasePostman, error) {
|
func (c *CasePostman) prepareConfig() *hrp.TConfig {
|
||||||
casePostman := c.converter.CasePostman
|
return hrp.NewConfig(c.Info.Name).
|
||||||
if casePostman == nil {
|
|
||||||
return nil, errors.New("empty postman case occurs")
|
|
||||||
}
|
|
||||||
return casePostman, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConverterPostman) prepareConfig(casePostman *CasePostman) *hrp.TConfig {
|
|
||||||
return hrp.NewConfig(casePostman.Info.Name).
|
|
||||||
SetVerifySSL(false)
|
SetVerifySSL(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterPostman) prepareTestSteps(casePostman *CasePostman) ([]*hrp.TStep, error) {
|
func (c *CasePostman) prepareTestSteps() ([]*hrp.TStep, error) {
|
||||||
// recursively convert collection items into a list
|
// recursively convert collection items into a list
|
||||||
var itemList []TItem
|
var itemList []TItem
|
||||||
for _, item := range casePostman.Items {
|
for _, item := range c.Items {
|
||||||
extractItemList(item, &itemList)
|
extractItemList(item, &itemList)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +192,7 @@ func extractItemList(item TItem, itemList *[]TItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConverterPostman) prepareTestStep(item *TItem) (*hrp.TStep, error) {
|
func (c *CasePostman) prepareTestStep(item *TItem) (*hrp.TStep, error) {
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("method", item.Request.Method).
|
Str("method", item.Request.Method).
|
||||||
Str("url", item.Request.URL.Raw).
|
Str("url", item.Request.URL.Raw).
|
||||||
@@ -240,7 +203,6 @@ func (c *ConverterPostman) prepareTestStep(item *TItem) (*hrp.TStep, error) {
|
|||||||
Request: &hrp.Request{},
|
Request: &hrp.Request{},
|
||||||
Validators: make([]interface{}, 0),
|
Validators: make([]interface{}, 0),
|
||||||
},
|
},
|
||||||
profile: c.converter.Profile,
|
|
||||||
}
|
}
|
||||||
if err := step.makeRequestName(item); err != nil {
|
if err := step.makeRequestName(item); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -268,7 +230,6 @@ func (c *ConverterPostman) prepareTestStep(item *TItem) (*hrp.TStep, error) {
|
|||||||
|
|
||||||
type stepFromPostman struct {
|
type stepFromPostman struct {
|
||||||
hrp.TStep
|
hrp.TStep
|
||||||
profile *Profile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeRequestName indicates the step name the same as item name
|
// makeRequestName indicates the step name the same as item name
|
||||||
@@ -317,18 +278,6 @@ func (s *stepFromPostman) makeRequestHeaders(item *TItem) error {
|
|||||||
}
|
}
|
||||||
s.Request.Headers[field.Key] = field.Value
|
s.Request.Headers[field.Key] = field.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.profile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// override all headers according to the profile
|
|
||||||
if s.profile.Override {
|
|
||||||
s.Request.Headers = make(map[string]string)
|
|
||||||
}
|
|
||||||
// create or update the headers according to the profile
|
|
||||||
for k, v := range s.profile.Headers {
|
|
||||||
s.Request.Headers[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,18 +290,6 @@ func (s *stepFromPostman) makeRequestCookies(item *TItem) error {
|
|||||||
}
|
}
|
||||||
s.parseRequestCookiesMap(field.Value)
|
s.parseRequestCookiesMap(field.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.profile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// override all cookies according to the profile
|
|
||||||
if s.profile.Override {
|
|
||||||
s.Request.Cookies = make(map[string]string)
|
|
||||||
}
|
|
||||||
// create or update the cookies according to the profile
|
|
||||||
for k, v := range s.profile.Cookies {
|
|
||||||
s.Request.Cookies[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
78
hrp/internal/convert/from_postman_test.go
Normal file
78
hrp/internal/convert/from_postman_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var collectionPath = "../../../examples/data/postman/postman_collection.json"
|
||||||
|
|
||||||
|
func TestLoadCollection(t *testing.T) {
|
||||||
|
casePostman, err := loadCasePostman(collectionPath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "postman collection demo", casePostman.Info.Name) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeTestCaseFromCollection(t *testing.T) {
|
||||||
|
tCase, err := LoadPostmanCase(collectionPath)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check name
|
||||||
|
if !assert.Equal(t, "postman collection demo", tCase.Config.Name) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check method
|
||||||
|
if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check url
|
||||||
|
if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check params
|
||||||
|
if !assert.Equal(t, "v1", tCase.TestSteps[0].Request.Params["k1"]) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check cookies (pass, postman collection doesn't contain cookies)
|
||||||
|
// check headers
|
||||||
|
if !assert.Equal(t, "application/x-www-form-urlencoded", tCase.TestSteps[2].Request.Headers["Content-Type"]) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "application/json", tCase.TestSteps[3].Request.Headers["Content-Type"]) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "text/plain", tCase.TestSteps[4].Request.Headers["Content-Type"]) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "HttpRunner", tCase.TestSteps[5].Request.Headers["User-Agent"]) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
// check body
|
||||||
|
if !assert.Equal(t, nil, tCase.TestSteps[0].Request.Body) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, tCase.TestSteps[2].Request.Body) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, map[string]interface{}{"k1": "v1", "k2": "v2"}, tCase.TestSteps[3].Request.Body) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "have a nice day", tCase.TestSteps[4].Request.Body) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, nil, tCase.TestSteps[5].Request.Body) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
1
hrp/internal/convert/from_pytest.go
Normal file
1
hrp/internal/convert/from_pytest.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package convert
|
||||||
24
hrp/internal/convert/from_swagger.go
Normal file
24
hrp/internal/convert/from_swagger.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-openapi/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadSwaggerCase(path string) (*hrp.TCase, error) {
|
||||||
|
// load swagger file
|
||||||
|
caseSwagger := new(spec.Swagger)
|
||||||
|
err := builtin.LoadFile(path, caseSwagger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "load swagger file failed")
|
||||||
|
}
|
||||||
|
if caseSwagger.Definitions == nil {
|
||||||
|
return nil, errors.New("invalid swagger case file, missing definitions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: convert swagger to TCase
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
28
hrp/internal/convert/from_yaml.go
Normal file
28
hrp/internal/convert/from_yaml.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp"
|
||||||
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewYAMLCase(path string) (*hrp.TCase, error) {
|
||||||
|
// load yaml case file
|
||||||
|
caseJSON := new(hrp.TCase)
|
||||||
|
err := builtin.LoadFile(path, caseJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "load yaml file failed")
|
||||||
|
}
|
||||||
|
if reflect.ValueOf(*caseJSON).IsZero() {
|
||||||
|
return nil, errors.New("invalid yaml file")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = caseJSON.MakeCompat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return caseJSON, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user