add unittest; add --patch options

This commit is contained in:
buyuxiang
2022-05-17 16:03:45 +08:00
parent ac0fc8b4e4
commit 115b5cbc3f
18 changed files with 604 additions and 248 deletions

View File

@@ -7,7 +7,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/internal/convert"
"github.com/httprunner/httprunner/v4/hrp/internal/convert/case2script"
)
var convertCmd = &cobra.Command{
@@ -18,15 +18,16 @@ var convertCmd = &cobra.Command{
setLogLevel(logLevel)
},
RunE: func(cmd *cobra.Command, args []string) error {
// TODO: integrate har2case, postman2case, etc. in convert command (forward compatibility)
if !pytestFlag && !gotestFlag {
return errors.New("please specify convertion type")
}
var err error
if gotestFlag {
err = convert.Convert2TestScripts("gotest", args...)
err = case2script.Convert2TestScripts("gotest", args...)
} else {
err = convert.Convert2TestScripts("pytest", args...)
err = case2script.Convert2TestScripts("pytest", args...)
}
if err != nil {
log.Error().Err(err).Msg("convert test scripts failed")

View File

@@ -6,7 +6,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/internal/har2case"
"github.com/httprunner/httprunner/v4/hrp/internal/convert/har2case"
)
// har2caseCmd represents the har2case command
@@ -22,7 +22,7 @@ var har2caseCmd = &cobra.Command{
var outputFiles []string
for _, arg := range args {
// must choose one
if !genYAMLFlag && !genJSONFlag {
if !har2caseGenYAMLFlag && !har2caseGenJSONFlag {
return errors.New("please select convert format type")
}
var outputPath string
@@ -31,17 +31,22 @@ var har2caseCmd = &cobra.Command{
har := har2case.NewHAR(arg)
// specify output dir
if outputDir != "" {
har.SetOutputDir(outputDir)
if har2caseOutputDir != "" {
har.SetOutputDir(har2caseOutputDir)
}
// specify profile
if profilePath != "" {
har.SetProfile(profilePath)
if har2caseProfilePath != "" {
har.SetProfile(har2caseProfilePath)
}
// specify profile
if har2casePatchPath != "" {
har.SetPatch(har2casePatchPath)
}
// generate json/yaml files
if genYAMLFlag {
if har2caseGenYAMLFlag {
outputPath, err = har.GenYAML()
} else {
outputPath, err = har.GenJSON() // default
@@ -57,16 +62,18 @@ var har2caseCmd = &cobra.Command{
}
var (
genJSONFlag bool
genYAMLFlag bool
outputDir string
profilePath string
har2caseGenJSONFlag bool
har2caseGenYAMLFlag bool
har2caseOutputDir string
har2caseProfilePath string
har2casePatchPath string
)
func init() {
rootCmd.AddCommand(har2caseCmd)
har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", true, "convert to JSON format")
har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to YAML format")
har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file")
har2caseCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies")
har2caseCmd.Flags().BoolVarP(&har2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format")
har2caseCmd.Flags().BoolVarP(&har2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format")
har2caseCmd.Flags().StringVarP(&har2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file")
har2caseCmd.Flags().StringVarP(&har2caseProfilePath, "profile", "p", "", "specify profile path to override headers and cookies")
har2caseCmd.Flags().StringVarP(&har2casePatchPath, "patch", "r", "", "specify the path of the file used to replace headers and cookies")
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/internal/postman2case"
"github.com/httprunner/httprunner/v4/hrp/internal/convert/postman2case"
)
// postman2caseCmd represents the postman2case command
@@ -22,24 +22,34 @@ var postman2caseCmd = &cobra.Command{
var outputFiles []string
for _, arg := range args {
// must choose one
if !postman2JSONFlag && !postman2YAMLFlag {
if !postman2caseGenJSONFlag && !postman2caseGenYAMLFlag {
return errors.New("please select convert format type")
}
var outputPath string
var err error
postman := postman2case.NewCollection(arg)
collection := postman2case.NewCollection(arg)
// specify output dir
if postman2Dir != "" {
postman.SetOutputDir(postman2Dir)
if postman2caseOutputDir != "" {
collection.SetOutputDir(postman2caseOutputDir)
}
// specify profile path
if postman2caseProfilePath != "" {
collection.SetProfile(postman2caseProfilePath)
}
// specify patch path
if postman2casePatchPath != "" {
collection.SetPatch(postman2casePatchPath)
}
// generate json/yaml files
if genYAMLFlag {
outputPath, err = postman.GenYAML()
if postman2caseGenYAMLFlag {
outputPath, err = collection.GenYAML()
} else {
outputPath, err = postman.GenJSON() // default
outputPath, err = collection.GenJSON() // default
}
if err != nil {
return err
@@ -52,14 +62,18 @@ var postman2caseCmd = &cobra.Command{
}
var (
postman2JSONFlag bool
postman2YAMLFlag bool
postman2Dir string
postman2caseGenJSONFlag bool
postman2caseGenYAMLFlag bool
postman2caseOutputDir string
postman2caseProfilePath string
postman2casePatchPath string
)
func init() {
rootCmd.AddCommand(postman2caseCmd)
postman2caseCmd.Flags().BoolVarP(&postman2JSONFlag, "to-json", "j", true, "convert to JSON format")
postman2caseCmd.Flags().BoolVarP(&postman2YAMLFlag, "to-yaml", "y", false, "convert to YAML format")
postman2caseCmd.Flags().StringVarP(&postman2Dir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file")
postman2caseCmd.Flags().BoolVarP(&postman2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format")
postman2caseCmd.Flags().BoolVarP(&postman2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format")
postman2caseCmd.Flags().StringVarP(&postman2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file")
postman2caseCmd.Flags().StringVarP(&postman2caseProfilePath, "profile", "p", "", "specify profile path to override original headers (except for Content-Type) and cookies")
postman2caseCmd.Flags().StringVarP(&postman2casePatchPath, "patch", "r", "", "specify patch path to create or update headers and cookies")
}

View File

@@ -1,4 +1,4 @@
package convert
package case2script
import (
_ "embed"

View File

@@ -22,6 +22,13 @@ const (
suffixYAML = ".yaml"
)
const (
configProfile = "profile"
configPatch = "patch"
keyHeaders = "headers"
keyCookies = "cookies"
)
func NewHAR(path string) *har {
return &har{
path: path,
@@ -33,6 +40,7 @@ type har struct {
filterStr string
excludeStr string
profile map[string]interface{}
patch map[string]interface{}
outputDir string
}
@@ -46,6 +54,16 @@ func (h *har) SetProfile(path string) {
}
}
func (h *har) SetPatch(path string) {
log.Info().Str("path", path).Msg("set patch")
h.patch = make(map[string]interface{})
err := builtin.LoadFile(path, h.patch)
if err != nil {
log.Warn().Str("path", path).
Msg("invalid patch format, ignore!")
}
}
func (h *har) SetOutputDir(dir string) {
log.Info().Str("dir", dir).Msg("set output directory")
h.outputDir = dir
@@ -146,6 +164,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
Validators: make([]interface{}, 0),
},
profile: h.profile,
patch: h.patch,
}
if err := step.makeRequestMethod(entry); err != nil {
return nil, err
@@ -174,6 +193,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
type tStep struct {
hrp.TStep
profile map[string]interface{}
patch map[string]interface{}
}
func (s *tStep) makeRequestMethod(entry *Entry) error {
@@ -199,43 +219,59 @@ func (s *tStep) makeRequestParams(entry *Entry) error {
return nil
}
func (s *tStep) updateRequestInfo(config string, key string) bool {
var m map[string]interface{}
switch config {
case configProfile:
m = s.profile
case configPatch:
m = s.patch
default:
return false
}
iRequestMap, existed := m[key]
if existed {
requestMap, ok := iRequestMap.(map[string]interface{})
if ok {
for k, v := range requestMap {
switch key {
case keyHeaders:
s.Request.Headers[k] = fmt.Sprintf("%v", v)
case keyCookies:
s.Request.Cookies[k] = fmt.Sprintf("%v", v)
}
}
return true
}
log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config)
}
return false
}
func (s *tStep) makeRequestCookies(entry *Entry) error {
s.Request.Cookies = make(map[string]string)
cookies, ok := s.profile["cookies"]
if ok {
// use cookies from profile
cookies, ok := cookies.(map[string]interface{})
if ok {
for k, v := range cookies {
s.Request.Cookies[k] = fmt.Sprintf("%v", v)
}
return nil
}
log.Warn().Interface("cookies", cookies).
Msg("cookies from profile is not a map, ignore!")
// override all cookies according to the profile
if s.updateRequestInfo(configProfile, keyCookies) {
return nil
}
// use cookies from har
for _, cookie := range entry.Request.Cookies {
s.Request.Cookies[cookie.Name] = cookie.Value
}
// create or update the cookies indicated in the patch
s.updateRequestInfo(configPatch, keyCookies)
return nil
}
func (s *tStep) makeRequestHeaders(entry *Entry) error {
s.Request.Headers = make(map[string]string)
headers, ok := s.profile["headers"]
if ok {
// use headers from profile
cookies, ok := headers.(map[string]interface{})
if ok {
for k, v := range cookies {
s.Request.Headers[k] = fmt.Sprintf("%v", v)
}
return nil
}
log.Warn().Interface("headers", headers).
Msg("headers from profile is not a map, ignore!")
// override all headers according to the profile
if s.updateRequestInfo(configProfile, keyHeaders) {
return nil
}
// use headers from har
@@ -245,6 +281,9 @@ func (s *tStep) makeRequestHeaders(entry *Entry) error {
}
s.Request.Headers[header.Name] = header.Value
}
// create or update the headers indicated in the patch
s.updateRequestInfo(configPatch, keyHeaders)
return nil
}

View File

@@ -1,6 +1,7 @@
package har2case
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -9,9 +10,9 @@ import (
)
var (
harPath = "../../../examples/data/har/demo.har"
harPath2 = "../../../examples/data/har/postman-echo.har"
profilePath = "../../../examples/data/har/profile.yml"
harPath = "../../../../examples/data/har/demo.har"
harPath2 = "../../../../examples/data/har/postman-echo.har"
profilePath = "../../../../examples/data/har/profile.yml"
)
func TestGenJSON(t *testing.T) {
@@ -381,3 +382,32 @@ func TestMakeValidate(t *testing.T) {
t.Fatal()
}
}
func Test_tStep_makeRequestCookies(t *testing.T) {
type fields struct {
TStep hrp.TStep
profile map[string]interface{}
patch map[string]interface{}
}
type args struct {
entry *Entry
}
tests := []struct {
name string
fields fields
args args
wantErr assert.ErrorAssertionFunc
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &tStep{
TStep: tt.fields.TStep,
profile: tt.fields.profile,
patch: tt.fields.patch,
}
tt.wantErr(t, s.makeRequestCookies(tt.args.entry), fmt.Sprintf("makeRequestCookies(%v)", tt.args.entry))
})
}
}

View File

@@ -3,7 +3,6 @@ package postman2case
import (
"bytes"
"fmt"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
"io"
"mime/multipart"
"net/url"
@@ -17,6 +16,7 @@ import (
"github.com/httprunner/httprunner/v4/hrp"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
)
const (
@@ -33,11 +33,18 @@ const (
)
const (
suffixName = ".converted"
suffixName = ".converted" // distinguish the converted json(testcase) from the origin json(collection)
extensionJSON = ".json"
extensionYAML = ".yaml"
)
const (
configProfile = "profile"
configPatch = "patch"
keyHeaders = "headers"
keyCookies = "cookies"
)
var contentTypeMap = map[string]string{
"text": "text/plain",
"javascript": "application/javascript",
@@ -54,9 +61,31 @@ func NewCollection(path string) *collection {
type collection struct {
path string
profile map[string]interface{}
patch map[string]interface{}
outputDir string
}
func (c *collection) SetProfile(path string) {
log.Info().Str("path", path).Msg("set profile")
c.profile = make(map[string]interface{})
err := builtin.LoadFile(path, c.profile)
if err != nil {
log.Warn().Str("path", path).
Msg("invalid profile format, ignore!")
}
}
func (c *collection) SetPatch(path string) {
log.Info().Str("path", path).Msg("set patch")
c.patch = make(map[string]interface{})
err := builtin.LoadFile(path, c.patch)
if err != nil {
log.Warn().Str("path", path).
Msg("invalid patch format, ignore!")
}
}
func (c *collection) SetOutputDir(dir string) {
log.Info().Str("dir", dir).Msg("set output directory")
c.outputDir = dir
@@ -169,10 +198,12 @@ func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) {
Msg("convert teststep")
step := &tStep{
hrp.TStep{
TStep: hrp.TStep{
Request: &hrp.Request{},
Validators: make([]interface{}, 0),
},
profile: c.profile,
patch: c.patch,
}
if err := step.makeRequestName(item); err != nil {
return nil, err
@@ -186,20 +217,22 @@ func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) {
if err := step.makeRequestParams(item); err != nil {
return nil, err
}
if err := step.makeRequestHeadersAndCookies(item); err != nil {
if err := step.makeRequestHeaders(item); err != nil {
return nil, err
}
if err := step.makeRequestCookies(item); err != nil {
return nil, err
}
if err := step.makeRequestBody(item); err != nil {
return nil, err
}
if err := step.makeValidate(item); err != nil {
return nil, err
}
return &step.TStep, nil
}
type tStep struct {
hrp.TStep
profile map[string]interface{}
patch map[string]interface{}
}
// makeRequestName indicates the step name the same as item name
@@ -239,21 +272,89 @@ func (s *tStep) makeRequestParams(item *TItem) error {
return nil
}
func (s *tStep) makeRequestHeadersAndCookies(item *TItem) error {
s.Request.Headers = make(map[string]string)
for _, field := range item.Request.Headers {
if field.Disabled {
continue
func (s *tStep) updateRequestInfo(config string, key string) bool {
var m map[string]interface{}
switch config {
case configProfile:
m = s.profile
case configPatch:
m = s.patch
default:
return false
}
iRequestMap, existed := m[key]
if existed {
requestMap, ok := iRequestMap.(map[string]interface{})
if ok {
for k, v := range requestMap {
switch key {
case keyHeaders:
s.Request.Headers[k] = fmt.Sprintf("%v", v)
case keyCookies:
s.Request.Cookies[k] = fmt.Sprintf("%v", v)
}
}
return true
}
if strings.EqualFold(field.Key, "cookie") {
s.Request.Cookies[field.Key] = field.Value
log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config)
}
return false
}
func (s *tStep) makeRequestHeaders(item *TItem) error {
s.Request.Headers = make(map[string]string)
// override all headers according to the profile
if s.updateRequestInfo(configProfile, keyHeaders) {
return nil
}
// headers defined in postman collection
for _, field := range item.Request.Headers {
if field.Disabled || strings.EqualFold(field.Key, "cookie") {
continue
}
s.Request.Headers[field.Key] = field.Value
}
// create or update the headers indicated in the patch
s.updateRequestInfo(configPatch, keyHeaders)
return nil
}
func (s *tStep) makeRequestCookies(item *TItem) error {
s.Request.Cookies = make(map[string]string)
// override all cookies according to the profile
if s.updateRequestInfo(configProfile, keyCookies) {
return nil
}
// cookies defined in postman collection
for _, field := range item.Request.Headers {
if field.Disabled || !strings.EqualFold(field.Key, "cookie") {
continue
}
s.parseRequestCookiesMap(field.Value)
}
// create or update the cookies indicated in the patch
s.updateRequestInfo(configPatch, keyCookies)
return nil
}
func (s *tStep) parseRequestCookiesMap(cookies string) {
for _, cookie := range strings.Split(cookies, ";") {
cookie = strings.TrimSpace(cookie)
index := strings.Index(cookie, "=")
if index == -1 {
log.Warn().Str("cookie", cookie).Msg("cookie format invalid")
continue
}
s.Request.Cookies[cookie[0:index]] = cookie[index+1:]
}
}
func (s *tStep) makeRequestBody(item *TItem) error {
mode := item.Request.Body.Mode
if mode == "" {
@@ -267,7 +368,7 @@ func (s *tStep) makeRequestBody(item *TItem) error {
case enumBodyUrlEncoded:
return s.makeRequestBodyUrlEncoded(item)
case enumBodyFile, enumBodyGraphQL:
return errors.New("not supported body type")
return errors.Errorf("unsupported body type: %v", mode)
}
return nil
}

View File

@@ -0,0 +1,155 @@
package postman2case
import (
"testing"
"github.com/stretchr/testify/assert"
)
var (
collectionPath = "../../../../examples/data/postman2case/demo.json"
profilePath = "../../../../examples/data/postman2case/profile.yml"
patchPath = "../../../../examples/data/postman2case/patch.yml"
)
func TestGenJSON(t *testing.T) {
jsonPath, err := NewCollection(collectionPath).GenJSON()
if !assert.NoError(t, err) {
t.Fatal()
}
if !assert.NotEmpty(t, jsonPath) {
t.Fatal()
}
}
func TestGenYAML(t *testing.T) {
yamlPath, err := NewCollection(collectionPath).GenYAML()
if !assert.NoError(t, err) {
t.Fatal()
}
if !assert.NotEmpty(t, yamlPath) {
t.Fatal()
}
}
func TestLoadCollection(t *testing.T) {
tCollection, err := NewCollection(collectionPath).load()
if !assert.NoError(t, err) {
t.Fatal(err)
}
if !assert.Equal(t, "postman collection demo", tCollection.Info.Name) {
t.Fatal()
}
}
func TestMakeTestCase(t *testing.T) {
tCase, err := NewCollection(collectionPath).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 contains cookies)
// check headers
if !assert.Contains(t, tCase.TestSteps[1].Request.Headers["Content-Type"], "multipart/form-data") {
t.Fatal()
}
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.NotEmpty(t, tCase.TestSteps[1].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 TestMakeTestCaseWithProfile(t *testing.T) {
c := NewCollection(collectionPath)
c.SetProfile(profilePath)
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 step.Request.Method == "POST" && !assert.Len(t, step.Request.Headers, 2) {
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 TestMakeTestCaseWithPatch(t *testing.T) {
c := NewCollection(collectionPath)
c.SetPatch(patchPath)
tCase, err := c.makeTestCase()
if !assert.NoError(t, err) {
t.Fatal()
}
// create cookies Cookie1 indicated in patch
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 patch
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 patch
if !assert.Equal(t, "close", tCase.TestSteps[5].Request.Headers["Connection"]) {
t.Fatal()
}
}

View File

@@ -1,39 +0,0 @@
package postman2case
import (
"testing"
"github.com/stretchr/testify/assert"
)
var collectionPath = "../../../examples/data/postman2case/postman_collection.json"
func TestLoadPostmanCollection(t *testing.T) {
c, err := NewCollection(collectionPath).load()
if !assert.NoError(t, err) {
t.Fatal(err)
}
if !assert.Equal(t, "postman collection demo", c.Info.Name) {
t.Fatal()
}
}
func TestGenJSON(t *testing.T) {
jsonPath, err := NewCollection(collectionPath).GenJSON()
if !assert.NoError(t, err) {
t.Fatal()
}
if !assert.NotEmpty(t, jsonPath) {
t.Fatal()
}
}
func TestGenYAML(t *testing.T) {
yamlPath, err := NewCollection(collectionPath).GenYAML()
if !assert.NoError(t, err) {
t.Fatal()
}
if !assert.NotEmpty(t, yamlPath) {
t.Fatal()
}
}