From db72a37ae78407aa18b6cb9f921bc450cf71f472 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 27 Jun 2022 00:25:32 +0800 Subject: [PATCH] refactor: simplify testcase converter --- docs/CHANGELOG.md | 1 + hrp/internal/convert/converter.go | 276 +++++++----------- hrp/internal/convert/converter_har.go | 109 ++----- hrp/internal/convert/converter_har_test.go | 117 +------- hrp/internal/convert/converter_json.go | 73 +---- hrp/internal/convert/converter_postman.go | 121 ++------ .../convert/converter_postman_test.go | 72 +---- hrp/internal/convert/converter_pytest.go | 39 --- hrp/internal/convert/converter_swagger.go | 26 ++ hrp/internal/convert/converter_yaml.go | 64 +--- 10 files changed, 228 insertions(+), 670 deletions(-) create mode 100644 hrp/internal/convert/converter_swagger.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0bda2b7d..ff38210a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,7 @@ - fix: failed to load json/data content in api reference - fix: failed to convert postman collection containing multipart/form-data requests to pytest - fix: only get the first parameter in referenced testcase +- refactor: simplify testcase converter **python version** diff --git a/hrp/internal/convert/converter.go b/hrp/internal/convert/converter.go index 107afa15..7bb11ed9 100644 --- a/hrp/internal/convert/converter.go +++ b/hrp/internal/convert/converter.go @@ -4,9 +4,7 @@ import ( _ "embed" "fmt" "path/filepath" - "reflect" - "github.com/go-openapi/spec" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -15,6 +13,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) +// target testcase format extensions const ( suffixJSON = ".json" suffixYAML = ".yaml" @@ -83,15 +82,11 @@ 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 + InputPath string + OutputDir string + InputType InputType + OutputType OutputType + TCase *hrp.TCase } // 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"` } -func NewTCaseConverter(path string) (tCaseConverter *TCaseConverter) { - tCaseConverter = &TCaseConverter{ - InputPath: path, - InputType: InputTypeUnknown, - } +// LoadTCase loads source file and convert to TCase type +func LoadTCase(path string) (*hrp.TCase, error) { extName := filepath.Ext(path) if extName == "" { - log.Warn().Msg("extension name should be specified") - return + return nil, errors.New("file extension is not specified") } - 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 + tCase, err := LoadHARCase(path) + if err != nil { + return nil, err } + return tCase, nil 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 + // priority: hrp JSON case > postman > swagger + // check if hrp JSON case + tCase, err := LoadJSONCase(path) + if err == nil { + return tCase, nil } - 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 + + // check if postman format + casePostman, err := LoadPostmanCase(path) + if err == nil { + return casePostman, nil } - caseSwagger := new(spec.Swagger) - err = builtin.LoadFile(path, caseSwagger) - if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { - tCaseConverter.InputType = InputTypeSwagger - tCaseConverter.CaseSwagger = caseSwagger + + // check if swagger format + caseSwagger, err := LoadSwaggerCase(path) + if err == nil { + return caseSwagger, nil } + + return nil, errors.New("unexpected JSON format") 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 + // priority: hrp YAML case > swagger + // check if hrp YAML case + tCase, err := NewYAMLCase(path) + if err == nil { + return tCase, nil } - caseSwagger := new(spec.Swagger) - err = builtin.LoadFile(path, caseSwagger) - if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { - tCaseConverter.InputType = InputTypeSwagger - tCaseConverter.CaseSwagger = caseSwagger + + // check if swagger format + caseSwagger, err := LoadSwaggerCase(path) + if err == nil { + return caseSwagger, nil } + + return nil, errors.New("unexpected YAML format") case ".go": // TODO - tCaseConverter.InputType = InputTypeGoTest + return nil, errors.New("convert gotest is not implemented") case ".py": // TODO - tCaseConverter.InputType = InputTypePyTest + return nil, errors.New("convert pytest is not implemented") case ".jmx": // TODO - tCaseConverter.InputType = InputTypeJMeter - default: - log.Warn(). - Str("input path", tCaseConverter.InputPath). - Msgf("unsupported file type: %v", extName) + return nil, errors.New("convert JMeter jmx is not implemented") } - 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 + return nil, fmt.Errorf("unsupported file type: %v", extName) } func (c *TCaseConverter) genOutputPath(suffix string) string { @@ -209,40 +166,39 @@ func (c *TCaseConverter) genOutputPath(suffix string) string { // TODO avoid outFileFullName conflict? } +// convert TCase to pytest case 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 + args := append([]string{"make"}, c.InputPath) + 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 } -// 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) +// 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 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()), }) - // 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 err error - for _, iCaseConverter := range iCaseConverters { - log.Info().Str("input path", iCaseConverter.Struct().InputPath).Msg("start converting") + for _, path := range args { + // loads source file in support types and convert to TCase format + 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 - switch iCaseConverter.Struct().OutputType { + switch outputType { case OutputTypeYAML: - outputFile, err = iCaseConverter.ToYAML() + outputFile, err = caseConverter.ToYAML() case OutputTypeGoTest: - outputFile, err = iCaseConverter.ToGoTest() + outputFile, err = caseConverter.ToGoTest() case OutputTypePyTest: - outputFile, err = iCaseConverter.ToPyTest() + outputFile, err = caseConverter.ToPyTest() default: - outputFile, err = iCaseConverter.ToJSON() + outputFile, err = caseConverter.ToJSON() } if err != nil { log.Error().Err(err). - Str("input path", iCaseConverter.Struct().InputPath). - Msg("error occurs during converting") + Str("source path", path). + Msg("convert case failed") continue } 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") } -func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error) { - tCase := iCaseConverter.Struct().TCase - if tCase == nil { - return nil, errors.Errorf("empty json/yaml testcase occurs") +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 } - profile := iCaseConverter.Struct().Profile - if profile == nil { - return tCase, nil - } - for _, step := range tCase.TestSteps { + + for _, step := range c.TCase.TestSteps { // override original headers and cookies if profile.Override { step.Request.Headers = make(map[string]string) @@ -339,5 +279,5 @@ func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error) step.Request.Cookies[k] = v } } - return tCase, nil + return nil } diff --git a/hrp/internal/convert/converter_har.go b/hrp/internal/convert/converter_har.go index 6ee9c156..7d004dbf 100644 --- a/hrp/internal/convert/converter_har.go +++ b/hrp/internal/convert/converter_har.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "fmt" "net/url" + "reflect" "sort" "strings" "time" @@ -357,56 +358,31 @@ type TestResult struct { // ==================== model definition ends here ==================== -func NewConverterHAR(converter *TCaseConverter) *ConverterHAR { - return &ConverterHAR{ - converter: converter, - } -} - -type ConverterHAR struct { - converter *TCaseConverter -} - -func (c *ConverterHAR) Struct() *TCaseConverter { - return c.converter -} - -func (c *ConverterHAR) ToJSON() (string, error) { - tCase, err := c.makeTestCase() +func LoadHARCase(path string) (*hrp.TCase, error) { + // load har file + caseHAR, err := loadCaseHAR(path) 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 { - return "", err + return nil, errors.Wrap(err, "load har file failed") } - return jsonPath, nil -} - -func (c *ConverterHAR) ToYAML() (string, error) { - tCase, err := c.makeTestCase() - if err != nil { - return "", err + if reflect.ValueOf(*caseHAR).IsZero() { + return nil, errors.New("invalid har file") } - yamlPath := c.converter.genOutputPath(suffixYAML) - err = builtin.Dump2YAML(tCase, yamlPath) - if err != nil { - return "", err - } - return yamlPath, nil + return caseHAR, nil } -func (c *ConverterHAR) ToGoTest() (string, error) { - //TODO implement me - 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) { +// convert CaseHar to TCase format +func (c *CaseHar) ToTCase() (*hrp.TCase, error) { teststeps, err := c.prepareTestSteps() if err != nil { return nil, err @@ -423,27 +399,14 @@ func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) { return tCase, nil } -func (c *ConverterHAR) load() (*CaseHar, error) { - har := c.converter.CaseHAR - if har == nil { - return nil, errors.New("empty har case occurs") - } - return har, nil -} - -func (c *ConverterHAR) prepareConfig() *hrp.TConfig { +func (c *CaseHar) prepareConfig() *hrp.TConfig { return hrp.NewConfig("testcase description"). SetVerifySSL(false) } -func (c *ConverterHAR) prepareTestSteps() ([]*hrp.TStep, error) { - har, err := c.load() - if err != nil { - return nil, err - } - +func (c *CaseHar) prepareTestSteps() ([]*hrp.TStep, error) { var steps []*hrp.TStep - for _, entry := range har.Log.Entries { + for _, entry := range c.Log.Entries { step, err := c.prepareTestStep(&entry) if err != nil { return nil, err @@ -454,7 +417,7 @@ func (c *ConverterHAR) prepareTestSteps() ([]*hrp.TStep, error) { return steps, nil } -func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) { +func (c *CaseHar) prepareTestStep(entry *Entry) (*hrp.TStep, error) { log.Info(). Str("method", entry.Request.Method). Str("url", entry.Request.URL). @@ -465,7 +428,6 @@ func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) { Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, - profile: c.converter.Profile, } if err := step.makeRequestMethod(entry); err != nil { return nil, err @@ -493,7 +455,6 @@ func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) { type stepFromHAR struct { hrp.TStep - profile *Profile } func (s *stepFromHAR) makeRequestMethod(entry *Entry) error { @@ -525,18 +486,6 @@ func (s *stepFromHAR) makeRequestCookies(entry *Entry) error { for _, cookie := range entry.Request.Cookies { 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 } @@ -549,18 +498,6 @@ func (s *stepFromHAR) makeRequestHeaders(entry *Entry) error { } 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 } diff --git a/hrp/internal/convert/converter_har_test.go b/hrp/internal/convert/converter_har_test.go index af94c98c..711e191e 100644 --- a/hrp/internal/convert/converter_har_test.go +++ b/hrp/internal/convert/converter_har_test.go @@ -14,65 +14,27 @@ var ( harProfileOverridePath = "../../../examples/data/har/profile_override.yml" ) -var converterHAR = NewConverterHAR(NewTCaseConverter(harPath)) -var converterHAR2 = NewConverterHAR(NewTCaseConverter(harPath2)) +var caseHar *CaseHar -func TestHAR2JSON(t *testing.T) { - jsonPath, err := converterHAR.ToJSON() - 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 init() { + caseHar, _ = loadCaseHAR(harPath) } func TestLoadHAR(t *testing.T) { - h, err := converterHAR.load() + caseHAR, err := loadCaseHAR(harPath) if !assert.NoError(t, err) { 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() } - if !assert.Equal(t, "POST", h.Log.Entries[1].Request.Method) { + if !assert.Equal(t, "POST", caseHAR.Log.Entries[1].Request.Method) { t.Fatal() } } -func TestLoadHARWithProfile(t *testing.T) { - tCaseConverter := NewTCaseConverter(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() +func TestLoadTCaseFromHAR(t *testing.T) { + tCase, err := LoadHARCase(harPath) if !assert.NoError(t, err) { t.Fatal() } @@ -143,7 +105,7 @@ func TestMakeRequestURL(t *testing.T) { URL: "http://127.0.0.1:8080/api/login", }, } - step, err := converterHAR.prepareTestStep(entry) + step, err := caseHar.prepareTestStep(entry) if !assert.NoError(t, err) { 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) { 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) { entry := &Entry{ 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) { 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) { entry := &Entry{ 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) { 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) { 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) { 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) { t.Fatal() } diff --git a/hrp/internal/convert/converter_json.go b/hrp/internal/convert/converter_json.go index f55a7f5c..0eaf1019 100644 --- a/hrp/internal/convert/converter_json.go +++ b/hrp/internal/convert/converter_json.go @@ -1,78 +1,29 @@ package convert import ( + "reflect" + "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() +func LoadJSONCase(path string) (*hrp.TCase, error) { + // load json case file + caseJSON := new(hrp.TCase) + err := builtin.LoadFile(path, caseJSON) if err != nil { - return "", err + return nil, errors.Wrap(err, "load json file failed") } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(testCase, jsonPath) - if err != nil { - return "", err + if reflect.ValueOf(*caseJSON).IsZero() { + return nil, errors.New("invalid json file") } - 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) + // convert json case to TCase + err = caseJSON.MakeCompat() if err != nil { return nil, err } - err = tCase.MakeCompat() - if err != nil { - return nil, err - } - return tCase, nil + return caseJSON, nil } diff --git a/hrp/internal/convert/converter_postman.go b/hrp/internal/convert/converter_postman.go index 742a8721..01d9f3e5 100644 --- a/hrp/internal/convert/converter_postman.go +++ b/hrp/internal/convert/converter_postman.go @@ -112,66 +112,37 @@ var contentTypeMap = map[string]string{ "xml": "application/xml", } -func NewConverterPostman(converter *TCaseConverter) *ConverterPostman { - return &ConverterPostman{ - converter: converter, - } -} - -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() +func LoadPostmanCase(path string) (*hrp.TCase, error) { + // load postman file + casePostman, err := loadCasePostman(path) if err != nil { 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 { return nil, err } tCase := &hrp.TCase{ - Config: c.prepareConfig(casePostman), + Config: c.prepareConfig(), TestSteps: teststeps, } err = tCase.MakeCompat() @@ -181,23 +152,15 @@ func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) { return tCase, nil } -func (c *ConverterPostman) load() (*CasePostman, error) { - casePostman := c.converter.CasePostman - 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). +func (c *CasePostman) prepareConfig() *hrp.TConfig { + return hrp.NewConfig(c.Info.Name). 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 var itemList []TItem - for _, item := range casePostman.Items { + for _, item := range c.Items { 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(). Str("method", item.Request.Method). Str("url", item.Request.URL.Raw). @@ -240,7 +203,6 @@ func (c *ConverterPostman) prepareTestStep(item *TItem) (*hrp.TStep, error) { Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, - profile: c.converter.Profile, } if err := step.makeRequestName(item); err != nil { return nil, err @@ -268,7 +230,6 @@ func (c *ConverterPostman) prepareTestStep(item *TItem) (*hrp.TStep, error) { type stepFromPostman struct { hrp.TStep - profile *Profile } // 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 } - - 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 } @@ -341,18 +290,6 @@ func (s *stepFromPostman) makeRequestCookies(item *TItem) error { } 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 } diff --git a/hrp/internal/convert/converter_postman_test.go b/hrp/internal/convert/converter_postman_test.go index 57085bf2..2315a5bd 100644 --- a/hrp/internal/convert/converter_postman_test.go +++ b/hrp/internal/convert/converter_postman_test.go @@ -12,30 +12,8 @@ var ( 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() + casePostman, err := loadCasePostman(collectionPath) if !assert.NoError(t, err) { t.Fatal(err) } @@ -45,7 +23,7 @@ func TestLoadCollection(t *testing.T) { } func TestMakeTestCaseFromCollection(t *testing.T) { - tCase, err := converterPostman.makeTestCase() + tCase, err := LoadPostmanCase(collectionPath) if !assert.NoError(t, err) { t.Fatal() } @@ -102,49 +80,3 @@ func TestMakeTestCaseFromCollection(t *testing.T) { 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() - } -} diff --git a/hrp/internal/convert/converter_pytest.go b/hrp/internal/convert/converter_pytest.go index c2edca2c..233bcded 100644 --- a/hrp/internal/convert/converter_pytest.go +++ b/hrp/internal/convert/converter_pytest.go @@ -1,40 +1 @@ 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 -} diff --git a/hrp/internal/convert/converter_swagger.go b/hrp/internal/convert/converter_swagger.go new file mode 100644 index 00000000..ce3c7557 --- /dev/null +++ b/hrp/internal/convert/converter_swagger.go @@ -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 +} diff --git a/hrp/internal/convert/converter_yaml.go b/hrp/internal/convert/converter_yaml.go index 2ad783b1..93abc0f0 100644 --- a/hrp/internal/convert/converter_yaml.go +++ b/hrp/internal/convert/converter_yaml.go @@ -1,69 +1,29 @@ package convert import ( + "reflect" + "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() +func NewYAMLCase(path string) (*hrp.TCase, error) { + // load yaml case file + caseJSON := new(hrp.TCase) + err := builtin.LoadFile(path, caseJSON) if err != nil { - return "", err + return nil, errors.Wrap(err, "load yaml file failed") } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(testCase, jsonPath) - if err != nil { - return "", err + if reflect.ValueOf(*caseJSON).IsZero() { + return nil, errors.New("invalid yaml file") } - 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) + // convert json case to TCase + err = caseJSON.MakeCompat() if err != nil { return nil, err } - err = tCase.MakeCompat() - if err != nil { - return nil, err - } - return tCase, nil + return caseJSON, nil }