Files
httprunner/response.go
2021-12-22 19:36:53 +08:00

145 lines
3.5 KiB
Go

package hrp
import (
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/jmespath/go-jmespath"
"github.com/rs/zerolog/log"
"github.com/httprunner/hrp/internal/builtin"
)
func newResponseObject(t *testing.T, resp *http.Response) (*responseObject, error) {
// prepare response headers
headers := make(map[string]string)
for k, v := range resp.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
// prepare response cookies
cookies := make(map[string]string)
for _, cookie := range resp.Cookies() {
cookies[cookie.Name] = cookie.Value
}
// read response body
respBodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// parse response body
var body interface{}
if err := json.Unmarshal(respBodyBytes, &body); err != nil {
// response body is not json, use raw body
body = string(respBodyBytes)
}
respObjMeta := respObjMeta{
StatusCode: resp.StatusCode,
Headers: headers,
Cookies: cookies,
Body: body,
}
// convert respObjMeta to interface{}
respObjMetaBytes, _ := json.Marshal(respObjMeta)
var data interface{}
if err := json.Unmarshal(respObjMetaBytes, &data); err != nil {
log.Error().
Str("respObjMeta", string(respObjMetaBytes)).
Err(err).
Msg("[NewResponseObject] convert respObjMeta to interface{} failed")
return nil, err
}
return &responseObject{
t: t,
respObjMeta: data,
}, nil
}
type respObjMeta struct {
StatusCode int `json:"status_code"`
Headers map[string]string `json:"headers"`
Cookies map[string]string `json:"cookies"`
Body interface{} `json:"body"`
}
type responseObject struct {
t *testing.T
respObjMeta interface{}
validationResults map[string]interface{}
}
func (v *responseObject) Extract(extractors map[string]string) map[string]interface{} {
if extractors == nil {
return nil
}
extractMapping := make(map[string]interface{})
for key, value := range extractors {
extractedValue := v.searchJmespath(value)
log.Info().Str("from", value).Interface("value", extractedValue).Msg("extract value")
log.Info().Str("variable", key).Interface("value", extractedValue).Msg("set variable")
extractMapping[key] = extractedValue
}
return extractMapping
}
func (v *responseObject) Validate(validators []Validator, variablesMapping map[string]interface{}) (err error) {
for _, validator := range validators {
// parse check value
checkItem := validator.Check
var checkValue interface{}
if strings.Contains(checkItem, "$") {
// reference variable
checkValue, err = parseData(checkItem, variablesMapping)
if err != nil {
return err
}
} else {
checkValue = v.searchJmespath(checkItem)
}
// get assert method
assertMethod := validator.Assert
assertFunc := builtin.Assertions[assertMethod]
// parse expected value
expectValue, err := parseData(validator.Expect, variablesMapping)
if err != nil {
return err
}
// do assertion
result := assertFunc(v.t, expectValue, checkValue)
log.Info().
Str("assertMethod", assertMethod).
Interface("expectValue", expectValue).
Interface("checkValue", checkValue).
Bool("result", result).
Msgf("validate %s", checkItem)
if !result {
v.t.Fail()
}
}
return nil
}
func (v *responseObject) searchJmespath(expr string) interface{} {
checkValue, err := jmespath.Search(expr, v.respObjMeta)
if err != nil {
log.Error().Str("expr", expr).Err(err).Msg("search jmespath failed")
return expr // jmespath not found, return the expression
}
return checkValue
}