refactor: simplify testcase converter

This commit is contained in:
debugtalk
2022-06-27 00:25:32 +08:00
parent 2caf5d5159
commit 700cdfd815
10 changed files with 228 additions and 670 deletions

View File

@@ -12,6 +12,7 @@
- fix: failed to load json/data content in api reference - fix: failed to load json/data content in api reference
- fix: failed to convert postman collection containing multipart/form-data requests to pytest - fix: failed to convert postman collection containing multipart/form-data requests to pytest
- fix: only get the first parameter in referenced testcase - fix: only get the first parameter in referenced testcase
- refactor: simplify testcase converter
**python version** **python version**

View File

@@ -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"
@@ -83,15 +82,11 @@ func (outputType OutputType) String() string {
// TCaseConverter holds the common properties of case converter // TCaseConverter holds the common properties of case converter
type TCaseConverter struct { type TCaseConverter struct {
InputPath string InputPath string
OutputDir string OutputDir string
Profile *Profile InputType InputType
InputType InputType OutputType OutputType
OutputType OutputType TCase *hrp.TCase
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
@@ -101,102 +96,64 @@ 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) { // LoadTCase loads source file and convert to TCase type
tCaseConverter = &TCaseConverter{ func LoadTCase(path string) (*hrp.TCase, error) {
InputPath: path,
InputType: InputTypeUnknown,
}
extName := filepath.Ext(path) extName := filepath.Ext(path)
if extName == "" { if extName == "" {
log.Warn().Msg("extension name should be specified") return nil, errors.New("file extension is not specified")
return
} }
var err error
switch extName { switch extName {
case ".har": case ".har":
caseHAR := new(CaseHar) tCase, err := LoadHARCase(path)
err = builtin.LoadFile(path, caseHAR) if err != nil {
if err == nil && !reflect.ValueOf(*caseHAR).IsZero() { return nil, err
tCaseConverter.InputType = InputTypeHAR
tCaseConverter.CaseHAR = caseHAR
} }
return tCase, nil
case ".json": case ".json":
tCase := new(hrp.TCase) // priority: hrp JSON case > postman > swagger
err = builtin.LoadFile(path, tCase) // check if hrp JSON case
if err == nil && !reflect.ValueOf(*tCase).IsZero() { tCase, err := LoadJSONCase(path)
tCaseConverter.InputType = InputTypeJSON if err == nil {
tCaseConverter.TCase = tCase return tCase, nil
break
} }
casePostman := new(CasePostman)
err = builtin.LoadFile(path, casePostman) // check if postman format
// deal with postman field name conflict with swagger casePostman, err := LoadPostmanCase(path)
descriptionBackup := casePostman.Info.Description if err == nil {
casePostman.Info.Description = "" return casePostman, nil
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) // check if swagger format
if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { caseSwagger, err := LoadSwaggerCase(path)
tCaseConverter.InputType = InputTypeSwagger if err == nil {
tCaseConverter.CaseSwagger = caseSwagger return caseSwagger, nil
} }
return nil, errors.New("unexpected JSON format")
case ".yaml", ".yml": case ".yaml", ".yml":
tCase := new(hrp.TCase) // priority: hrp YAML case > swagger
err = builtin.LoadFile(path, tCase) // check if hrp YAML case
if err == nil && !reflect.ValueOf(*tCase).IsZero() { tCase, err := NewYAMLCase(path)
tCaseConverter.InputType = InputTypeYAML if err == nil {
tCaseConverter.TCase = tCase return tCase, nil
break
} }
caseSwagger := new(spec.Swagger)
err = builtin.LoadFile(path, caseSwagger) // check if swagger format
if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { caseSwagger, err := LoadSwaggerCase(path)
tCaseConverter.InputType = InputTypeSwagger if err == nil {
tCaseConverter.CaseSwagger = caseSwagger return caseSwagger, nil
} }
return nil, errors.New("unexpected YAML format")
case ".go": // TODO case ".go": // TODO
tCaseConverter.InputType = InputTypeGoTest return nil, errors.New("convert gotest is not implemented")
case ".py": // TODO case ".py": // TODO
tCaseConverter.InputType = InputTypePyTest return nil, errors.New("convert pytest is not implemented")
case ".jmx": // TODO case ".jmx": // TODO
tCaseConverter.InputType = InputTypeJMeter return nil, errors.New("convert JMeter jmx is not implemented")
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) { return nil, fmt.Errorf("unsupported file type: %v", extName)
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 { func (c *TCaseConverter) genOutputPath(suffix string) string {
@@ -209,40 +166,39 @@ func (c *TCaseConverter) genOutputPath(suffix string) string {
// TODO avoid outFileFullName conflict? // TODO avoid outFileFullName conflict?
} }
// convert TCase to pytest case
func (c *TCaseConverter) ToPyTest() (string, error) { func (c *TCaseConverter) ToPyTest() (string, error) {
script := convertConfig(c.TCase.Config) args := append([]string{"make"}, c.InputPath)
println(script) err := builtin.ExecPython3Command("httprunner", args...)
return script, nil if err != nil {
} return "", err
}
func convertConfig(config *hrp.TConfig) string { return c.genOutputPath(suffixPyTest), nil
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
} }
// TODO: convert TCase to gotest case
func (c *TCaseConverter) ToGoTest() (string, error) { func (c *TCaseConverter) ToGoTest() (string, error) {
return "", nil return "", nil
} }
// ICaseConverter represents all kinds of case converters which could convert case into JSON/YAML/gotest/pytest format // convert TCase to JSON case
type ICaseConverter interface { func (c *TCaseConverter) ToJSON() (string, error) {
Struct() *TCaseConverter jsonPath := c.genOutputPath(suffixJSON)
ToJSON() (string, error) err := builtin.Dump2JSON(c.TCase, jsonPath)
ToYAML() (string, error) if err != nil {
ToGoTest() (string, error) return "", err
ToPyTest() (string, error) }
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 Run(outputType OutputType, outputDir, profilePath string, args []string) { func Run(outputType OutputType, outputDir, profilePath string, args []string) {
@@ -252,57 +208,40 @@ 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 in support types 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("construct case loader failed")
continue
}
caseConverter := TCaseConverter{
OutputDir: outputDir,
OutputType: outputType,
TCase: tCase,
}
// override TCase with profile
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 +249,17 @@ 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) { func (c *TCaseConverter) overrideWithProfile(path string) error {
tCase := iCaseConverter.Struct().TCase log.Info().Str("path", path).Msg("load profile")
if tCase == nil { profile := new(Profile)
return nil, errors.Errorf("empty json/yaml testcase occurs") err := builtin.LoadFile(path, profile)
if err != nil {
log.Warn().Str("path", path).
Msg("failed to load profile, ignore!")
return err
} }
profile := iCaseConverter.Struct().Profile
if profile == nil { for _, step := range c.TCase.TestSteps {
return tCase, nil
}
for _, step := range 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 +279,5 @@ func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error)
step.Request.Cookies[k] = v step.Request.Cookies[k] = v
} }
} }
return tCase, nil return nil
} }

View File

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

View File

@@ -14,65 +14,27 @@ var (
harProfileOverridePath = "../../../examples/data/har/profile_override.yml" 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 +105,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 +124,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 +136,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 +146,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 +159,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 +172,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 +192,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 +212,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 +238,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()
} }

View File

@@ -1,78 +1,29 @@
package convert package convert
import ( import (
"reflect"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
) )
func NewConverterJSON(converter *TCaseConverter) *ConverterJSON { func LoadJSONCase(path string) (*hrp.TCase, error) {
return &ConverterJSON{ // load json case file
converter: converter, caseJSON := new(hrp.TCase)
} err := builtin.LoadFile(path, caseJSON)
}
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 { if err != nil {
return "", err return nil, errors.Wrap(err, "load json file failed")
} }
jsonPath := c.converter.genOutputPath(suffixJSON) if reflect.ValueOf(*caseJSON).IsZero() {
err = builtin.Dump2JSON(testCase, jsonPath) return nil, errors.New("invalid json file")
if err != nil {
return "", err
} }
return jsonPath, nil
}
func (c *ConverterJSON) ToYAML() (string, error) { // convert json case to TCase
testCase, err := c.makeTestCase() err = caseJSON.MakeCompat()
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 { if err != nil {
return nil, err return nil, err
} }
err = tCase.MakeCompat() return caseJSON, nil
if err != nil {
return nil, err
}
return tCase, nil
} }

View File

@@ -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 reflect.ValueOf(*casePostman).IsZero() {
return nil, errors.New("invalid postman file")
}
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
} }

View File

@@ -12,30 +12,8 @@ var (
collectionProfilePath = "../../../examples/data/postman/profile.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) { func TestLoadCollection(t *testing.T) {
casePostman, err := converterPostman.load() casePostman, err := loadCasePostman(collectionPath)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fatal(err) t.Fatal(err)
} }
@@ -45,7 +23,7 @@ func TestLoadCollection(t *testing.T) {
} }
func TestMakeTestCaseFromCollection(t *testing.T) { func TestMakeTestCaseFromCollection(t *testing.T) {
tCase, err := converterPostman.makeTestCase() tCase, err := LoadPostmanCase(collectionPath)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.Fatal() t.Fatal()
} }
@@ -102,49 +80,3 @@ func TestMakeTestCaseFromCollection(t *testing.T) {
t.Fatal() 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()
}
}

View File

@@ -1,40 +1 @@
package convert 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
}

View File

@@ -0,0 +1,26 @@
package convert
import (
"reflect"
"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 reflect.ValueOf(*caseSwagger).IsZero() {
return nil, errors.New("invalid swagger file")
}
// convert swagger to TCase
return nil, nil
}

View File

@@ -1,69 +1,29 @@
package convert package convert
import ( import (
"reflect"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
) )
func NewConverterYAML(converter *TCaseConverter) *ConverterYAML { func NewYAMLCase(path string) (*hrp.TCase, error) {
return &ConverterYAML{ // load yaml case file
converter: converter, caseJSON := new(hrp.TCase)
} err := builtin.LoadFile(path, caseJSON)
}
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 { if err != nil {
return "", err return nil, errors.Wrap(err, "load yaml file failed")
} }
jsonPath := c.converter.genOutputPath(suffixJSON) if reflect.ValueOf(*caseJSON).IsZero() {
err = builtin.Dump2JSON(testCase, jsonPath) return nil, errors.New("invalid yaml file")
if err != nil {
return "", err
} }
return jsonPath, nil
}
func (c *ConverterYAML) ToYAML() (string, error) { // convert json case to TCase
testCase, err := c.makeTestCase() err = caseJSON.MakeCompat()
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 { if err != nil {
return nil, err return nil, err
} }
err = tCase.MakeCompat() return caseJSON, nil
if err != nil {
return nil, err
}
return tCase, nil
} }