diff --git a/assert.go b/assert.go index 2a29005..2354776 100644 --- a/assert.go +++ b/assert.go @@ -2,8 +2,6 @@ package cute import ( "net/http" - - "github.com/ozontech/cute/errors" ) // This is type of asserts, for create some assert with using custom logic. @@ -123,73 +121,3 @@ func (it *Test) assertBody(t internalT, body []byte) []error { return errs }) } - -func isOptionError(err error) bool { - if tErr, ok := err.(errors.OptionalError); ok { - return tErr.IsOptional() - } - - return false -} - -func optionalAssertHeaders(assert AssertHeaders) AssertHeaders { - return func(headers http.Header) error { - err := assert(headers) - - return wrapOptionalError(err) - } -} - -func optionalAssertBody(assert AssertBody) AssertBody { - return func(body []byte) error { - err := assert(body) - - return wrapOptionalError(err) - } -} - -func optionalAssertResponse(assert AssertResponse) AssertResponse { - return func(resp *http.Response) error { - err := assert(resp) - - return wrapOptionalError(err) - } -} - -func optionalAssertHeadersT(assert AssertHeadersT) AssertHeadersT { - return func(t T, headers http.Header) error { - err := assert(t, headers) - - return wrapOptionalError(err) - } -} - -func optionalAssertBodyT(assert AssertBodyT) AssertBodyT { - return func(t T, body []byte) error { - err := assert(t, body) - - return wrapOptionalError(err) - } -} - -func optionalAssertResponseT(assert AssertResponseT) AssertResponseT { - return func(t T, resp *http.Response) error { - err := assert(t, resp) - - return wrapOptionalError(err) - } -} - -func wrapOptionalError(err error) error { - if err == nil { - return nil - } - - if tErr, ok := err.(errors.OptionalError); ok { - tErr.SetOptional(true) - - return tErr.(error) - } - - return errors.NewOptionalError(err.Error()) -} diff --git a/assert_optional.go b/assert_optional.go new file mode 100644 index 0000000..93c1ac6 --- /dev/null +++ b/assert_optional.go @@ -0,0 +1,69 @@ +package cute + +import ( + "net/http" + + "github.com/ozontech/cute/errors" +) + +func optionalAssertHeaders(assert AssertHeaders) AssertHeaders { + return func(headers http.Header) error { + err := assert(headers) + + return wrapOptionalError(err) + } +} + +func optionalAssertBody(assert AssertBody) AssertBody { + return func(body []byte) error { + err := assert(body) + + return wrapOptionalError(err) + } +} + +func optionalAssertResponse(assert AssertResponse) AssertResponse { + return func(resp *http.Response) error { + err := assert(resp) + + return wrapOptionalError(err) + } +} + +func optionalAssertHeadersT(assert AssertHeadersT) AssertHeadersT { + return func(t T, headers http.Header) error { + err := assert(t, headers) + + return wrapOptionalError(err) + } +} + +func optionalAssertBodyT(assert AssertBodyT) AssertBodyT { + return func(t T, body []byte) error { + err := assert(t, body) + + return wrapOptionalError(err) + } +} + +func optionalAssertResponseT(assert AssertResponseT) AssertResponseT { + return func(t T, resp *http.Response) error { + err := assert(t, resp) + + return wrapOptionalError(err) + } +} + +func wrapOptionalError(err error) error { + if err == nil { + return nil + } + + if tErr, ok := err.(errors.OptionalError); ok { + tErr.SetOptional(true) + + return tErr.(error) + } + + return errors.NewOptionalError(err.Error()) +} diff --git a/assert_require.go b/assert_require.go new file mode 100644 index 0000000..bf760af --- /dev/null +++ b/assert_require.go @@ -0,0 +1,69 @@ +package cute + +import ( + "net/http" + + "github.com/ozontech/cute/errors" +) + +func requireAssertHeaders(assert AssertHeaders) AssertHeaders { + return func(headers http.Header) error { + err := assert(headers) + + return wrapRequireError(err) + } +} + +func requireAssertBody(assert AssertBody) AssertBody { + return func(body []byte) error { + err := assert(body) + + return wrapRequireError(err) + } +} + +func requireAssertResponse(assert AssertResponse) AssertResponse { + return func(resp *http.Response) error { + err := assert(resp) + + return wrapRequireError(err) + } +} + +func requireAssertHeadersT(assert AssertHeadersT) AssertHeadersT { + return func(t T, headers http.Header) error { + err := assert(t, headers) + + return wrapRequireError(err) + } +} + +func requireAssertBodyT(assert AssertBodyT) AssertBodyT { + return func(t T, body []byte) error { + err := assert(t, body) + + return wrapRequireError(err) + } +} + +func requireAssertResponseT(assert AssertResponseT) AssertResponseT { + return func(t T, resp *http.Response) error { + err := assert(t, resp) + + return wrapRequireError(err) + } +} + +func wrapRequireError(err error) error { + if err == nil { + return nil + } + + if tErr, ok := err.(errors.RequireError); ok { + tErr.SetRequire(true) + + return tErr.(error) + } + + return errors.NewRequireError(err.Error()) +} diff --git a/builder.go b/builder.go index d4d1b23..19979f8 100644 --- a/builder.go +++ b/builder.go @@ -10,7 +10,7 @@ import ( const defaultHTTPTimeout = 30 var ( - errorAssertIsNil = "Assert must be not nil" + errorAssertIsNil = "assert must be not nil" ) // HTTPTestMaker is a creator tests @@ -468,6 +468,18 @@ func (it *cute) OptionalAssertBody(asserts ...AssertBody) ExpectHTTPBuilder { return it } +func (it *cute) RequireBody(asserts ...AssertBody) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertBody = append(it.tests[it.countTests].Expect.AssertBody, requireAssertBody(assert)) + } + + return it +} + func (it *cute) AssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder { for _, assert := range asserts { if assert == nil { @@ -492,6 +504,18 @@ func (it *cute) OptionalAssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilde return it } +func (it *cute) RequireHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertHeaders = append(it.tests[it.countTests].Expect.AssertHeaders, requireAssertHeaders(assert)) + } + + return it +} + func (it *cute) AssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder { for _, assert := range asserts { if assert == nil { @@ -516,6 +540,18 @@ func (it *cute) OptionalAssertResponse(asserts ...AssertResponse) ExpectHTTPBuil return it } +func (it *cute) RequireResponse(asserts ...AssertResponse) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertResponse = append(it.tests[it.countTests].Expect.AssertResponse, requireAssertResponse(assert)) + } + + return it +} + func (it *cute) AssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder { for _, assert := range asserts { if assert == nil { @@ -540,6 +576,18 @@ func (it *cute) OptionalAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder { return it } +func (it *cute) RequireBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertBodyT = append(it.tests[it.countTests].Expect.AssertBodyT, requireAssertBodyT(assert)) + } + + return it +} + func (it *cute) AssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder { for _, assert := range asserts { if assert == nil { @@ -564,6 +612,18 @@ func (it *cute) OptionalAssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuil return it } +func (it *cute) RequireHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertHeadersT = append(it.tests[it.countTests].Expect.AssertHeadersT, requireAssertHeadersT(assert)) + } + + return it +} + func (it *cute) AssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder { for _, assert := range asserts { if assert == nil { @@ -588,6 +648,18 @@ func (it *cute) OptionalAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBu return it } +func (it *cute) RequireResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder { + for _, assert := range asserts { + if assert == nil { + panic(errorAssertIsNil) + } + + it.tests[it.countTests].Expect.AssertResponseT = append(it.tests[it.countTests].Expect.AssertResponseT, requireAssertResponseT(assert)) + } + + return it +} + func (it *cute) EnableHardValidation() ExpectHTTPBuilder { it.tests[it.countTests].HardValidation = true diff --git a/cute.go b/cute.go index 3ba3ad6..e06eb81 100644 --- a/cute.go +++ b/cute.go @@ -120,6 +120,15 @@ func (it *cute) executeTest(ctx context.Context, allureProvider allureProvider) inT.Logf("Test start %v", tableTestName) resT := currentTest.execute(ctx, inT) res = append(res, resT) + + if resT.IsFailed() { + inT.Fail() + + allureProvider.Logf("Test was failed %v", currentTest.Name) + + return + } + inT.Logf("Test finished %v", tableTestName) }) } else { @@ -129,8 +138,17 @@ func (it *cute) executeTest(ctx context.Context, allureProvider allureProvider) currentTest.Name = allureProvider.Name() allureProvider.Logf("Test start %v", currentTest.Name) + resT := currentTest.execute(ctx, allureProvider) res = append(res, resT) + + if resT.IsFailed() { + allureProvider.Fail() + + allureProvider.Logf("Test was failed %v", currentTest.Name) + break + } + allureProvider.Logf("Test finished %v", currentTest.Name) } } diff --git a/errors/error.go b/errors/error.go index 93d70f5..bff32ed 100644 --- a/errors/error.go +++ b/errors/error.go @@ -19,18 +19,13 @@ type WithFields interface { PutFields(map[string]interface{}) } -// OptionalError is interface for put parameters in allure step. -// If function returns error, which implement this interface, allure step will have skip status -type OptionalError interface { - IsOptional() bool - SetOptional(bool) -} - type assertError struct { optional bool - name string - message string - fields map[string]interface{} + require bool + + name string + message string + fields map[string]interface{} } func NewAssertError(name string, message string, actual interface{}, expected interface{}) error { @@ -74,26 +69,10 @@ func (a *assertError) SetOptional(opt bool) { a.optional = opt } -type optionalError struct { - err string - optional bool -} - -func NewOptionalError(err string) error { - return &optionalError{ - optional: true, - err: err, - } -} - -func (o *optionalError) Error() string { - return o.err -} - -func (o *optionalError) IsOptional() bool { - return o.optional +func (a *assertError) IsRequire() bool { + return a.require } -func (o *optionalError) SetOptional(opt bool) { - o.optional = opt +func (a *assertError) SetRequire(b bool) { + a.require = b } diff --git a/errors/optional.go b/errors/optional.go new file mode 100644 index 0000000..892d66e --- /dev/null +++ b/errors/optional.go @@ -0,0 +1,33 @@ +package errors + +// OptionalError is interface for set error like optional error. +// If function returns error, which implement this interface, allure step will have skip status +type OptionalError interface { + IsOptional() bool + SetOptional(bool) +} + +type optionalError struct { + err string + optional bool +} + +// NewOptionalError ... +func NewOptionalError(err string) error { + return &optionalError{ + optional: true, + err: err, + } +} + +func (o *optionalError) Error() string { + return o.err +} + +func (o *optionalError) IsOptional() bool { + return o.optional +} + +func (o *optionalError) SetOptional(opt bool) { + o.optional = opt +} diff --git a/errors/require.go b/errors/require.go new file mode 100644 index 0000000..0ca8d8f --- /dev/null +++ b/errors/require.go @@ -0,0 +1,36 @@ +package errors + +// RequireError is interface for set error like require error. +// If function returns error, which implement this interface, allure step will have failed status +type RequireError interface { + IsRequire() bool + SetRequire(bool) +} + +type requireError struct { + err string + require bool +} + +// NewRequireError ... +func NewRequireError(err string) error { + return &requireError{ + require: true, + err: err, + } +} + +// Error .. +func (o *requireError) Error() string { + return o.err +} + +// IsRequire ... +func (o *requireError) IsRequire() bool { + return o.require +} + +// SetRequire ... +func (o *requireError) SetRequire(require bool) { + o.require = require +} diff --git a/examples/single_test.go b/examples/single_test.go index 08affb0..5df7fce 100644 --- a/examples/single_test.go +++ b/examples/single_test.go @@ -36,6 +36,7 @@ func Test_Single_1(t *testing.T) { }{ Name: "Vasya Pupkin", }), + cute.WithQueryKV("socks", "42"), cute.WithMethod(http.MethodGet), ). ExpectExecuteTimeout(10*time.Second). diff --git a/examples/two_step_test.go b/examples/two_step_test.go index 2b3ef50..7a8ddcc 100644 --- a/examples/two_step_test.go +++ b/examples/two_step_test.go @@ -5,6 +5,7 @@ package examples import ( "context" + "errors" "fmt" "io" "log" @@ -92,13 +93,19 @@ func Test_TwoSteps_3(t *testing.T) { cute.WithMethod(http.MethodGet), ). ExpectStatus(http.StatusOK). + RequireBody(func(body []byte) error { + return errors.New("example") + }). NextTest(). AfterTestExecute(func(response *http.Response, errors []error) error { // Execute after first step responseCode = response.StatusCode + fmt.Println("Hello from after test execute") + fmt.Println("Response code", responseCode) + return nil }). - // Second step + // Second step. This test isn't run, because previous test has failed require validation Create(). RequestBuilder( cute.WithURI("https://jsonplaceholder.typicode.com/posts/2/comments"), diff --git a/go.mod b/go.mod index 5c2e0a2..931d708 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.18 require ( github.com/PaesslerAG/jsonpath v0.1.1 - github.com/moul/http2curl v1.0.0 github.com/ozontech/allure-go/pkg/allure v0.6.4 github.com/ozontech/allure-go/pkg/framework v0.6.18 github.com/stretchr/testify v1.7.1 github.com/xeipuuv/gojsonschema v1.2.0 + moul.io/http2curl/v2 v2.3.0 ) require ( @@ -17,7 +17,6 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/smartystreets/goconvey v1.7.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index e031c56..53cd677 100644 --- a/go.sum +++ b/go.sum @@ -8,41 +8,55 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/ozontech/allure-go/pkg/allure v0.6.4 h1:lQ2NJSgl3sj/0oi4HTG+8EXkV1A+wWN7WIfDkOaBtmY= github.com/ozontech/allure-go/pkg/allure v0.6.4/go.mod h1:xyVZ+6tLDzQ4pr19eqLR0EckaI51kYUC1A5ihsDTygo= github.com/ozontech/allure-go/pkg/framework v0.6.18 h1:QoYFww5gMu3qPaoVDVetBa17ahKTHNYEhbyUIP+qYn8= github.com/ozontech/allure-go/pkg/framework v0.6.18/go.mod h1:QNKRZHCSCxJHSBuJssp00Fc7EL5XvVX/IEJNSgxBN18= +github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/interface.go b/interface.go index 0174e02..7cf277d 100644 --- a/interface.go +++ b/interface.go @@ -142,10 +142,17 @@ type RequestHTTPBuilder interface { // WithMethod // WithURL // WithHeaders + // WithHeadersKV // WithBody // WithMarshalBody // WithBody // WithURI + // WithQuery + // WithQueryKV + // WithFileForm + // WithFileFormKV + // WithForm + // WithFormKV RequestBuilder(r ...RequestBuilder) ExpectHTTPBuilder RequestParams @@ -191,14 +198,18 @@ type ExpectHTTPBuilder interface { // NotPresent is a function to asserts that jsonpath expression value is not present // Also you can write you assert. AssertBody(asserts ...AssertBody) ExpectHTTPBuilder + // RequireBody implements the same assertions as the `AssertBody`, but stops test execution when a test fails. + RequireBody(asserts ...AssertBody) ExpectHTTPBuilder // OptionalAssertBody is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertBody(asserts ...AssertBody) ExpectHTTPBuilder // AssertBodyT is function for validate response body with help testing.TB and allure allureProvider. // You may create allure step inside assert, add attachment, log information, etc. AssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder + // RequireBodyT implements the same assertions as the `AssertBodyT`, but stops test execution when a test fails. + RequireBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder // OptionalAssertBodyT is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder // AssertHeaders is function for validate response headers @@ -207,30 +218,39 @@ type ExpectHTTPBuilder interface { // NotPresent is a function to asserts header is present // Also you can write you assert. AssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder + // RequireHeaders implements the same assertions as the `AssertHeaders`, but stops test execution when a test fails. + RequireHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder // OptionalAssertHeaders is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder // AssertHeadersT is function for validate headers body with help testing.TB and allure allureProvider. // You may create allure step inside assert, add attachment, log information, etc. AssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder + // RequireHeadersT implements the same assertions as the `AssertHeadersT`, but stops test execution when a test fails. + RequireHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder // OptionalAssertHeadersT is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder // AssertResponse is function for validate response AssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder + // RequireResponse implements the same assertions as the `AssertResponse`, but stops test execution when a test fails. + RequireResponse(asserts ...AssertResponse) ExpectHTTPBuilder // OptionalAssertResponse is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder // AssertResponseT is function for validate response with help testing.TB. // You may create allure step inside assert, add attachment, log information, etc. AssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder + // RequireResponseT implements the same assertions as the `AssertResponseT`, but stops test execution when a test fails. + RequireResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder // OptionalAssertResponseT is not a mandatory assert. - // Mark in allure as Broken + // Mark in allure as Skipped OptionalAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder // EnableHardValidation is enabled hard validation, // If one of assert was failed, test will stopped. + // Deprecated. Please use require asserts. EnableHardValidation() ExpectHTTPBuilder After @@ -258,9 +278,10 @@ type ResultsHTTPBuilder interface { GetHTTPResponse() *http.Response // GetErrors is a function, which returns all errors from test GetErrors() []error - - // GetName is a function, which returns Name Test + // GetName is a function, which returns name of Test GetName() string + // IsFailed is a function, which returns flag about status of test + IsFailed() bool } // BeforeExecute ... diff --git a/request.go b/request.go index 4e24880..57eb395 100644 --- a/request.go +++ b/request.go @@ -16,10 +16,12 @@ type File struct { } type requestOptions struct { - method string - url *url.URL + method string + url *url.URL + uri string headers map[string][]string + query map[string][]string body []byte bodyMarshal interface{} fileForms map[string]*File @@ -29,6 +31,7 @@ type requestOptions struct { func newRequestOptions() *requestOptions { return &requestOptions{ headers: make(map[string][]string), + query: make(map[string][]string), fileForms: make(map[string]*File), forms: make(map[string][]byte), } @@ -55,10 +58,12 @@ func WithURI(uri string) func(o *requestOptions) { } } -// WithHeaders is a function for set headers in request +// WithHeaders is a function for set or merge headers in request func WithHeaders(headers map[string][]string) func(o *requestOptions) { return func(o *requestOptions) { - o.headers = headers + for key, values := range headers { + o.headers[key] = append(o.headers[key], values...) + } } } @@ -69,6 +74,22 @@ func WithHeadersKV(name string, value string) func(o *requestOptions) { } } +// WithQueryKV is a function for set query in request +func WithQueryKV(name string, value string) func(o *requestOptions) { + return func(o *requestOptions) { + o.query[name] = []string{value} + } +} + +// WithQuery is a function for set or merge query parameters in request +func WithQuery(queries map[string][]string) func(o *requestOptions) { + return func(o *requestOptions) { + for key, values := range queries { + o.query[key] = values + } + } +} + // WithBody is a function for set body in request func WithBody(body []byte) func(o *requestOptions) { return func(o *requestOptions) { diff --git a/request_test.go b/request_test.go index e8e9fd2..902ca31 100644 --- a/request_test.go +++ b/request_test.go @@ -10,12 +10,17 @@ import ( func TestRequest(t *testing.T) { var ( - req = &requestOptions{} + req = newRequestOptions() headers = map[string][]string{ "key": []string{ "value", }, } + query = map[string][]string{ + "query_key": []string{ + "query_value", + }, + } method = http.MethodGet mBody = map[string]interface{}{ "key": map[string]interface{}{ @@ -34,11 +39,13 @@ func TestRequest(t *testing.T) { WithMethod(method)(req) WithURL(u)(req) WithBody(body)(req) + WithQuery(query)(req) require.Equal(t, req.headers, headers) require.Equal(t, req.uri, uri) require.Equal(t, req.bodyMarshal, mBody) require.Equal(t, req.method, method) + require.Equal(t, req.query, query) require.Equal(t, req.body, body) require.Equal(t, req.url, u) diff --git a/results.go b/results.go index 688086a..58f57c0 100644 --- a/results.go +++ b/results.go @@ -5,16 +5,18 @@ import ( ) type testResults struct { - name string - resp *http.Response - errors []error + isFailed bool + name string + resp *http.Response + errors []error } -func newTestResult(name string, resp *http.Response, errs []error) ResultsHTTPBuilder { +func newTestResult(name string, resp *http.Response, isFailed bool, errs []error) ResultsHTTPBuilder { return &testResults{ - name: name, - resp: resp, - errors: errs, + name: name, + resp: resp, + isFailed: isFailed, + errors: errs, } } @@ -29,3 +31,7 @@ func (r *testResults) GetErrors() []error { func (r *testResults) GetName() string { return r.name } + +func (r *testResults) IsFailed() bool { + return r.isFailed +} diff --git a/results_test.go b/results_test.go index 31eaa7d..47ccd6d 100644 --- a/results_test.go +++ b/results_test.go @@ -19,8 +19,9 @@ func TestResult(t *testing.T) { name = "Name" testResults ResultsHTTPBuilder = &testResults{ - name: name, - resp: resp, + name: name, + resp: resp, + isFailed: true, errors: []error{ firstErr, secondErr, @@ -28,7 +29,8 @@ func TestResult(t *testing.T) { } ) + require.Equal(t, name, testResults.GetName()) + require.Equal(t, true, testResults.IsFailed()) require.Equal(t, resp, testResults.GetHTTPResponse()) require.Equal(t, []error{firstErr, secondErr}, testResults.GetErrors()) - require.Equal(t, name, testResults.GetName()) } diff --git a/roundtripper.go b/roundtripper.go index 3a8ebad..06ca947 100644 --- a/roundtripper.go +++ b/roundtripper.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/moul/http2curl" "github.com/ozontech/allure-go/pkg/allure" cuteErrors "github.com/ozontech/cute/errors" "github.com/ozontech/cute/internal/utils" + "moul.io/http2curl/v2" ) func (it *Test) makeRequest(t internalT, req *http.Request) (*http.Response, []error) { diff --git a/test.go b/test.go index c166f1c..c70b4c8 100644 --- a/test.go +++ b/test.go @@ -9,6 +9,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "os" "testing" "time" @@ -173,20 +174,27 @@ func (it *Test) execute(ctx context.Context, allureProvider allureProvider) Resu resp, errs = it.startTest(ctx, allureProvider) } - it.processTestErrors(allureProvider, errs) + isFailedTest := it.processTestErrors(allureProvider, errs) // Remove from base struct all asserts it.clearFields() - return newTestResult(name, resp, errs) + return newTestResult(name, resp, isFailedTest, errs) } -func (it *Test) processTestErrors(t internalT, errs []error) { +// processTestErrors returns flag, which mean finish test or not. +// true - need finish test +// false - continue +func (it *Test) processTestErrors(t internalT, errs []error) bool { + var ( + countNotOptionalErrors = 0 + failTest = false + ) + if len(errs) == 0 { - return + return false } - resErrors := make([]error, 0) for _, err := range errs { if tErr, ok := err.(cuteErrors.OptionalError); ok { if tErr.IsOptional() { @@ -195,31 +203,29 @@ func (it *Test) processTestErrors(t internalT, errs []error) { } } - resErrors = append(resErrors, err) - } - - if len(resErrors) == 0 { - return - } - - if len(resErrors) == 1 { - t.Errorf("[ERROR] %v", resErrors[0]) - if it.HardValidation { - t.FailNow() + if tErr, ok := err.(cuteErrors.RequireError); ok { + if tErr.IsRequire() { + failTest = true + } } - return - } - - for _, err := range resErrors { t.Errorf("[ERROR] %v", err) + + countNotOptionalErrors++ } - t.Errorf("Test finished with %v errors", len(resErrors)) + if countNotOptionalErrors != 0 { + t.Errorf("Test finished with %v errors", countNotOptionalErrors) + } - if it.HardValidation { - t.FailNow() + // If we have not optional errors and hardValidation (assert errors) + // or if we have failed tests (require errors) + // we should fail test + if (it.HardValidation && countNotOptionalErrors != 0) || failTest { + return true } + + return false } func (it *Test) startTestWithStep(ctx context.Context, t internalT) (*http.Response, []error) { @@ -256,7 +262,7 @@ func (it *Test) startTest(ctx context.Context, t internalT) (*http.Response, []e // CreateWithStep request req, err := it.createRequest(ctx) if err != nil { - return resp, []error{err} + return nil, []error{err} } // Execute Before @@ -367,18 +373,35 @@ func (it *Test) buildRequest(ctx context.Context) (*http.Request, error) { var ( req *http.Request err error - o = newRequestOptions() + + o = newRequestOptions() ) + // Set builder parameters for _, builder := range it.Request.Builders { builder(o) } - url := o.uri - if o.url != nil { - url = o.url.String() + reqURL := o.url + + if reqURL == nil { + reqURL, err = url.Parse(o.uri) + if err != nil { + return nil, err + } } + // Set query parameters + query := reqURL.Query() + for key, values := range o.query { + for _, value := range values { + query.Add(key, value) + } + } + + reqURL.RawQuery = query.Encode() + + // Set body body := o.body if o.bodyMarshal != nil { body, err = json.Marshal(o.bodyMarshal) // TODO move marshaler to it struct @@ -388,6 +411,7 @@ func (it *Test) buildRequest(ctx context.Context) (*http.Request, error) { } } + // Set multipart if len(o.fileForms) != 0 || len(o.forms) != 0 { var ( buffer = new(bytes.Buffer) @@ -414,22 +438,24 @@ func (it *Test) buildRequest(ctx context.Context) (*http.Request, error) { return nil, err } - req, err = http.NewRequestWithContext(ctx, o.method, url, buffer) + req, err = http.NewRequestWithContext(ctx, o.method, reqURL.String(), buffer) if err != nil { return nil, err } req.Header.Add("Content-Type", mp.FormDataContentType()) } else { - req, err = http.NewRequestWithContext(ctx, o.method, url, io.NopCloser(bytes.NewReader(body))) + req, err = http.NewRequestWithContext(ctx, o.method, reqURL.String(), io.NopCloser(bytes.NewReader(body))) if err != nil { return nil, err } } + // Set headers for nameHeader, valuesHeader := range o.headers { req.Header[nameHeader] = valuesHeader } + return req, nil }