diff --git a/builder.go b/builder.go index e37c790..a90b4b8 100644 --- a/builder.go +++ b/builder.go @@ -19,6 +19,16 @@ type HTTPTestMaker struct { } // NewHTTPTestMaker is function for set options for all cute. +// For example, you can set timeout for all requests or set custom http client +// Options: +// - WithCustomHTTPTimeout - set timeout for all requests +// - WithHTTPClient - set custom http client +// - WithCustomHTTPRoundTripper - set custom http round tripper +// - WithJSONMarshaler - set custom json marshaler +// - WithMiddlewareAfter - set function which will run AFTER test execution +// - WithMiddlewareAfterT - set function which will run AFTER test execution with TB +// - WithMiddlewareBefore - set function which will run BEFORE test execution +// - WithMiddlewareBeforeT - set function which will run BEFORE test execution with TB func NewHTTPTestMaker(opts ...Option) *HTTPTestMaker { var ( o = &options{ diff --git a/builder_request.go b/builder_request.go index 772b468..2e89046 100644 --- a/builder_request.go +++ b/builder_request.go @@ -5,18 +5,54 @@ import ( "time" ) +// RequestRepeat is a function for set options in request +// if response.Code != Expect.Code, than request will repeat Count counts with Delay delay. +// Default delay is 1 second. func (qt *cute) RequestRepeat(count int) RequestHTTPBuilder { qt.tests[qt.countTests].Request.Repeat.Count = count return qt } +// RequestRepeatDelay set delay for request repeat. +// if response.Code != Expect.Code, than request will repeat Count counts with Delay delay. +// Default delay is 1 second. func (qt *cute) RequestRepeatDelay(delay time.Duration) RequestHTTPBuilder { qt.tests[qt.countTests].Request.Repeat.Delay = delay return qt } +// RequestRepeatPolitic set politic for request repeat. +// if response.Code != Expect.Code, than request will repeat Count counts with Delay delay. +// if Optional is true and request is failed, than test step allure will be skipped, and t.Fail() will not execute. +// If Broken is true and request is failed, than test step allure will be broken, and t.Fail() will not execute. +func (qt *cute) RequestRepeatPolitic(politic *RequestRepeatPolitic) RequestHTTPBuilder { + if politic == nil { + panic("politic is nil in RequestRepeatPolitic") + } + + qt.tests[qt.countTests].Request.Repeat = politic + + return qt +} + +// RequestRepeatOptional set option politic for request repeat. +// if Optional is true and request is failed, than test step allure will be skipped, and t.Fail() will not execute. +func (qt *cute) RequestRepeatOptional(option bool) RequestHTTPBuilder { + qt.tests[qt.countTests].Request.Repeat.Optional = option + + return qt +} + +// RequestRepeatBroken set broken politic for request repeat. +// If Broken is true and request is failed, than test step allure will be broken, and t.Fail() will not execute. +func (qt *cute) RequestRepeatBroken(broken bool) RequestHTTPBuilder { + qt.tests[qt.countTests].Request.Repeat.Broken = broken + + return qt +} + func (qt *cute) Request(r *http.Request) ExpectHTTPBuilder { qt.tests[qt.countTests].Request.Base = r diff --git a/errors/error.go b/errors/error.go index d2b875b..3a24f16 100644 --- a/errors/error.go +++ b/errors/error.go @@ -82,6 +82,14 @@ type CuteError struct { Attachments []*Attachment } +// NewCuteError is the function, which creates cute error with "Name" and "Message" for allure +func NewCuteError(name string, err error) *CuteError { + return &CuteError{ + Name: name, + Err: err, + } +} + // NewAssertError is the function, which creates error with "Actual" and "Expected" for allure func NewAssertError(name string, message string, actual interface{}, expected interface{}) error { return &CuteError{ @@ -94,15 +102,16 @@ func NewAssertError(name string, message string, actual interface{}, expected in } } -// NewAssertErrorWithMessage ... +// NewAssertErrorWithMessage is the function, which creates error with "Name" and "Message" for allure +// Deprecated: use NewEmptyAssertError instead func NewAssertErrorWithMessage(name string, message string) error { - return &CuteError{ - Name: name, - Message: message, - } + return NewEmptyAssertError(name, message) } -// NewEmptyAssertError ... +// NewEmptyAssertError is the function, which creates error with "Name" and "Message" for allure +// Returns AssertError with empty fields +// You can use PutFields and PutAttachment to add additional information +// You can use SetOptional, SetRequire, SetBroken to change error behavior func NewEmptyAssertError(name string, message string) AssertError { return &CuteError{ Name: name, @@ -111,12 +120,14 @@ func NewEmptyAssertError(name string, message string) AssertError { } } -// Unwrap ... +// Unwrap is a method to get wrapped error +// It is used for errors.Is and errors.As functions func (a *CuteError) Unwrap() error { return a.Err } -// Error ... +// Error is a method to get error message +// It is used for fmt.Errorf and fmt.Println functions func (a *CuteError) Error() string { if a.Trace == "" { return a.Message @@ -131,12 +142,14 @@ func (a *CuteError) Error() string { return fmt.Sprintf("%s\nCalled from: %s", errText, a.Trace) } -// GetName ... +// GetName is a method to get error name +// It is used for allure step name func (a *CuteError) GetName() string { return a.Name } -// SetName ... +// SetName is a method to set error name +// It is used for allure step name func (a *CuteError) SetName(name string) { a.Name = name } diff --git a/examples/single_test.go b/examples/single_test.go index 565dcbc..7b623e4 100644 --- a/examples/single_test.go +++ b/examples/single_test.go @@ -98,6 +98,42 @@ func Test_Single_Broken(t *testing.T) { ExecuteTest(context.Background(), t) } +func Test_Single_RepeatPolitic_Optional_Success_Test(t *testing.T) { + cute.NewTestBuilder(). + Title("Test_Single_RepeatPolitic_Optional_Success_Test"). + Create(). + RequestRepeat(2). + RequestRepeatOptional(true). + RequestBuilder( + cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), + ). + BrokenAssertBodyT(func(t cute.T, body []byte) error { + return errors.New("example broken error") + }). + ExpectStatus(http.StatusCreated). + ExecuteTest(context.Background(), t) + + t.Logf("You should see it") +} + +func Test_Single_RepeatPolitic_Broken_Failed_Test(t *testing.T) { + cute.NewTestBuilder(). + Title("Test_Single_RepeatPolitic_Broken_Failed_Test"). + Create(). + RequestRepeat(2). + RequestRepeatOptional(true). + RequestBuilder( + cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), + ). + BrokenAssertBodyT(func(t cute.T, body []byte) error { + return errors.New("example broken error") + }). + ExpectStatus(http.StatusCreated). + ExecuteTest(context.Background(), t) + + t.Logf("You should see it") +} + func Test_Single_Broken_2(t *testing.T) { cute.NewTestBuilder(). Title("Test_Single_Broken_2"). diff --git a/interface.go b/interface.go index 0ce147f..4af22d4 100644 --- a/interface.go +++ b/interface.go @@ -144,11 +144,11 @@ type TableTest interface { ControlTest } -// RequestHTTPBuilder is a scope of methods for create HTTP requests +// RequestHTTPBuilder is a scope of methods to create HTTP requests type RequestHTTPBuilder interface { // Request is function for set http.Request Request(r *http.Request) ExpectHTTPBuilder - // RequestBuilder is function for create http.Request with help builder. + // RequestBuilder is function for set http.Request with builders // Available builders: // WithMethod // WithURL @@ -169,13 +169,31 @@ type RequestHTTPBuilder interface { RequestParams } -// RequestParams is a scope of methods for configurate http request +// RequestParams is a scope of methods to configure request type RequestParams interface { - // RequestRepeat is a count of repeat request, if request was failed. + // RequestRepeat is a function for set options in request + // if response.Code != Expect.Code, than request will repeat counts with delay. + // Default delay is 1 second. RequestRepeat(count int) RequestHTTPBuilder - // RequestRepeatDelay is a time between repeat request, if request was failed. - // Default 1 second + + // RequestRepeatDelay set delay for request repeat. + // if response.Code != Expect.Code, than request will repeat counts with delay. + // Default delay is 1 second. RequestRepeatDelay(delay time.Duration) RequestHTTPBuilder + + // RequestRepeatPolitic is a politic for repeat request. + // if response.Code != Expect.Code, than request will repeat counts with delay. + // if Optional is true and request is failed, than test step allure will be skipped, and t.Fail() will not execute. + // If Broken is true and request is failed, than test step allure will be broken, and t.Fail() will execute. + RequestRepeatPolitic(politic *RequestRepeatPolitic) RequestHTTPBuilder + + // RequestRepeatOptional is a option politic for repeat request. + // if Optional is true and request is failed, than test step allure will be skipped, and t.Fail() will not execute. + RequestRepeatOptional(optional bool) RequestHTTPBuilder + + // RequestRepeatBroken is a broken politic for repeat request. + // If Broken is true and request is failed, than test step allure will be broken, and t.Fail() will execute. + RequestRepeatBroken(broken bool) RequestHTTPBuilder } // ExpectHTTPBuilder is a scope of methods for validate http response diff --git a/jsonschema.go b/jsonschema.go index 75b7c0a..cb27619 100644 --- a/jsonschema.go +++ b/jsonschema.go @@ -35,7 +35,7 @@ func checkJSONSchema(expect gojsonschema.JSONLoader, data []byte) []error { validateResult, err := gojsonschema.Validate(expect, gojsonschema.NewBytesLoader(data)) if err != nil { - return []error{errors.NewAssertErrorWithMessage("could not validate json schema", err.Error())} + return []error{errors.NewEmptyAssertError("could not validate json schema", err.Error())} } if !validateResult.Valid() && len(validateResult.Errors()) > 0 { diff --git a/roundtripper.go b/roundtripper.go index 2cee681..33edd7d 100644 --- a/roundtripper.go +++ b/roundtripper.go @@ -36,6 +36,14 @@ func (it *Test) makeRequest(t internalT, req *http.Request) (*http.Response, []e executeWithStep(t, createTitle(i, countRepeat, req), func(t T) []error { resp, err = it.doRequest(t, req) if err != nil { + if it.Request.Repeat.Broken { + err = wrapBrokenError(err) + } + + if it.Request.Repeat.Optional { + err = wrapOptionalError(err) + } + return []error{err} } @@ -60,10 +68,13 @@ func (it *Test) doRequest(t T, baseReq *http.Request) (*http.Response, error) { // copy request, because body can be read once req, err := copyRequest(baseReq.Context(), baseReq) if err != nil { - return nil, err + return nil, cuteErrors.NewCuteError("[Internal] Could not copy request", err) } resp, httpErr := it.httpClient.Do(req) + if resp == nil { + return nil, cuteErrors.NewCuteError("[HTTP] Response is nil", httpErr) + } // BAD CODE. Need to copy body, because we can't read body again from resp.Request.Body. Problem is io.Reader resp.Request.Body, baseReq.Body, err = utils.DrainBody(baseReq.Body) @@ -80,19 +91,17 @@ func (it *Test) doRequest(t T, baseReq *http.Request) (*http.Response, error) { } if httpErr != nil { - return nil, httpErr + return nil, cuteErrors.NewCuteError("[HTTP] Could not do request", httpErr) } - if resp != nil { - // Add information (code, body, headers) about response to Allure step - if addErr := it.addInformationResponse(t, resp); addErr != nil { - // Ignore err return, because it's connected with test logic - it.Error(t, "[ERROR] Could not log information about response. Error %v", addErr) - } + // Add information (code, body, headers) about response to Allure step + if addErr := it.addInformationResponse(t, resp); addErr != nil { + // Ignore err return, because it's connected with test logic + it.Error(t, "[ERROR] Could not log information about response. Error %v", addErr) + } - if validErr := it.validateResponseCode(resp); validErr != nil { - return nil, validErr - } + if validErr := it.validateResponseCode(resp); validErr != nil { + return nil, validErr } return resp, nil diff --git a/test.go b/test.go index ac38fa1..975329a 100644 --- a/test.go +++ b/test.go @@ -51,10 +51,15 @@ type Request struct { } // RequestRepeatPolitic is struct for repeat politic +// if Optional is true and request is failed, than test step allure will be skip, and t.Fail() will not execute. +// If Broken is true and request is failed, than test step allure will be broken, and t.Fail() will not execute. +// If Optional and Broken is false, than test step will be failed, and t.Fail() will execute. // If response.Code != Expect.Code, than request will repeat Count counts with Delay delay. type RequestRepeatPolitic struct { - Count int - Delay time.Duration + Count int + Delay time.Duration + Optional bool + Broken bool } // Middleware is struct for executeInsideAllure something before or after test @@ -206,10 +211,9 @@ func (it *Test) executeInsideAllure(ctx context.Context, allureProvider allurePr } // processTestErrors returns flag, which mean finish test or not. -// If test has not optional errors, than test will be failed. -// If test has broken errors, than test will be broken. -// If test has require errors, than test will be failed. -// If test has success, than test will be success. +// If test has only optional errors, than test will be success +// If test has broken errors, than test will be broken on allure and executed t.FailNow(). +// If test has require errors, than test will be failed on allure and executed t.FailNow(). func (it *Test) processTestErrors(t internalT, errs []error) ResultState { if len(errs) == 0 { return ResultStateSuccess @@ -217,7 +221,7 @@ func (it *Test) processTestErrors(t internalT, errs []error) ResultState { var ( countNotOptionalErrors = 0 - state = ResultStateFail + state ResultState ) for _, err := range errs { @@ -227,6 +231,8 @@ func (it *Test) processTestErrors(t internalT, errs []error) ResultState { if tErr.IsOptional() { it.Info(t, "[OPTIONAL ERROR] %v", err.Error()) + state = ResultStateSuccess + continue } } @@ -262,6 +268,8 @@ func (it *Test) processTestErrors(t internalT, errs []error) ResultState { } if countNotOptionalErrors != 0 { + state = ResultStateFail + it.Error(t, "Test finished with %v errors", countNotOptionalErrors) }