Skip to content

Commit

Permalink
feat: validate response object
Browse files Browse the repository at this point in the history
  • Loading branch information
debugtalk committed Sep 26, 2021
1 parent df7366a commit 9f59dbf
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 18 deletions.
2 changes: 1 addition & 1 deletion examples/postman_echo/hardcode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestCaseHardcode(t *testing.T) {
},
}

err := httpboomer.Test(testcase)
err := httpboomer.Test(t, testcase)
if err != nil {
t.Fatalf("run testcase error: %v", err)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/imroc/req v0.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/myzhan/boomer v1.6.0
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/shirou/gopsutil v3.21.8+incompatible // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U=
github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/myzhan/boomer v1.6.0 h1:xjgvmhDjgU9IEKnB7nU1HyoVEfj8SuuU3u6oY3Nugj0=
Expand Down Expand Up @@ -42,5 +45,6 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 4 additions & 4 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ type TRequest struct {
}

type TValidator struct {
Check string // get value with jmespath
Comparator string
Expect interface{}
Message string
Check string // get value with jmespath
Assert string
Expect interface{}
Message string
}

type TStep struct {
Expand Down
109 changes: 109 additions & 0 deletions response.go
Original file line number Diff line number Diff line change
@@ -1 +1,110 @@
package httpboomer

import (
"encoding/json"
"log"
"testing"

"github.com/imroc/req"
"github.com/jmespath/go-jmespath"
"github.com/stretchr/testify/assert"
)

var assertFunctionsMap = map[string]func(t assert.TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool{
"equals": assert.EqualValues,
"equal": assert.EqualValues, // alias for equals
"greater_than": assert.Greater,
"less_than": assert.Less,
"greater_or_equals": assert.GreaterOrEqual,
"less_or_equals": assert.LessOrEqual,
"not_equal": assert.NotEqual,
"contains": assert.Contains,
"regex_match": assert.Regexp,
}

func NewResponseObject(t *testing.T, resp *req.Resp) *ResponseObject {
// prepare response headers
headers := make(map[string]string)
for k, v := range resp.Response().Header {
if len(v) > 0 {
headers[k] = v[0]
}
}

// prepare response cookies
cookies := make(map[string]string)
for _, cookie := range resp.Response().Cookies() {
cookies[cookie.Name] = cookie.Value
}

// parse response body
var body interface{}
if err := json.Unmarshal(resp.Bytes(), &body); err != nil {
log.Fatalf("[NewResponseObject] json.Unmarshal response body err: %v, body: %v",
err, string(resp.Bytes()))
return nil
}

respObjMeta := respObjMeta{
StatusCode: resp.Response().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.Fatalf("[NewResponseObject] json.Unmarshal respObjMeta err: %v, respObjMetaBytes: %v",
err, string(respObjMetaBytes))
return nil
}

return &ResponseObject{
t: t,
respObjMeta: data,
}
}

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) Validate(validators []TValidator) error {
for _, validator := range validators {
// parse check value
checkItem := validator.Check
checkValue := v.searchJmespath(checkItem)
// get assert method
assertMethod := validator.Assert
assertFunc := assertFunctionsMap[assertMethod]
// parse expected value
expectValue := validator.Expect
// do assertion
result := assertFunc(v.t, expectValue, checkValue)
log.Printf("assert %s %s %v => %v", checkItem, assertMethod, expectValue, result)
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.Printf("[searchJmespath] jmespath.Search error: %v", err)
return nil
}
return checkValue
}
22 changes: 20 additions & 2 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@ package httpboomer
import (
"log"
"net/http"
"testing"

"github.com/imroc/req"
)

var defaultRunner = NewRunner()

func Test(testcases ...*TestCase) error {
return defaultRunner.Run(testcases...)
func Test(t *testing.T, testcases ...*TestCase) error {
return defaultRunner.WithTestingT(t).Run(testcases...)
}

func NewRunner() *Runner {
return &Runner{
t: &testing.T{},
Client: req.New(),
}
}

type Runner struct {
t *testing.T
Client *req.Req
}

func (r *Runner) WithTestingT(t *testing.T) *Runner {
r.t = t
return r
}

func (r *Runner) Run(testcases ...*TestCase) error {
for _, testcase := range testcases {
if err := r.runCase(testcase); err != nil {
Expand Down Expand Up @@ -62,6 +70,7 @@ func (r *Runner) runStep(step IStep, config *TConfig) error {
}

func (r *Runner) runStepRequest(step *TStep) error {
// prepare request args
var v []interface{}
v = append(v, req.Header(step.Request.Headers))
v = append(v, req.Param(step.Request.Params))
Expand All @@ -75,12 +84,21 @@ func (r *Runner) runStepRequest(step *TStep) error {
})
}

// do request action
req.Debug = true
resp, err := r.Client.Do(string(step.Request.Method), step.Request.URL, v...)
if err != nil {
return err
}
defer resp.Response().Body.Close()

// validate response
respObj := NewResponseObject(r.t, resp)
err = respObj.Validate(step.Validators)
if err != nil {
return err
}

return nil
}

Expand Down
6 changes: 3 additions & 3 deletions runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ func TestHttpRunner(t *testing.T) {
GET("/headers").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.Host", "httpbin.org", "check http response host"),
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
Step("user-agent").
GET("/user-agent").
Validate().
AssertEqual("status_code", 200, "check status code").
AssertEqual("body.\"user-agent\"", "python-requests", "check User-Agent"),
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
Step("TestCase3").CallRefCase(&TestCase{Config: TConfig{Name: "TestCase3"}}),
},
}
Expand All @@ -31,7 +31,7 @@ func TestHttpRunner(t *testing.T) {
},
}

err := Test(testcase1, testcase2)
err := Test(t, testcase1, testcase2)
if err != nil {
t.Fatalf("run testcase error: %v", err)
}
Expand Down
10 changes: 7 additions & 3 deletions step_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ var (
WithHeaders(map[string]string{"User-Agent": "HttpBoomer"}).
WithCookies(map[string]string{"user": "debugtalk"}).
Validate().
AssertEqual("status_code", 200, "check status code")
AssertEqual("status_code", 200, "check status code").
AssertEqual("headers.Connection", "keep-alive", "check header Connection").
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type").
AssertEqual("body.args.foo1", "bar1", "check param foo1").
AssertEqual("body.args.foo2", "bar2", "check param foo2")
stepPOSTData = Step("post form data").
POST("/post").
WithParams(map[string]interface{}{"foo1": "bar1", "foo2": "bar2"}).
Expand Down Expand Up @@ -73,10 +77,10 @@ func TestRunRequestRun(t *testing.T) {
config := &TConfig{
BaseURL: "https://postman-echo.com",
}
if err := defaultRunner.runStep(stepGET, config); err != nil {
if err := defaultRunner.WithTestingT(t).runStep(stepGET, config); err != nil {
t.Fatalf("tStep.Run() error: %s", err)
}
if err := defaultRunner.runStep(stepPOSTData, config); err != nil {
if err := defaultRunner.WithTestingT(t).runStep(stepPOSTData, config); err != nil {
t.Fatalf("tStepPOSTData.Run() error: %s", err)
}
}
12 changes: 7 additions & 5 deletions validate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package httpboomer

import "fmt"
import (
"fmt"
)

// implements IStep interface
type stepRequestValidation struct {
Expand All @@ -9,10 +11,10 @@ type stepRequestValidation struct {

func (s *stepRequestValidation) AssertEqual(jmesPath string, expected interface{}, msg string) *stepRequestValidation {
validator := TValidator{
Check: jmesPath,
Comparator: "equals",
Expect: expected,
Message: msg,
Check: jmesPath,
Assert: "equals",
Expect: expected,
Message: msg,
}
s.step.Validators = append(s.step.Validators, validator)
return s
Expand Down

0 comments on commit 9f59dbf

Please sign in to comment.