mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-06 20:32:44 +08:00
feat: add --profile flag for har2case to support overwrite headers/cookies with specified yaml/json profile file
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
Reference in New Issue
Block a user