Skip to content

Commit

Permalink
[ISSUE-70] Possibility to not fail the test during retries
Browse files Browse the repository at this point in the history
  • Loading branch information
siller174 committed Apr 19, 2024
1 parent 9389b60 commit d8ccf40
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 30 deletions.
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 option, 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
}

// RequestRepeatOption set option politic for request repeat.
// if Optional is true and request is failed, than test step allure will be option, 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
21 changes: 15 additions & 6 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
// 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 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
24 changes: 21 additions & 3 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,29 @@ type RequestHTTPBuilder interface {

// RequestParams is a scope of methods for configurate http 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 option, 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 option, 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
38 changes: 25 additions & 13 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,31 +91,32 @@ 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
}

func (it *Test) validateResponseCode(resp *http.Response) error {
func (it *Test) validateResponseCode(resp *http.Response) cuteErrors.AssertError {
if it.Expect.Code != 0 && it.Expect.Code != resp.StatusCode {
return cuteErrors.NewAssertError(
err := cuteErrors.NewAssertError(
"Assert response code",
fmt.Sprintf("Response code expect %v, but was %v", it.Expect.Code, resp.StatusCode),
resp.StatusCode,
it.Expect.Code)

// it's safe to cast to AssertError, because we create it in this function
return err.(cuteErrors.AssertError)
}

return 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 option, 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 d8ccf40

Please sign in to comment.