Skip to content

Commit

Permalink
[ISSUE-70] Possibility to not fail the test during retries (#72)
Browse files Browse the repository at this point in the history
* [ISSUE-70] Possibility to not fail the test during retries

* [ISSUE-70] Possibility to not fail the test during retries

* [ISSUE-70] Possibility to not fail the test during retries

* [ISSUE-70] Possibility to not fail the test during retries
  • Loading branch information
siller174 authored Apr 22, 2024
1 parent 9389b60 commit b0638c3
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 35 deletions.
10 changes: 10 additions & 0 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
36 changes: 36 additions & 0 deletions builder_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
33 changes: 23 additions & 10 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
}
Expand Down
36 changes: 36 additions & 0 deletions examples/single_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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").
Expand Down
30 changes: 24 additions & 6 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
31 changes: 20 additions & 11 deletions roundtripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}

Expand All @@ -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)
Expand All @@ -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
Expand Down
22 changes: 15 additions & 7 deletions test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -206,18 +211,17 @@ 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
}

var (
countNotOptionalErrors = 0
state = ResultStateFail
state ResultState
)

for _, err := range errs {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit b0638c3

Please sign in to comment.