feat: add --profile flag for har2case to support overwrite headers/cookies with specified yaml/json profile file

This commit is contained in:
debugtalk
2022-03-26 10:44:19 +08:00
parent d3b3b80d17
commit a5f7eea9c3
16 changed files with 190 additions and 1136 deletions

View File

@@ -2,15 +2,18 @@
## 4.0.0 ## 4.0.0
- refactor: merge [hrp] into httprunner repo - refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine
**go version** **go version**
- feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file
- change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis - change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis
- fix: call referenced api/testcase with relative path - fix: call referenced api/testcase with relative path
**python version** **python version**
- change: remove locust, you should run load tests with go version
- change: remove fastapi and uvicorn dependencies
- fix: ignore exceptions when reporting GA events - fix: ignore exceptions when reporting GA events
- fix: remove misuse of NoReturn in Python typing - fix: remove misuse of NoReturn in Python typing

View File

@@ -33,4 +33,4 @@ Copyright 2021 debugtalk
* [hrp run](hrp_run.md) - run API test * [hrp run](hrp_run.md) - run API test
* [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp startproject](hrp_startproject.md) - create a scaffold project
###### Auto generated by spf13/cobra on 23-Mar-2022 ###### Auto generated by spf13/cobra on 26-Mar-2022

View File

@@ -41,4 +41,4 @@ hrp boom [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 23-Mar-2022 ###### Auto generated by spf13/cobra on 26-Mar-2022

View File

@@ -15,6 +15,7 @@ hrp har2case $har_path... [flags]
``` ```
-h, --help help for har2case -h, --help help for har2case
-d, --output-dir string specify output directory, default to the same dir with har file -d, --output-dir string specify output directory, default to the same dir with har file
-p, --profile string specify profile path to override headers and cookies
-j, --to-json convert to JSON format (default true) -j, --to-json convert to JSON format (default true)
-y, --to-yaml convert to YAML format -y, --to-yaml convert to YAML format
``` ```
@@ -23,4 +24,4 @@ hrp har2case $har_path... [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 23-Mar-2022 ###### Auto generated by spf13/cobra on 26-Mar-2022

View File

@@ -34,4 +34,4 @@ hrp run $path... [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 23-Mar-2022 ###### Auto generated by spf13/cobra on 26-Mar-2022

View File

@@ -19,4 +19,4 @@ hrp startproject $project_name [flags]
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing. * [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
###### Auto generated by spf13/cobra on 23-Mar-2022 ###### Auto generated by spf13/cobra on 26-Mar-2022

View File

@@ -1,4 +1,4 @@
headers: headers:
Content-Type: "application/x-www-form-urlencoded" Content-Type: "application/x-www-form-urlencoded"
cookies: cookies:
CASTGC: "TGT" UserName: "debugtalk"

View File

@@ -6,7 +6,7 @@ import (
"github.com/httprunner/httprunner/hrp" "github.com/httprunner/httprunner/hrp"
) )
// generated by examples/hrp/har/demo.har using HttpRunner v3.1.6 // generated by examples/data/har/demo.har using HttpRunner v3.1.6
var ( var (
demoHttpRunnerJSONPath hrp.TestCasePath = "demo_httprunner.json" demoHttpRunnerJSONPath hrp.TestCasePath = "demo_httprunner.json"
demoHttpRunnerYAMLPath hrp.TestCasePath = "demo_httprunner.yaml" demoHttpRunnerYAMLPath hrp.TestCasePath = "demo_httprunner.yaml"

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,12 @@ var har2caseCmd = &cobra.Command{
if outputDir != "" { if outputDir != "" {
har.SetOutputDir(outputDir) har.SetOutputDir(outputDir)
} }
// specify profile
if profilePath != "" {
har.SetProfile(profilePath)
}
// generate json/yaml files // generate json/yaml files
if genYAMLFlag { if genYAMLFlag {
outputPath, err = har.GenYAML() outputPath, err = har.GenYAML()
@@ -54,6 +60,7 @@ var (
genJSONFlag bool genJSONFlag bool
genYAMLFlag bool genYAMLFlag bool
outputDir string outputDir string
profilePath string
) )
func init() { func init() {
@@ -61,4 +68,5 @@ func init() {
har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", true, "convert to JSON format") 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().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(&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")
} }

View File

@@ -6,7 +6,6 @@ import (
"encoding/csv" "encoding/csv"
"encoding/hex" "encoding/hex"
builtinJSON "encoding/json" builtinJSON "encoding/json"
"errors"
"fmt" "fmt"
"math" "math"
"math/rand" "math/rand"
@@ -17,6 +16,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -235,13 +235,12 @@ func LoadFile(path string, structObj interface{}) (err error) {
log.Info().Str("path", path).Msg("load file") log.Info().Str("path", path).Msg("load file")
file, err := readFile(path) file, err := readFile(path)
if err != nil { if err != nil {
log.Error().Err(err).Msg("read file failed") return errors.Wrap(err, "read file failed")
return err
} }
ext := filepath.Ext(path) ext := filepath.Ext(path)
switch ext { switch ext {
case ".json": case ".json", ".har":
decoder := json.NewDecoder(bytes.NewReader(file)) decoder := json.NewDecoder(bytes.NewReader(file))
decoder.UseNumber() decoder.UseNumber()
err = decoder.Decode(structObj) err = decoder.Decode(structObj)

View File

@@ -3,9 +3,7 @@ package har2case
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@@ -31,13 +29,25 @@ func NewHAR(path string) *har {
} }
type har struct { type har struct {
path string path string
filterStr string filterStr string
excludeStr string excludeStr string
outputDir string profileJSON map[string]interface{}
outputDir string
}
func (h *har) SetProfile(path string) {
log.Info().Str("path", path).Msg("set profile")
h.profileJSON = make(map[string]interface{})
err := builtin.LoadFile(path, h.profileJSON)
if err != nil {
log.Warn().Str("path", path).
Msg("invalid profile format, ignore!")
}
} }
func (h *har) SetOutputDir(dir string) { func (h *har) SetOutputDir(dir string) {
log.Info().Str("dir", dir).Msg("set output directory")
h.outputDir = dir h.outputDir = dir
} }
@@ -93,23 +103,11 @@ func (h *har) makeTestCase() (*hrp.TCase, error) {
} }
func (h *har) load() (*Har, error) { func (h *har) load() (*Har, error) {
fp, err := os.Open(h.path)
if err != nil {
return nil, fmt.Errorf("open: %w", err)
}
data, err := io.ReadAll(fp)
fp.Close()
if err != nil {
return nil, fmt.Errorf("read: %w", err)
}
har := &Har{} har := &Har{}
err = json.Unmarshal(data, har) err := builtin.LoadFile(h.path, har)
if err != nil { if err != nil {
return nil, fmt.Errorf("json.Unmarshal error: %w", err) return nil, errors.Wrap(err, "load har failed")
} }
return har, nil return har, nil
} }
@@ -147,6 +145,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
Request: &hrp.Request{}, Request: &hrp.Request{},
Validators: make([]interface{}, 0), Validators: make([]interface{}, 0),
}, },
profileJSON: h.profileJSON,
} }
if err := step.makeRequestMethod(entry); err != nil { if err := step.makeRequestMethod(entry); err != nil {
return nil, err return nil, err
@@ -174,6 +173,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
type tStep struct { type tStep struct {
hrp.TStep hrp.TStep
profileJSON map[string]interface{}
} }
func (s *tStep) makeRequestMethod(entry *Entry) error { func (s *tStep) makeRequestMethod(entry *Entry) error {
@@ -201,6 +201,21 @@ func (s *tStep) makeRequestParams(entry *Entry) error {
func (s *tStep) makeRequestCookies(entry *Entry) error { func (s *tStep) makeRequestCookies(entry *Entry) error {
s.Request.Cookies = make(map[string]string) s.Request.Cookies = make(map[string]string)
cookies, ok := s.profileJSON["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!")
}
// use cookies from har
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
} }
@@ -209,6 +224,21 @@ func (s *tStep) makeRequestCookies(entry *Entry) error {
func (s *tStep) makeRequestHeaders(entry *Entry) error { func (s *tStep) makeRequestHeaders(entry *Entry) error {
s.Request.Headers = make(map[string]string) s.Request.Headers = make(map[string]string)
headers, ok := s.profileJSON["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!")
}
// use headers from har
for _, header := range entry.Request.Headers { for _, header := range entry.Request.Headers {
if strings.EqualFold(header.Name, "cookie") { if strings.EqualFold(header.Name, "cookie") {
continue continue

View File

@@ -9,8 +9,9 @@ import (
) )
var ( var (
harPath = "../../../examples/hrp/har/demo.har" harPath = "../../../examples/data/har/demo.har"
harPath2 = "../../../examples/hrp/har/postman-echo.har" harPath2 = "../../../examples/data/har/postman-echo.har"
profilePath = "../../../examples/data/har/profile.yml"
) )
func TestGenJSON(t *testing.T) { func TestGenJSON(t *testing.T) {
@@ -47,6 +48,26 @@ func TestLoadHAR(t *testing.T) {
} }
} }
func TestLoadHARWithProfile(t *testing.T) {
har := NewHAR(harPath)
har.SetProfile(profilePath)
_, err := har.load()
if !assert.NoError(t, err) {
t.Fail()
}
if !assert.Equal(t,
map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"},
har.profileJSON["headers"]) {
t.Fail()
}
if !assert.Equal(t,
map[string]interface{}{"UserName": "debugtalk"},
har.profileJSON["cookies"]) {
t.Fail()
}
}
func TestMakeTestCase(t *testing.T) { func TestMakeTestCase(t *testing.T) {
har := NewHAR(harPath) har := NewHAR(harPath)
tCase, err := har.makeTestCase() tCase, err := har.makeTestCase()
@@ -115,12 +136,105 @@ func TestMakeTestCase(t *testing.T) {
} }
func TestGetFilenameWithoutExtension(t *testing.T) { func TestGetFilenameWithoutExtension(t *testing.T) {
filename := getFilenameWithoutExtension("../../../examples/hrp/har/postman-echo.har") filename := getFilenameWithoutExtension(harPath2)
if !assert.Equal(t, "postman-echo", filename) { if !assert.Equal(t, "postman-echo", filename) {
t.Fail() t.Fail()
} }
} }
func TestMakeRequestHeaders(t *testing.T) {
har := NewHAR("")
entry := &Entry{
Request: Request{
Method: "POST",
Headers: []NVP{
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
},
},
}
step, err := har.prepareTestStep(entry)
if !assert.NoError(t, err) {
t.Fail()
}
if !assert.Equal(t, map[string]string{
"Content-Type": "application/json; charset=utf-8",
}, step.Request.Headers) {
t.Fail()
}
}
func TestMakeRequestHeadersWithProfile(t *testing.T) {
har := NewHAR("")
har.SetProfile(profilePath)
entry := &Entry{
Request: Request{
Method: "POST",
Headers: []NVP{
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
},
},
}
step, err := har.prepareTestStep(entry)
if !assert.NoError(t, err) {
t.Fail()
}
if !assert.Equal(t, map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
}, step.Request.Headers) {
t.Fail()
}
}
func TestMakeRequestCookies(t *testing.T) {
har := NewHAR("")
entry := &Entry{
Request: Request{
Method: "POST",
Cookies: []Cookie{
{Name: "abc", Value: "123"},
{Name: "UserName", Value: "leolee"},
},
},
}
step, err := har.prepareTestStep(entry)
if !assert.NoError(t, err) {
t.Fail()
}
if !assert.Equal(t, map[string]string{
"abc": "123",
"UserName": "leolee",
}, step.Request.Cookies) {
t.Fail()
}
}
func TestMakeRequestCookiesWithProfile(t *testing.T) {
har := NewHAR("")
har.SetProfile(profilePath)
entry := &Entry{
Request: Request{
Method: "POST",
Cookies: []Cookie{
{Name: "abc", Value: "123"},
{Name: "UserName", Value: "leolee"},
},
},
}
step, err := har.prepareTestStep(entry)
if !assert.NoError(t, err) {
t.Fail()
}
if !assert.Equal(t, map[string]string{
"UserName": "debugtalk",
}, step.Request.Cookies) {
t.Fail()
}
}
func TestMakeRequestDataParams(t *testing.T) { func TestMakeRequestDataParams(t *testing.T) {
har := NewHAR("") har := NewHAR("")
entry := &Entry{ entry := &Entry{