Repository: ozontech/cute
Branch: master
Commit: 9f4583b9e8d9
Files: 72
Total size: 255.0 KB
Directory structure:
gitextract_fkbtdg57/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .golangci.yaml
├── LICENSE
├── Makefile
├── README.MD
├── allure.go
├── assert.go
├── assert_broken.go
├── assert_broken_test.go
├── assert_optional.go
├── assert_optional_test.go
├── assert_require.go
├── assert_require_test.go
├── assert_trace.go
├── asserts/
│ ├── headers/
│ │ ├── headers.go
│ │ └── headers_test.go
│ └── json/
│ ├── json.go
│ ├── json_test.go
│ └── util.go
├── builder.go
├── builder_allure.go
├── builder_asserts.go
├── builder_middleware.go
├── builder_option.go
├── builder_request.go
├── builder_retry.go
├── builder_table.go
├── builder_table_test.go
├── builder_test.go
├── cute.go
├── errors/
│ ├── broken.go
│ ├── error.go
│ ├── optional.go
│ ├── require.go
│ └── trace.go
├── examples/
│ ├── custom_asserts.go
│ ├── inside_step_test.go
│ ├── masked_data_test.go
│ ├── parallel_test.go
│ ├── single_test.go
│ ├── suite/
│ │ ├── common.go
│ │ ├── main_test.go
│ │ ├── one_step.go
│ │ ├── one_step_errors.go
│ │ ├── resources/
│ │ │ └── example_valid_request.json
│ │ ├── simple.go
│ │ └── two_steps.go
│ ├── table_test/
│ │ └── table_test.go
│ ├── two_step_test.go
│ └── upload_file_test.go
├── go.mod
├── go.sum
├── init.go
├── interface.go
├── internal/
│ └── utils/
│ ├── body.go
│ └── json.go
├── json_marshaler.go
├── jsonschema.go
├── jsonschema_test.go
├── logger.go
├── provider.go
├── request.go
├── request_test.go
├── result.go
├── result_test.go
├── roundtripper.go
├── step.go
├── test.go
└── test_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report \
about: Create a report to help us improve \
title: ' ' \
labels: ' ' \
assignees: ' '
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Used func: '...'
2. What you actually wanted to do: '...'
3. What has been expected: '...'
4. What you got actual: '....'
5. Error Log (if any): '...'
6. Allure-Report screenshot (if any): '...'
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request \
about: Suggest an idea for this project \
title: '' \
labels: '' \
assignees: ''
---
**Is your feature request related to a problem? Please describe.** \
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like** \
A clear and concise description of what you want to happen.
**Describe alternatives you've considered** \
A clear and concise description of any alternative solutions or features you've considered.
**Additional context** \
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/main.yml
================================================
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.21
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run golangci-lint
# You may pin to the exact commit or the version.
# uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018
uses: golangci/golangci-lint-action@v2.5.2
# for settings see https://github.com/golangci/golangci-lint-action
with:
only-new-issues: true
# golangci-lint command line arguments
args: --timeout=5m0s
examples:
name: examples
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run examples
run: make examples
- name: Archive code coverage results
uses: actions/upload-artifact@v4
with:
name: allure-results
path: ./examples/allure-results
================================================
FILE: .gitignore
================================================
.idea/
testdata/
allure-results/
bin/
vendor/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
================================================
FILE: .golangci.yaml
================================================
run:
timeout: 10m
issues-exit-code: 1
tests: true
skip-dirs:
- bin
- vendor
- var
- tmp
skip-files:
- \.pb\.go$
- \.pb\.goclay\.go$
output:
format: colored-line-number
print-issued-lines: true
print-linter-name: true
linters-settings:
govet:
check-shadowing: true
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 2
linters:
disable-all: true
enable:
- errcheck
- goconst
- goimports
- gosec
- govet
- ineffassign
- megacheck
- revive
- typecheck
- unused # will be used insted of varcheck + deadcode + structcheck. More info https://github.com/golangci/golangci-lint/issues/1841
- prealloc
- wsl
issues:
exclude-use-default: false
exclude:
# _ instead of err checks
- G104
# for "public interface + private struct implementation" cases only!
- exported func .* returns unexported type .*, which can be annoying to use
# can be removed in the development phase
# - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)
# not for the active development - can be removed in the stable phase
- should have a package comment
- don't use an underscore in package name
# EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked
- should check returned error before deferring
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
export GO111MODULE=on
export GOSUMDB=off
LOCAL_BIN:=$(CURDIR)/bin
##################### GOLANG-CI RELATED CHECKS #####################
# Check global GOLANGCI-LINT
GOLANGCI_BIN:=$(LOCAL_BIN)/golangci-lint
GOLANGCI_TAG:=1.54.2
# Check local bin version
ifneq ($(wildcard $(GOLANGCI_BIN)),)
GOLANGCI_BIN_VERSION:=$(shell $(GOLANGCI_BIN) --version)
ifneq ($(GOLANGCI_BIN_VERSION),)
GOLANGCI_BIN_VERSION_SHORT:=$(shell echo "$(GOLANGCI_BIN_VERSION)" | sed -E 's/.* version (.*) built from .* on .*/\1/g')
else
GOLANGCI_BIN_VERSION_SHORT:=0
endif
ifneq "$(GOLANGCI_TAG)" "$(word 1, $(sort $(GOLANGCI_TAG) $(GOLANGCI_BIN_VERSION_SHORT)))"
GOLANGCI_BIN:=
endif
endif
# Check global bin version
ifneq (, $(shell which golangci-lint))
GOLANGCI_VERSION:=$(shell golangci-lint --version 2> /dev/null )
ifneq ($(GOLANGCI_VERSION),)
GOLANGCI_VERSION_SHORT:=$(shell echo "$(GOLANGCI_VERSION)"|sed -E 's/.* version (.*) built from .* on .*/\1/g')
else
GOLANGCI_VERSION_SHORT:=0
endif
ifeq "$(GOLANGCI_TAG)" "$(word 1, $(sort $(GOLANGCI_TAG) $(GOLANGCI_VERSION_SHORT)))"
GOLANGCI_BIN:=$(shell which golangci-lint)
endif
endif
##################### GOLANG-CI RELATED CHECKS #####################
.PHONY: install
install:
go mod tidy && go mod download
# run full lint like in pipeline
.PHONY: lint
lint: install-lint
$(GOLANGCI_BIN) run --config=.golangci.yaml ./... --new-from-rev=origin/master --build-tags=examples,allure_go,provider
.PHONY: install-lint
install-lint:
ifeq ($(wildcard $(GOLANGCI_BIN)),)
$(info #Downloading golangci-lint v$(GOLANGCI_TAG))
tmp=$$(mktemp -d) && cd $$tmp && pwd && go mod init temp && go get -d github.com/golangci/golangci-lint/cmd/golangci-lint@v$(GOLANGCI_TAG) && \
go build -ldflags "-X 'main.version=$(GOLANGCI_TAG)' -X 'main.commit=test' -X 'main.date=test'" -o $(LOCAL_BIN)/golangci-lint github.com/golangci/golangci-lint/cmd/golangci-lint && \
rm -rf $$tmp
GOLANGCI_BIN:=$(LOCAL_BIN)/golangci-lint
endif
.PHONY: cover
cover:
go test -v -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
.PHONY: example
example:
go test ./... -tags example
.PHONY: test
test:
go test ./...
================================================
FILE: README.MD
================================================
# CUTE — create your tests easily
HTTP and REST API testing for Go using Allure reports.
## Features
- Expressive and intuitive syntax.
- Built-in JSON support.
- Custom asserts.
- One step to BDD.
- Allure reports.
---
## Head of contents
- [Features](#features)
- [Workflow](#workflow)
- [Installation](#installation)
- [Requirements](#requirements)
- [Demo](#demo)
- [Test examples](#test-examples)
- [Single test](#single-step-test)
- [Multi-step test](#multi-step-test)
- [Suite tests](#suite)
- [Table tests](#table-tests)
- [Asserts](#asserts)
- [Ready-made asserts](#ready-made-asserts)
- [JSON asserts](#json-asserts)
- [Headers asserts](#headers-asserts)
- [JSON schema](#json-schema-validations)
- [Custom asserts](#custom-asserts)
- [Base](#base)
- [T](#t)
- [Errors](#assert-errors)
- [Global Environment Keys](#global-environment-keys)
## Workflow
1. Create a request and write assets.
2. Run tests.
3. Check Allure reports.
## Installation
```bash
go get -u github.com/ozontech/cute
```
## Requirements
- Go 1.17+
## Demo
Run example.
```bash
make example
```
To view detailed test reports, install Allure framework. It's optional.
[Learn more about Allure reports](https://github.com/allure-framework)
```bash
brew install allure
```
Run Allure.
```bash
allure serve ./examples/allure-results
```
## Test examples
See [**Examples**](examples) directory for featured examples.
###
Allows implementing single-request tests. See full example in the [**Examples**](examples) directory.
To view an Allure report, use `testing.T` or `provider.T` from [allure-go](https://github.com/ozontech/allure-go/tree/master?tab=readme-ov-file).
```go
import (
"context"
"net/http"
"path"
"testing"
"time"
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/json"
)
func TestExample(t *testing.T) {
cute.NewTestBuilder().
Title("Title").
Description("some_description").
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusOK).
AssertBody(
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
).
ExecuteTest(context.Background(), t)
}
```
Allure report

###
Allows implementing several requests within one test.
```go
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/ozontech/cute"
)
func Test_TwoSteps(t *testing.T) {
responseCode := 0
// First step
cute.NewTestBuilder().
Title("Test with two requests and parse body.").
Tag("two_steps").
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectStatus(http.StatusOK).
NextTest().
// Execute after first step and parse response code
AfterTestExecute(func(response *http.Response, errors []error) error {
responseCode = response.StatusCode
return nil
}).
// Second step
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/2/comments"),
cute.WithMethod(http.MethodDelete),
).
ExecuteTest(context.Background(), t)
fmt.Println("Response code from first request", responseCode)
}
```
See full in the [**Examples**](examples/two_step_test.go) directory.
Allure report

###
Suite provides a structure for describing tests by organizing them into test suites. It's helpful if you have a large number of different tests and find it difficult to browse through them without using additional layer nesting levels of test calls.
[Learn more about suite with Allure reports](https://github.com/ozontech/allure-go#suite)
1. Declare a structure with `suite.Suite` and `*cute.HTTPTestMaker`.
```go
import (
"github.com/ozontech/cute"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/suite"
)
type ExampleSuite struct {
suite.Suite
host *url.URL
testMaker *cute.HTTPTestMaker
}
func (i *ExampleSuite) BeforeAll(t provider.T) {
// Prepare http test builder
i.testMaker = cute.NewHTTPTestMaker()
// Preparing host
host, err := url.Parse("https://jsonplaceholder.typicode.com/")
if err != nil {
t.Fatalf("could not parse url, error %w", err)
}
i.host = host
}
```
2. Declare a test.
```go
import (
"github.com/ozontech/allure-go/pkg/framework/suite"
)
func TestExampleTest(t *testing.T) {
suite.RunSuite(t, new(ExampleSuite))
}
```
3. Describe tests.
```go
import (
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/headers"
"github.com/ozontech/cute/asserts/json"
)
func (i *ExampleSuite) TestExample_OneStep(t provider.T) {
var (
testBuilder = i.testMaker.NewTestBuilder()
)
u, _ := url.Parse(i.host.String())
u.Path = path.Join(u.Path, "/posts/1/comments")
testBuilder.
Title("TestExample_OneStep").
Tags("one_step", "some_local_tag", "json").
Create().
StepName("Example GET json request").
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
"some_array_header": []string{"1", "2", "3", "some_thing"},
}),
cute.WithURL(u),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectJSONSchemaFile("file://./resources/example_valid_request.json").
ExpectStatus(http.StatusOK).
AssertBody(
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
json.NotPresent("$[1].some_not_present"),
json.GreaterThan("$", 3),
json.Length("$", 5),
json.LessThan("$", 100),
json.NotEqual("$[3].name", "kekekekeke"),
).
OptionalAssertBody(
json.GreaterThan("$", 3),
json.Length("$", 5),
json.LessThan("$", 100),
).
AssertHeaders(
headers.Present("Content-Type"),
).
ExecuteTest(context.Background(), t)
}
```
See full example in the [**Examples**](examples/suite) directory.
Allure report

##
You can create a table test in 2 ways. They'll have the same Allure reports.
### Builder table tests
```go
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/ozontech/cute"
)
func Test_Table_Array(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodPost),
},
},
Expect: &cute.Expect{
Code: 200,
},
},
{
Name: "test_2",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
func(body []byte) error {
return errors.NewAssertError("example error", "example message", nil, nil)
},
},
},
},
}
cute.NewTestBuilder().
Title("Example table test").
Tag("table_test").
Description("Execute array tests").
CreateTableTest().
PutTests(tests...).
ExecuteTest(context.Background(), t)
}
```
### Array tests
```go
func Test_Execute_Array(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodPost),
},
},
Expect: &cute.Expect{
Code: 200,
},
},
{
Name: "test_2",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
func(body []byte) error {
return errors.NewAssertError("example error", "example message", nil, nil)
},
},
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
```
See full example in the [**Examples**](examples/table_test/table_test.go) directory.
Allure report
Common report for all table tests:

Main report:

You can create your own asserts or use ready-made from the package asserts.
### Ready-made asserts
####
- `Equal` is a function to assert that a JSONPath expression matches the given value.
- `NotEqual` is a function to check that a JSONPath expression value isn't equal to the given value.
- `Length` is a function to assert that value is the expected length.
- `GreaterThan` is a function to assert that value is greater than the given length.
- `GreaterOrEqualThan` is a function to assert that value is greater or equal to the given length.
- `LessThan` is a function to assert that value is less than the given length.
- `LessOrEqualThan` is a function to assert that value is less or equal to the given length.
- `Present` is a function to assert that value is present. Value can be 0 or null.
- `NotEmpty` is a function to assert that value is present and not empty. Value can't be 0 or null.
- `NotPresent` is a function to assert that value isn't present.
- `Diff` is a function to compare two JSONs.
- `Contains` is a function to assert that a JSONPath expression extracts a value in an array.
- `EqualJSON` is a function to check that a JSON path expression value is equal to given JSON.
- `NotEqualJSON` is a function to check that a JSONPath expression value isn't equal to given JSON.
- `GetValueFromJSON` is a function for getting a value from a JSON.
[Learn more about expressions](https://goessner.net/articles/JsonPath/)
[Learn more about asserts implementation](https://github.com/ozontech/cute/blob/master/asserts/json/json.go)
####
- `Present` is a function to assert that header is present.
- `NotPresent` is a function to assert that header isn't present.
[Learn more about asserts implementation](asserts/headers/headers.go)
####
You can validate a JSON schema in 3 ways. Choose a way depending on JSON schema location.
- `ExpectJSONSchemaString(string)` is a function for validating a JSON schema from a string.
- `ExpectJSONSchemaByte([]byte)` is a function for validating a JSON schema from an array of bytes.
- `ExpectJSONSchemaFile(string)` is a function for validating a JSON schema from a file or remote resource.
Allure report

###
You can implement [3 type of asserts](assert.go):
#### Base
Types for creating custom assertions.
```go
type AssertBody func(body []byte) error
type AssertHeaders func(headers http.Header) error
type AssertResponse func(response *http.Response) error
```
Example:
```go
func customAssertBody() cute.AssertBody {
return func(bytes []byte) error {
if len(bytes) == 0 {
return errors.New("response body is empty")
}
return nil
}
}
```
#### T
Used for creating custom asserts via [Allure Actions](https://github.com/ozontech/allure-go#suite) and [testing.TB](https://pkg.go.dev/testing#TB).
You can:
- log information to Allure,
- log error on Allure yourself,
- return an error.
```go
type AssertBodyT func(t cute.T, body []byte) error
type AssertHeadersT func(t cute.T, headers http.Header) error
type AssertResponseT func(t cute.T, response *http.Response) error
```
Example with T:
```go
func customAssertBodyT() cute.AssertBodyT {
return func(t cute.T, bytes []byte) error {
require.GreaterOrEqual(t, len(bytes), 100)
return nil
}
}
```
Example with creating steps:
```go
func customAssertBodySuite() cute.AssertBodyT {
return func(t cute.T, bytes []byte) error {
step := allure.NewSimpleStep("Custom assert step")
defer func() {
t.Step(step)
}()
if len(bytes) == 0 {
step.Status = allure.Failed
step.Attachment(allure.NewAttachment("Error", allure.Text, []byte("response body is empty")))
return nil
}
return nil
}
}
```
Allure report

####
You can use `errors.NewAssertError` method from [errors](errors/error.go) package.
Example:
```go
import (
"github.com/ozontech/cute"
"github.com/ozontech/cute/errors"
)
func customAssertBodyWithCustomError() cute.AssertBody {
return func(bytes []byte) error {
if len(bytes) == 0 {
return errors.NewAssertError("customAssertBodyWithCustomError", "body must be not empty", "len is 0", "len more 0")
}
return nil
}
}
```
To create a pretty-error in your custom assert, implement it with [interfaces](errors/error.go):
- Name.
```go
type WithNameError interface {
GetName() string
SetName(string)
}
```
- Parameters for Allure step.
```go
type WithFields interface {
GetFields() map[string]interface{}
PutFields(map[string]interface{})
}
```
Allure report

#### Optional assert
If assert returns an optional error, step fails but the test is successful.
You can use `errors.NewOptionalError(error)` method from [errors](errors/error.go) package.
```go
import (
"github.com/ozontech/cute"
"github.com/ozontech/cute/errors"
)
func customAssertBodyWithCustomError() cute.AssertBody {
return func(bytes []byte) error {
if len(bytes) == 0 {
return errors.NewOptionalError("body is empty")
}
return nil
}
}
```
To create optional error, implement error with interface:
```go
type OptionalError interface {
IsOptional() bool
SetOptional(bool)
}
```
Allure report

##
| Key | Meaning | Default |
|---|---------------------------------------------------------------|-------------------------|
|`ALLURE_OUTPUT_PATH`| Path to output allure results. | `.` (Folder with tests) |
|`ALLURE_OUTPUT_FOLDER`| Name of result folder. | `/allure-results` |
|`ALLURE_ISSUE_PATTERN`| Url pattepn to issue. Must contain `%s`. | |
|`ALLURE_TESTCASE_PATTERN`| URL pattern to TestCase. Must contain `%s`. | |
|`ALLURE_LAUNCH_TAGS`| Default tags for all tests. Tags must be separated by commas. | |
================================================
FILE: allure.go
================================================
package cute
func (qt *cute) setAllureInformation(t allureProvider) {
// Log main vars to allureProvider
qt.setLabelsAllure(t)
qt.setInfoAllure(t)
qt.setLinksAllure(t)
}
func (qt *cute) setLinksAllure(t linksAllureProvider) {
if qt.allureLinks.issue != "" {
t.SetIssue(qt.allureLinks.issue)
}
if qt.allureLinks.testCase != "" {
t.SetTestCase(qt.allureLinks.testCase)
}
if qt.allureLinks.link != nil {
t.Link(qt.allureLinks.link)
}
if qt.allureLinks.tmsLink != "" {
t.TmsLink(qt.allureLinks.tmsLink)
}
if len(qt.allureLinks.tmsLinks) > 0 {
t.TmsLinks(qt.allureLinks.tmsLinks...)
}
}
func (qt *cute) setLabelsAllure(t labelsAllureProvider) {
if qt.allureLabels.id != "" {
t.ID(qt.allureLabels.id)
}
if qt.allureLabels.suiteLabel != "" {
t.AddSuiteLabel(qt.allureLabels.suiteLabel)
}
if qt.allureLabels.subSuite != "" {
t.AddSubSuite(qt.allureLabels.subSuite)
}
if qt.allureLabels.parentSuite != "" {
t.AddParentSuite(qt.allureLabels.parentSuite)
}
if qt.allureLabels.story != "" {
t.Story(qt.allureLabels.story)
}
if qt.allureLabels.tag != "" {
t.Tag(qt.allureLabels.tag)
}
if qt.allureLabels.allureID != "" {
t.AllureID(qt.allureLabels.allureID)
}
if qt.allureLabels.severity != "" {
t.Severity(qt.allureLabels.severity)
}
if qt.allureLabels.owner != "" {
t.Owner(qt.allureLabels.owner)
}
if qt.allureLabels.lead != "" {
t.Lead(qt.allureLabels.lead)
}
if qt.allureLabels.label != nil {
t.Label(qt.allureLabels.label)
}
if len(qt.allureLabels.labels) != 0 {
t.Labels(qt.allureLabels.labels...)
}
if qt.allureLabels.feature != "" {
t.Feature(qt.allureLabels.feature)
}
if qt.allureLabels.epic != "" {
t.Epic(qt.allureLabels.epic)
}
if len(qt.allureLabels.tags) != 0 {
t.Tags(qt.allureLabels.tags...)
}
if qt.allureLabels.layer != "" {
t.Layer(qt.allureLabels.layer)
}
}
func (qt *cute) setInfoAllure(t infoAllureProvider) {
if qt.allureInfo.title != "" {
t.Title(qt.allureInfo.title)
}
if qt.allureInfo.description != "" {
t.Description(qt.allureInfo.description)
}
if qt.allureInfo.stage != "" {
t.Stage(qt.allureInfo.stage)
}
}
================================================
FILE: assert.go
================================================
package cute
import (
"net/http"
)
// This is type of asserts, for create some assert with using custom logic.
// AssertBody is type for create custom assertions for body
// Example asserts:
// - json.LengthGreaterThan
// - json.LengthGreaterOrEqualThan
// - json.LengthLessThan
// - json.LengthLessOrEqualThan
// - json.Present
// - json.NotEmpty
// - json.NotPresent
type AssertBody func(body []byte) error
// AssertHeaders is type for create custom assertions for headers
// Example asserts:
// - headers.Present
// - headers.NotPresent
type AssertHeaders func(headers http.Header) error
// AssertResponse is type for create custom assertions for response
type AssertResponse func(response *http.Response) error
// This is type for create custom assertions with using allure and testing.allureProvider
// AssertBodyT is type for create custom assertions for body with TB
// Check example in AssertBody
// TB is testing.T and it can be used for require ore assert from testify or another packages
type AssertBodyT func(t T, body []byte) error
// AssertHeadersT is type for create custom assertions for headers with TB
// Check example in AssertHeaders
// TB is testing.T and it can be used for require ore assert from testify or another packages
type AssertHeadersT func(t T, headers http.Header) error
// AssertResponseT is type for create custom assertions for response with TB
// Check example in AssertResponse
// TB is testing.T and it can be used for require ore assert from testify or another packages
type AssertResponseT func(t T, response *http.Response) error
func (it *Test) assertHeaders(t internalT, headers http.Header) []error {
var (
asserts = it.Expect.AssertHeaders
assertT = it.Expect.AssertHeadersT
)
if len(asserts) == 0 && len(assertT) == 0 {
return nil
}
return it.executeWithStep(t, "Assert headers", func(t T) []error {
errs := make([]error, 0)
// Execute assert only response
for _, f := range asserts {
err := f(headers)
if err != nil {
errs = append(errs, err)
}
}
// Execute assert for response with TB
for _, f := range assertT {
err := f(t, headers)
if err != nil {
errs = append(errs, err)
}
}
return errs
})
}
func (it *Test) assertResponse(t internalT, resp *http.Response) []error {
var (
asserts = it.Expect.AssertResponse
assertT = it.Expect.AssertResponseT
)
if len(asserts) == 0 && len(assertT) == 0 {
return nil
}
return it.executeWithStep(t, "Assert response", func(t T) []error {
errs := make([]error, 0)
// Execute assert only response
for _, f := range asserts {
err := f(resp)
if err != nil {
errs = append(errs, err)
}
}
// Execute assert for response with TB
for _, f := range assertT {
err := f(t, resp)
if err != nil {
errs = append(errs, err)
}
}
return errs
})
}
func (it *Test) assertBody(t internalT, body []byte) []error {
var (
asserts = it.Expect.AssertBody
assertT = it.Expect.AssertBodyT
)
if len(asserts) == 0 && len(assertT) == 0 {
return nil
}
return it.executeWithStep(t, "Assert body", func(t T) []error {
errs := make([]error, 0)
// Execute assert only response
for _, f := range asserts {
err := f(body)
if err != nil {
errs = append(errs, err)
}
}
// Execute assert for response with TB
for _, f := range assertT {
err := f(t, body)
if err != nil {
errs = append(errs, err)
}
}
return errs
})
}
================================================
FILE: assert_broken.go
================================================
package cute
import (
"net/http"
"github.com/ozontech/cute/errors"
)
func brokenAssertHeaders(assert AssertHeaders) AssertHeaders {
return func(headers http.Header) error {
err := assert(headers)
return wrapBrokenError(err)
}
}
func brokenAssertBody(assert AssertBody) AssertBody {
return func(body []byte) error {
err := assert(body)
return wrapBrokenError(err)
}
}
func brokenAssertResponse(assert AssertResponse) AssertResponse {
return func(resp *http.Response) error {
err := assert(resp)
return wrapBrokenError(err)
}
}
func brokenAssertHeadersT(assert AssertHeadersT) AssertHeadersT {
return func(t T, headers http.Header) error {
err := assert(t, headers)
return wrapBrokenError(err)
}
}
func brokenAssertBodyT(assert AssertBodyT) AssertBodyT {
return func(t T, body []byte) error {
err := assert(t, body)
return wrapBrokenError(err)
}
}
func brokenAssertResponseT(assert AssertResponseT) AssertResponseT {
return func(t T, resp *http.Response) error {
err := assert(t, resp)
return wrapBrokenError(err)
}
}
func wrapBrokenError(err error) error {
if err == nil {
return nil
}
if tErr, ok := err.(errors.BrokenError); ok {
tErr.SetBroken(true)
return tErr.(error)
}
return errors.WrapBrokenError(err)
}
================================================
FILE: assert_broken_test.go
================================================
package cute
import (
"errors"
"net/http"
"testing"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBrokenAssertResponse(t *testing.T) {
v := &http.Response{}
f := func(_ *http.Response) error {
return errors.New("test error")
}
err := brokenAssertResponse(f)(v)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestBrokenAssertResponseT(t *testing.T) {
v := &http.Response{}
f := func(T, *http.Response) error {
return errors.New("test error")
}
err := brokenAssertResponseT(f)(nil, v)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestBrokenAssertHeaders(t *testing.T) {
h := http.Header{}
f := func(_ http.Header) error {
return errors.New("test error")
}
err := brokenAssertHeaders(f)(h)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestBrokenAssertHeadersT(t *testing.T) {
h := http.Header{}
f := func(T, http.Header) error {
return errors.New("test error")
}
err := brokenAssertHeadersT(f)(nil, h)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestBrokenAssertBody(t *testing.T) {
v := []byte{}
f := func(_ []byte) error {
return errors.New("test error")
}
err := brokenAssertBody(f)(v)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestBrokenAssertBodyT(t *testing.T) {
v := []byte{}
f := func(T, []byte) error {
return errors.New("test error")
}
err := brokenAssertBodyT(f)(nil, v)
if BrokenError, ok := err.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
func TestWrapBrokenError(t *testing.T) {
err := errors.New("test error")
optError := wrapBrokenError(err)
if BrokenError, ok := optError.(cuteErrors.BrokenError); assert.True(t, ok) {
require.True(t, BrokenError.IsBroken())
}
}
================================================
FILE: assert_optional.go
================================================
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.WrapOptionalError(err)
}
================================================
FILE: assert_optional_test.go
================================================
package cute
import (
"errors"
"net/http"
"testing"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestOptionalAssertResponse(t *testing.T) {
v := &http.Response{}
f := func(*http.Response) error {
return errors.New("test error")
}
err := optionalAssertResponse(f)(v)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestOptionalAssertResponseT(t *testing.T) {
v := &http.Response{}
f := func(T, *http.Response) error {
return errors.New("test error")
}
err := optionalAssertResponseT(f)(nil, v)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestOptionalAssertHeaders(t *testing.T) {
h := http.Header{}
f := func(http.Header) error {
return errors.New("test error")
}
err := optionalAssertHeaders(f)(h)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestOptionalAssertHeadersT(t *testing.T) {
h := http.Header{}
f := func(T, http.Header) error {
return errors.New("test error")
}
err := optionalAssertHeadersT(f)(nil, h)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestOptionalAssertBody(t *testing.T) {
v := []byte{}
f := func([]byte) error {
return errors.New("test error")
}
err := optionalAssertBody(f)(v)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestOptionalAssertBodyT(t *testing.T) {
v := []byte{}
f := func(T, []byte) error {
return errors.New("test error")
}
err := optionalAssertBodyT(f)(nil, v)
if optionalError, ok := err.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
func TestWrapOptionalError(t *testing.T) {
err := errors.New("test error")
optError := wrapOptionalError(err)
if optionalError, ok := optError.(cuteErrors.OptionalError); assert.True(t, ok) {
require.True(t, optionalError.IsOptional())
}
}
================================================
FILE: assert_require.go
================================================
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.WrapRequireError(err)
}
================================================
FILE: assert_require_test.go
================================================
package cute
import (
"errors"
"net/http"
"testing"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRequireAssertResponse(t *testing.T) {
v := &http.Response{}
f := func(_ *http.Response) error {
return errors.New("test error")
}
err := requireAssertResponse(f)(v)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestRequireAssertResponseT(t *testing.T) {
v := &http.Response{}
f := func(T, *http.Response) error {
return errors.New("test error")
}
err := requireAssertResponseT(f)(nil, v)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestRequireAssertHeaders(t *testing.T) {
h := http.Header{}
f := func(http.Header) error {
return errors.New("test error")
}
err := requireAssertHeaders(f)(h)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestRequireAssertHeadersT(t *testing.T) {
h := http.Header{}
f := func(T, http.Header) error {
return errors.New("test error")
}
err := requireAssertHeadersT(f)(nil, h)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestRequireAssertBody(t *testing.T) {
v := []byte{}
f := func([]byte) error {
return errors.New("test error")
}
err := requireAssertBody(f)(v)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestRequireAssertBodyT(t *testing.T) {
v := []byte{}
f := func(T, []byte) error {
return errors.New("test error")
}
err := requireAssertBodyT(f)(nil, v)
if RequireError, ok := err.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
func TestWrapRequireError(t *testing.T) {
err := errors.New("test error")
optError := wrapRequireError(err)
if RequireError, ok := optError.(cuteErrors.RequireError); assert.True(t, ok) {
require.True(t, RequireError.IsRequire())
}
}
================================================
FILE: assert_trace.go
================================================
package cute
import (
"fmt"
"net/http"
"runtime"
"github.com/ozontech/cute/errors"
)
// assertHeadersWithTrace is a function to add trace inside assert headers error
func assertHeadersWithTrace(assert AssertHeaders, trace string) AssertHeaders {
return func(headers http.Header) error {
err := assert(headers)
return wrapWithTrace(err, trace)
}
}
// assertBodyWithTrace is a function to add trace inside assert body error
func assertBodyWithTrace(assert AssertBody, trace string) AssertBody {
return func(body []byte) error {
err := assert(body)
return wrapWithTrace(err, trace)
}
}
// assertResponseWithTrace is a function to add trace inside assert response error
func assertResponseWithTrace(assert AssertResponse, trace string) AssertResponse {
return func(resp *http.Response) error {
err := assert(resp)
return wrapWithTrace(err, trace)
}
}
// assertHeadersTWithTrace is a function to add trace inside assert headers error
func assertHeadersTWithTrace(assert AssertHeadersT, trace string) AssertHeadersT {
return func(t T, headers http.Header) error {
err := assert(t, headers)
return wrapWithTrace(err, trace)
}
}
// assertBodyTWithTrace is a function to add trace inside assert body error
func assertBodyTWithTrace(assert AssertBodyT, trace string) AssertBodyT {
return func(t T, body []byte) error {
err := assert(t, body)
return wrapWithTrace(err, trace)
}
}
// assertResponseTWithTrace is a function to add trace inside assert response error
func assertResponseTWithTrace(assert AssertResponseT, trace string) AssertResponseT {
return func(t T, resp *http.Response) error {
err := assert(t, resp)
return wrapWithTrace(err, trace)
}
}
// wrapWithTrace is a function to add trace inside error
func wrapWithTrace(err error, trace string) error {
if err == nil {
return nil
}
if tErr, ok := err.(errors.WithTrace); ok {
tErr.SetTrace(trace)
return tErr.(error)
}
return errors.WrapErrorWithTrace(err, trace)
}
func getTrace() string {
pcs := make([]uintptr, 10)
depth := runtime.Callers(3, pcs)
if depth == 0 {
fmt.Println("Couldn't get the stack information")
return ""
}
callers := runtime.CallersFrames(pcs[:depth])
caller, _ := callers.Next()
return fmt.Sprintf("%s:%d", caller.File, caller.Line)
}
================================================
FILE: asserts/headers/headers.go
================================================
package headers
import (
"fmt"
"net/http"
"github.com/ozontech/cute"
"github.com/ozontech/cute/errors"
)
// Present is a function to asserts that header is present
func Present(key string) cute.AssertHeaders {
return func(headers http.Header) error {
if v := headers.Get(key); v == "" {
return errors.NewAssertError("Present", fmt.Sprintf("header %s is not present", key), nil, nil)
}
return nil
}
}
// NotPresent is a function to asserts that header is not present
func NotPresent(key string) cute.AssertHeaders {
return func(headers http.Header) error {
if v := headers.Values(key); len(v) > 0 {
return errors.NewAssertError("NotPresent", fmt.Sprintf("header %s is present", key), nil, nil)
}
return nil
}
}
================================================
FILE: asserts/headers/headers_test.go
================================================
package headers
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
func TestPresent(t *testing.T) {
headers := http.Header{
"Content-Type": []string{"application/json"},
}
err := Present("Content-Type")(headers)
require.NoError(t, err)
}
func TestPresentError(t *testing.T) {
headers := http.Header{
"Content-Type": []string{},
}
err := Present("not-present")(headers)
require.Error(t, err)
}
func TestNotPresent(t *testing.T) {
headers := http.Header{
"Content-Type": []string{"", "application/json"},
}
err := NotPresent("Content-Type")(headers)
require.Error(t, err)
}
func TestNotPresentError(t *testing.T) {
headers := http.Header{
"Content-Type": []string{},
}
err := NotPresent("not-present")(headers)
require.NoError(t, err)
}
================================================
FILE: asserts/json/json.go
================================================
package json
import (
"fmt"
jd "github.com/josephburnett/jd/lib"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
"github.com/ozontech/cute"
cuteErrors "github.com/ozontech/cute/errors"
)
// Diff is a function to compare two jsons
func Diff(original string) cute.AssertBody {
return func(body []byte) error {
originalJSON, err := jd.ReadJsonString(original)
if err != nil {
return fmt.Errorf("could not parse original json in Diff error: '%s'", err)
}
bodyJSON, err := jd.ReadJsonString(string(body))
if err != nil {
return fmt.Errorf("could not parse body json in Diff error: '%s'", err)
}
diff := originalJSON.Diff(bodyJSON).Render()
if diff != "" {
cErr := cuteErrors.NewEmptyAssertError("JSON Diff", "JSON is not the same")
cErr.PutAttachment(&cuteErrors.Attachment{
Name: "JSON diff",
MimeType: "text/plain",
Content: []byte(diff),
})
return cErr
}
return nil
}
}
// Contains is a function to assert that a jsonpath expression extracts a value in an array
// About expression - https://goessner.net/articles/JsonPath/
func Contains(expression string, expect interface{}) cute.AssertBody {
return func(body []byte) error {
return contains(body, expression, expect)
}
}
// Equal is a function to assert that a jsonpath expression matches the given value
// About expression - https://goessner.net/articles/JsonPath/
func Equal(expression string, expect interface{}) cute.AssertBody {
return func(body []byte) error {
return equal(body, expression, expect)
}
}
// NotEqual is a function to check json path expression value is not equal to given value
// About expression - https://goessner.net/articles/JsonPath/
func NotEqual(expression string, expect interface{}) cute.AssertBody {
return func(body []byte) error {
return notEqual(body, expression, expect)
}
}
// EqualJSON is a function to check json path expression value is equal to given json
// About expression - https://goessner.net/articles/JsonPath/
func EqualJSON(expression string, expect []byte) cute.AssertBody {
return func(body []byte) error {
return equalJSON(body, expression, expect)
}
}
// NotEqualJSON is a function to check json path expression value is not equal to given json
// About expression - https://goessner.net/articles/JsonPath/
func NotEqualJSON(expression string, expect []byte) cute.AssertBody {
return func(body []byte) error {
return notEqualJSON(body, expression, expect)
}
}
// Length is a function to asserts that value is the expected length
// About expression - https://goessner.net/articles/JsonPath/
func Length(expression string, expectLength int) cute.AssertBody {
return func(body []byte) error {
return length(body, expression, expectLength)
}
}
// LengthGreaterThan is a function to asserts that value is greater than the given length
// About expression - https://goessner.net/articles/JsonPath/
func LengthGreaterThan(expression string, minimumLength int) cute.AssertBody {
return func(body []byte) error {
return greaterThan(body, expression, minimumLength)
}
}
// LengthGreaterOrEqualThan is a function to asserts that value is greater or equal than the given length
// About expression - https://goessner.net/articles/JsonPath/
func LengthGreaterOrEqualThan(expression string, minimumLength int) cute.AssertBody {
return func(body []byte) error {
return greaterOrEqualThan(body, expression, minimumLength)
}
}
// LengthLessThan is a function to asserts that value is less than the given length
// About expression - https://goessner.net/articles/JsonPath/
func LengthLessThan(expression string, maximumLength int) cute.AssertBody {
return func(body []byte) error {
return lessThan(body, expression, maximumLength)
}
}
// LengthLessOrEqualThan is a function to asserts that value is less or equal than the given length
// About expression - https://goessner.net/articles/JsonPath/
func LengthLessOrEqualThan(expression string, maximumLength int) cute.AssertBody {
return func(body []byte) error {
return lessOrEqualThan(body, expression, maximumLength)
}
}
// Present is a function to asserts that value is present
// value can be nil or 0
// About expression - https://goessner.net/articles/JsonPath/
func Present(expression string) cute.AssertBody {
return func(body []byte) error {
return present(body, expression)
}
}
// NotEmpty is a function to asserts that value is present
// value can't be nil or 0
// About expression - https://goessner.net/articles/JsonPath/
func NotEmpty(expression string) cute.AssertBody {
return func(body []byte) error {
return notEmpty(body, expression)
}
}
// NotPresent is a function to asserts that value is not present
// About expression - https://goessner.net/articles/JsonPath/
func NotPresent(expression string) cute.AssertBody {
return func(body []byte) error {
return notPresent(body, expression)
}
}
// GetValueFromJSON is function for get value from json
func GetValueFromJSON(js []byte, expression string) ([]interface{}, error) {
obj, err := oj.Parse(js)
if err != nil {
return nil, fmt.Errorf("could not parse json in GetValueFromJSON error: '%s'", err)
}
jsonPath, err := jp.ParseString(expression)
if err != nil {
return nil, fmt.Errorf("could not parse path in GetValueFromJSON error: '%s'", err)
}
res := jsonPath.Get(obj)
if len(res) == 0 {
return nil, fmt.Errorf("could not find element by path %v in JSON", expression)
}
return res, nil
}
================================================
FILE: asserts/json/json_test.go
================================================
package json
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type jsonTest struct {
caseName string
data string
expression string
expect interface{}
IsNilErr bool
}
func TestDiff(t *testing.T) {
testCases := []struct {
name string
originalJSON string
bodyJSON string
expectedError string
}{
{
name: "SameJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1", "key2": "value2"}`,
expectedError: "", // No error expected, JSONs are the same
},
{
name: "DifferentValueJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1", "key2": "value3"}`,
expectedError: "JSON is not the same",
},
{
name: "MissingKeyJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1"}`,
expectedError: "JSON is not the same",
},
{
name: "ExtraKeyJSON",
originalJSON: `{"key1": "value1"}`,
bodyJSON: `{"key1": "value1", "key2": "value2"}`,
expectedError: "JSON is not the same",
},
{
name: "EmptyJSON",
originalJSON: `{}`,
bodyJSON: `{}`,
expectedError: "", // No error expected, empty JSONs are the same
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Call the Diff function with the test input
err := Diff(testCase.originalJSON)([]byte(testCase.bodyJSON))
// Check if the error message matches the expected result
if testCase.expectedError == "" {
assert.NoError(t, err) // No error expected
} else {
assert.Error(t, err) // Error expected
assert.Contains(t, err.Error(), testCase.expectedError)
}
})
}
}
func TestNotPresent(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
},
{
caseName: "not present check",
data: `{"o":["a", "b", "c"]}`,
expression: "$.b",
IsNilErr: true,
},
{
caseName: "not present check ",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o[0]",
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o[0]['1']",
},
}
for _, test := range tests {
err := NotPresent(test.expression)([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestPresent(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
IsNilErr: true,
},
{
caseName: "not present check",
data: `{"o":["a", "b", "c"]}`,
expression: "$.b",
},
{
caseName: "correct present check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o[0]",
IsNilErr: true,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o[0]['1']",
IsNilErr: true,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
},
{
caseName: "empty integer",
data: `{"o":0}`,
expression: "$.o",
IsNilErr: true,
},
{
caseName: "empty object",
data: `{"o":null}`,
expression: "$.o",
IsNilErr: true,
},
{
caseName: "empty string",
data: `{"o":null, "b":""}`,
expression: "$.b",
IsNilErr: true,
},
}
for _, test := range tests {
err := Present(test.expression)([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestNotEmpty(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
IsNilErr: true,
},
{
caseName: "not present check",
data: `{"o":["a", "b", "c"]}`,
expression: "$.b",
},
{
caseName: "correct present check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o[0]",
IsNilErr: true,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o[0]['1']",
IsNilErr: true,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
},
{
caseName: "empty integer",
data: `{"o":0}`,
expression: "$.o",
},
{
caseName: "empty object",
data: `{"o":null}`,
expression: "$.o",
},
{
caseName: "empty string",
data: `{"o":null, "b":""}`,
expression: "$.b",
},
}
for _, test := range tests {
err := NotEmpty(test.expression)([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestLength(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
{
caseName: "not correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 4,
},
{
caseName: "correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 6,
IsNilErr: true,
},
{
caseName: "not correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 99,
},
{
caseName: "check not contain value",
data: `{"o":"123456"}`,
expression: "$.a",
expect: 1,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
}
for _, test := range tests {
err := Length(test.expression, test.expect.(int))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestLengthGreaterThan(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 2,
IsNilErr: true,
},
{
caseName: "not correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 4,
},
{
caseName: "not correct check array when equal",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 3,
},
{
caseName: "correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "not correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 99,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 1,
IsNilErr: true,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
expect: 0,
},
}
for _, test := range tests {
err := LengthGreaterThan(test.expression, test.expect.(int))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestLengthGreaterOrEqualThan(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 2,
IsNilErr: true,
},
{
caseName: "correct check array when equal",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
{
caseName: "not correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 4,
},
{
caseName: "correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "correct check string when equal",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 6,
IsNilErr: true,
},
{
caseName: "not correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 99,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 1,
IsNilErr: true,
},
{
caseName: "correct check map when equal",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
{
caseName: "not correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 5,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
expect: 0,
},
}
for _, test := range tests {
err := LengthGreaterOrEqualThan(test.expression, test.expect.(int))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestLengthLessThan(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "not correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 3,
},
{
caseName: "correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 7,
IsNilErr: true,
},
{
caseName: "not correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 6,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "not correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 3,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
expect: 0,
},
}
for _, test := range tests {
err := LengthLessThan(test.expression, test.expect.(int))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestLengthLessOrEqualThan(t *testing.T) {
tests := []jsonTest{
{
caseName: "correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "correct check array when equal",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
{
caseName: "not correct check array",
data: `{"o":["a", "b", "c"]}`,
expression: "$.o",
expect: 2,
},
{
caseName: "correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 7,
IsNilErr: true,
},
{
caseName: "correct check string when equal",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 6,
IsNilErr: true,
},
{
caseName: "not correct check string",
data: `{"o":"123456"}`,
expression: "$.o",
expect: 5,
},
{
caseName: "correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 4,
IsNilErr: true,
},
{
caseName: "correct check map when equal",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 3,
IsNilErr: true,
},
{
caseName: "not correct check map",
data: `{"o":[{"1":"a"}, {"2":"b"}, {"3":"c"}]}`,
expression: "$.o",
expect: 2,
},
{
caseName: "check not correct path",
data: `{"o":["a", "b", "c"]}`,
expression: "$.not_correct",
expect: 0,
},
}
for _, test := range tests {
err := LengthLessOrEqualThan(test.expression, test.expect.(int))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestEqual(t *testing.T) {
tests := []jsonTest{
{
caseName: "valid json",
data: `{"first": 777, "second": [{"key_1": "some_key", "value": "some_value"}]}`,
expression: "$.second[0].value",
expect: "some_value",
IsNilErr: true,
},
{
caseName: "not valid json",
data: "{not_valid_json}",
},
{
caseName: "3rd party key",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.l",
expect: nil,
},
{
caseName: "not array",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b[bs]",
},
{
caseName: "valid array",
data: `{"arr": ["one","two"]}`,
expression: "$.arr",
expect: []string{"one", "two"},
IsNilErr: true,
},
{
caseName: "check equal map",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b",
expect: map[string]interface{}{"bs": "sb"},
IsNilErr: true,
},
{
caseName: "check equal string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "as",
IsNilErr: true,
},
{
caseName: "check equal not correct string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: []byte("not_correct"),
},
{
caseName: "check 186135434",
data: `{"a":186135434, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: 186135434,
IsNilErr: true,
},
{
caseName: "check float",
data: `{"a":1.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: 1.0000001,
IsNilErr: true,
},
{
caseName: "check float 2",
data: `{"a":999.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: 999.0000001,
IsNilErr: true,
},
}
for _, test := range tests {
err := Equal(test.expression, test.expect)([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestNotEqual(t *testing.T) {
tests := []jsonTest{
{
caseName: "valid json",
data: `{"first": 777, "second": [{"key_1": "some_key", "value": "some_value"}]}`,
expression: "$.second[0].value",
expect: "some_value",
IsNilErr: false,
},
{
caseName: "not valid json",
data: "{not_valid_json}",
},
{
caseName: "3rd party key",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.l",
expect: nil,
},
{
caseName: "not array",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b[bs]",
expect: "sb",
},
{
caseName: "check equal map",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b",
expect: map[string]interface{}{"bs": "sb"},
IsNilErr: false,
},
{
caseName: "check equal string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "as",
IsNilErr: false,
},
}
for _, test := range tests {
err := NotEqual(test.expression, test.expect)([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestEqualJSON(t *testing.T) {
tests := []jsonTest{
{
caseName: "valid json",
data: `{"first": 777, "second": [{"key_1": "some_key", "value": "some_value"}]}`,
expression: "$.second[0].value",
expect: `"some_value"`,
IsNilErr: true,
},
{
caseName: "not valid json",
data: "{not_valid_json}",
},
{
caseName: "3rd party key",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.l",
},
{
caseName: "not array",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b[bs]",
},
{
caseName: "valid array",
data: `{"arr": ["one","two"]}`,
expression: "$.arr",
expect: `["one", "two"]`,
IsNilErr: true,
},
{
caseName: "check equal map",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b",
expect: `{"bs": "sb"}`,
IsNilErr: true,
},
{
caseName: "check equal string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: `"as"`,
IsNilErr: true,
},
{
caseName: "check equal not correct string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: `"not_correct"`,
},
{
caseName: "check deep equal",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$",
expect: `{ "b": {"bs": "sb"}, "a":"as" }`,
IsNilErr: true,
},
{
caseName: "check deep equal not correct",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$",
expect: `{ "b": {"sb": "bs"}, "a":"as" }`,
},
{
caseName: "check 186135434",
data: `{"a":186135434, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "186135434",
IsNilErr: true,
},
{
caseName: "check float",
data: `{"a":1.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "1.0000001",
IsNilErr: true,
},
{
caseName: "check float 2",
data: `{"a":999.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "999.0000001",
IsNilErr: true,
},
}
for _, test := range tests {
exp, _ := test.expect.(string)
err := EqualJSON(test.expression, []byte(exp))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestNotEqualJSON(t *testing.T) {
tests := []jsonTest{
{
caseName: "valid json",
data: `{"first": 777, "second": [{"key_1": "some_key", "value": "some_value"}]}`,
expression: "$.second[0].value",
expect: `"some_value"`,
},
{
caseName: "not valid json",
data: "{not_valid_json}",
},
{
caseName: "3rd party key",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.l",
},
{
caseName: "not array",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b[bs]",
},
{
caseName: "valid array",
data: `{"arr": ["one","two"]}`,
expression: "$.arr",
expect: `["one", "two"]`,
},
{
caseName: "check equal map",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.b",
expect: `{"bs": "sb"}`,
},
{
caseName: "check equal string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: `"as"`,
},
{
caseName: "check equal not correct string",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$.a",
expect: `"not_correct"`,
IsNilErr: true,
},
{
caseName: "check deep equal",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$",
expect: `{ "b": {"bs": "sb"}, "a":"as" }`,
},
{
caseName: "check deep equal not correct",
data: `{"a":"as", "b":{"bs":"sb"}}`,
expression: "$",
expect: `{ "b": {"sb": "bs"}, "a":"as" }`,
IsNilErr: true,
},
{
caseName: "check 186135434",
data: `{"a":186135434, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "186135434",
},
{
caseName: "check float",
data: `{"a":1.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "1.0000001",
},
{
caseName: "check float 2",
data: `{"a":999.0000001, "b":{"bs":"sb"}}`,
expression: "$.a",
expect: "999.0000001",
},
}
for _, test := range tests {
exp, _ := test.expect.(string)
err := NotEqualJSON(test.expression, []byte(exp))([]byte(test.data))
if test.IsNilErr {
require.NoError(t, err, "failed test %v", test.caseName)
} else {
require.Error(t, err, "failed test %v", test.caseName)
}
}
}
func TestGetValueFromJSON(t *testing.T) {
testCases := []struct {
name string
inputJSON string
expression string
expectedValue []interface{}
expectedError string
}{
{
name: "ValidExpressionObject",
inputJSON: `{"key1": "value1", "key2": {"key3": "value3"}}`,
expression: "key2.key3",
expectedValue: []interface{}{"value3"},
expectedError: "", // No error expected
},
{
name: "ValidExpressionArray",
inputJSON: `{"key1": "value1", "key2": [1, 2, 3]}`,
expression: "key2[1]",
expectedValue: []interface{}{int64(2)},
expectedError: "", // No error expected
},
{
name: "ValidExpressionMap",
inputJSON: `{"key1": "value1", "key2": {"subkey1": "subvalue1"}}`,
expression: "key2",
expectedValue: []interface{}{map[string]interface{}{"subkey1": "subvalue1"}},
expectedError: "", // No error expected
},
{
name: "InvalidJSON",
inputJSON: `invalid json`,
expression: "key1",
expectedValue: nil,
expectedError: "could not parse json",
},
{
name: "InvalidExpression",
inputJSON: `{"key1": "value1"}`,
expression: "key2",
expectedValue: nil,
expectedError: "could not find element by path key2 in JSON",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Call the GetValueFromJSON function with the test input
value, err := GetValueFromJSON([]byte(testCase.inputJSON), testCase.expression)
// Check if the error message matches the expected result
if testCase.expectedError == "" {
assert.NoError(t, err) // No error expected
} else {
assert.Error(t, err) // Error expected
assert.Contains(t, err.Error(), testCase.expectedError)
}
// Check if the returned value is an array and matches the expected result
assert.IsType(t, []interface{}{}, value)
assert.Equal(t, testCase.expectedValue, value)
})
}
}
================================================
FILE: asserts/json/util.go
================================================
package json
import (
"bytes"
"fmt"
"reflect"
"strings"
"github.com/ohler55/ojg/oj"
"github.com/ozontech/cute/errors"
)
// Contains is a function to assert that a jsonpath expression extracts a value in an array
// Given the response is {"first": 777, "second": [{"key_1": "some_key", "value": "some_value"}]}, we can assert on the result like so `$.second[? @.key_1=="some_key"].value`, "some_value"
// About expression - https://goessner.net/articles/JsonPath/
func contains(data []byte, expression string, expect interface{}) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
ok, found := insideArray(value, expect)
if !ok {
return errors.NewAssertError("Contains", fmt.Sprintf("on path %v. %v could not be applied builtin len()", expression, expect), nil, nil)
}
if !found {
return errors.NewAssertError("Contains", fmt.Sprintf("on path %v. expect %v, but actual %v", expression, expect, value), value, expect)
}
}
return nil
}
func equalAbstract(data []byte, expression string, expect interface{}, name string) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
if !objectsAreEqual(value, expect) {
return errors.NewAssertError(name, fmt.Sprintf("on path %v. expect %v, but actual %v", expression, expect, value), value, expect)
}
}
return nil
}
func notEqualAbstract(data []byte, expression string, expect interface{}, name string) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
if objectsAreEqual(value, expect) {
return errors.NewAssertError(name, fmt.Sprintf("on path %v. expect %v, but actual %v", expression, expect, value), value, expect)
}
}
return nil
}
// Equal is a function to assert that a jsonpath expression matches the given value
// About expression - https://goessner.net/articles/JsonPath/
func equal(data []byte, expression string, expect interface{}) error {
return equalAbstract(data, expression, expect, "Equal")
}
// NotEqual is a function to check json path expression value is not equal to given value
// About expression - https://goessner.net/articles/JsonPath/
func notEqual(data []byte, expression string, expect interface{}) error {
return notEqualAbstract(data, expression, expect, "NotEqual")
}
// EqualJSON is a function to check json path expression value is equal to given json
// About expression - https://goessner.net/articles/JsonPath/
func equalJSON(data []byte, expression string, expect []byte) error {
obj, err := oj.Parse(expect)
if err != nil {
return fmt.Errorf("could not parse json in EqualJSON error: '%s'", err)
}
return equalAbstract(data, expression, obj, "EqualJSON")
}
// NotEqualJSON is a function to check json path expression value is not equal to given json
// About expression - https://goessner.net/articles/JsonPath/
func notEqualJSON(data []byte, expression string, expect []byte) error {
obj, err := oj.Parse(expect)
if err != nil {
return fmt.Errorf("could not parse json in NotEqualJSON error: '%s'", err)
}
return notEqualAbstract(data, expression, obj, "NotEqualJSON")
}
// Length is a function to asserts that value is the expected length
// About expression - https://goessner.net/articles/JsonPath/
func length(data []byte, expression string, expectLength int) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
v := reflect.ValueOf(value)
if v.Len() != expectLength {
return errors.NewAssertError("Length", fmt.Sprintf("on path %v. expect lenght %v, but actual %v", expression, expectLength, v.Len()), v.Len(), expectLength)
}
}
return nil
}
// GreaterThan is a function to asserts that value is greater than the given length
// About expression - https://goessner.net/articles/JsonPath/
func greaterThan(data []byte, expression string, minimumLength int) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
v := reflect.ValueOf(value)
if v.Len() <= minimumLength {
return errors.NewAssertError("GreaterThan", fmt.Sprintf("on path %v. %v is greater than %v", expression, v.Len(), minimumLength), v.Len(), minimumLength)
}
}
return nil
}
// GreaterOrEqualThan is a function to asserts that value is greater or equal than the given length
// About expression - https://goessner.net/articles/JsonPath/
func greaterOrEqualThan(data []byte, expression string, minimumLength int) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
v := reflect.ValueOf(value)
if v.Len() < minimumLength {
return errors.NewAssertError("GreaterOrEqualThan", fmt.Sprintf("on path %v. %v is greater or equal than %v", expression, v.Len(), minimumLength), v.Len(), minimumLength)
}
}
return nil
}
// LessThan is a function to asserts that value is less than the given length
// About expression - https://goessner.net/articles/JsonPath/
func lessThan(data []byte, expression string, maximumLength int) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
v := reflect.ValueOf(value)
if v.Len() >= maximumLength {
return errors.NewAssertError("LessThan", fmt.Sprintf("on path %v. %v is less than %v", expression, v.Len(), maximumLength), v.Len(), maximumLength)
}
}
return nil
}
// LessOrEqualThan is a function to asserts that value is less or equal than the given length
// About expression - https://goessner.net/articles/JsonPath/
func lessOrEqualThan(data []byte, expression string, maximumLength int) error {
values, err := GetValueFromJSON(data, expression)
if err != nil {
return err
}
for _, value := range values {
v := reflect.ValueOf(value)
if v.Len() > maximumLength {
return errors.NewAssertError("LessThan", fmt.Sprintf("on path %v. %v is less or equal than %v", expression, v.Len(), maximumLength), v.Len(), maximumLength)
}
}
return nil
}
// notEmpty is a function to asserts that value is not empty (!= 0, != null)
// About expression - https://goessner.net/articles/JsonPath/
func notEmpty(data []byte, expression string) error {
values, _ := GetValueFromJSON(data, expression)
if len(values) == 0 {
return errors.NewAssertError("NotEmpty", fmt.Sprintf("on path %v. value is not present", expression), nil, nil)
}
for _, value := range values {
if isEmpty(value) {
return errors.NewAssertError("NotEmpty", fmt.Sprintf("on path %v. value is not present", expression), nil, nil)
}
}
return nil
}
// Present is a function to asserts that value is present
// value can be 0 or null
// About expression - https://goessner.net/articles/JsonPath/
func present(data []byte, expression string) error {
values, err := GetValueFromJSON(data, expression)
if err != nil || len(values) == 0 {
return errors.NewAssertError("Present", fmt.Sprintf("on path %v. value not present", expression), nil, nil)
}
return nil
}
// NotPresent is a function to asserts that value is not present
// About expression - https://goessner.net/articles/JsonPath/
func notPresent(data []byte, expression string) error {
values, _ := GetValueFromJSON(data, expression)
for _, value := range values {
if !isEmpty(value) {
return errors.NewAssertError("NotPresent", fmt.Sprintf("on path %v. value present", expression), nil, nil)
}
}
return nil
}
func objectsAreEqual(expect, actual interface{}) bool {
if reflect.DeepEqual(expect, actual) {
return true
}
if expect == nil || actual == nil {
return expect == actual
}
if fmt.Sprintf("%v", expect) == fmt.Sprintf("%v", actual) {
return true
}
exp, ok := expect.([]byte)
if !ok {
return reflect.DeepEqual(expect, actual)
}
act, ok := actual.([]byte)
if !ok {
return false
}
if exp == nil || act == nil {
return exp == nil && act == nil
}
return bytes.Equal(exp, act)
}
func isEmpty(object interface{}) bool {
if object == nil {
return true
}
objValue := reflect.ValueOf(object)
switch objValue.Kind() {
case reflect.Ptr:
if objValue.IsNil() {
return true
}
deref := objValue.Elem().Interface()
return isEmpty(deref)
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
return objValue.Len() == 0
default:
zero := reflect.Zero(objValue.Type())
return reflect.DeepEqual(object, zero.Interface())
}
}
func insideArray(list interface{}, element interface{}) (ok, found bool) {
var (
listValue = reflect.ValueOf(list)
elementValue = reflect.ValueOf(element)
)
defer func() {
if err := recover(); err != nil {
ok = false
found = false
}
}()
if reflect.TypeOf(list).Kind() == reflect.String {
return true, strings.Contains(listValue.String(), elementValue.String())
}
if reflect.TypeOf(list).Kind() == reflect.Map {
mapKeys := listValue.MapKeys()
for i := 0; i < len(mapKeys); i++ {
if objectsAreEqual(mapKeys[i].Interface(), element) {
return true, true
}
}
return true, false
}
for i := 0; i < listValue.Len(); i++ {
if objectsAreEqual(listValue.Index(i).Interface(), element) {
return true, true
}
}
return true, false
}
================================================
FILE: builder.go
================================================
package cute
import (
"net/http"
"time"
)
const defaultHTTPTimeout = 30
var (
errorAssertIsNil = "assert must be not nil"
)
// HTTPTestMaker is a creator tests
type HTTPTestMaker struct {
httpClient *http.Client
middleware *Middleware
jsonMarshaler JSONMarshaler
}
// 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{
middleware: new(Middleware),
}
timeout = defaultHTTPTimeout * time.Second
roundTripper = http.DefaultTransport
jsMarshaler JSONMarshaler = &jsonMarshaler{}
)
for _, opt := range opts {
opt(o)
}
if o.httpTimeout != 0 {
timeout = o.httpTimeout
}
if o.httpRoundTripper != nil { //nolint
roundTripper = o.httpRoundTripper
}
httpClient := &http.Client{
Transport: roundTripper,
Timeout: timeout,
}
if o.httpClient != nil {
httpClient = o.httpClient
}
if o.jsonMarshaler != nil {
jsMarshaler = o.jsonMarshaler
}
m := &HTTPTestMaker{
httpClient: httpClient,
jsonMarshaler: jsMarshaler,
middleware: o.middleware,
}
return m
}
// NewTestBuilder is a function for initialization foundation for cute
func (m *HTTPTestMaker) NewTestBuilder() AllureBuilder {
tests := createDefaultTests(m)
return &cute{
baseProps: m,
countTests: 0,
tests: tests,
allureInfo: new(allureInformation),
allureLinks: new(allureLinks),
allureLabels: new(allureLabels),
parallel: false,
}
}
func createDefaultTests(m *HTTPTestMaker) []*Test {
tests := make([]*Test, 1)
tests[0] = createDefaultTest(m)
return tests
}
func createDefaultTest(m *HTTPTestMaker) *Test {
return &Test{
httpClient: m.httpClient,
jsonMarshaler: m.jsonMarshaler,
Middleware: createMiddlewareFromTemplate(m.middleware),
AllureStep: new(AllureStep),
Request: &Request{
Retry: new(RequestRetryPolitic),
},
Expect: &Expect{JSONSchema: new(ExpectJSONSchema)},
}
}
func createMiddlewareFromTemplate(m *Middleware) *Middleware {
after := make([]AfterExecute, 0, len(m.After))
after = append(after, m.After...)
afterT := make([]AfterExecuteT, 0, len(m.AfterT))
afterT = append(afterT, m.AfterT...)
before := make([]BeforeExecute, 0, len(m.Before))
before = append(before, m.Before...)
beforeT := make([]BeforeExecuteT, 0, len(m.BeforeT))
beforeT = append(beforeT, m.BeforeT...)
middleware := &Middleware{
After: after,
AfterT: afterT,
Before: before,
BeforeT: beforeT,
}
return middleware
}
func (qt *cute) Create() MiddlewareRequest {
return qt
}
func (qt *cute) CreateStep(name string) MiddlewareRequest {
qt.tests[qt.countTests].AllureStep.Name = name
return qt
}
func (qt *cute) CreateRequest() RequestHTTPBuilder {
return qt
}
================================================
FILE: builder_allure.go
================================================
package cute
import (
"fmt"
"github.com/ozontech/allure-go/pkg/allure"
)
func (qt *cute) Parallel() AllureBuilder {
qt.parallel = true
return qt
}
func (qt *cute) Title(title string) AllureBuilder {
qt.allureInfo.title = title
return qt
}
func (qt *cute) Epic(epic string) AllureBuilder {
qt.allureLabels.epic = epic
return qt
}
func (qt *cute) Titlef(format string, args ...interface{}) AllureBuilder {
qt.allureInfo.title = fmt.Sprintf(format, args...)
return qt
}
func (qt *cute) Descriptionf(format string, args ...interface{}) AllureBuilder {
qt.allureInfo.description = fmt.Sprintf(format, args...)
return qt
}
func (qt *cute) Stage(stage string) AllureBuilder {
qt.allureInfo.stage = stage
return qt
}
func (qt *cute) Stagef(format string, args ...interface{}) AllureBuilder {
qt.allureInfo.stage = fmt.Sprintf(format, args...)
return qt
}
func (qt *cute) Layer(value string) AllureBuilder {
qt.allureLabels.layer = value
return qt
}
func (qt *cute) TmsLink(tmsLink string) AllureBuilder {
qt.allureLinks.tmsLink = tmsLink
return qt
}
func (qt *cute) TmsLinks(tmsLinks ...string) AllureBuilder {
qt.allureLinks.tmsLinks = append(qt.allureLinks.tmsLinks, tmsLinks...)
return qt
}
func (qt *cute) SetIssue(issue string) AllureBuilder {
qt.allureLinks.issue = issue
return qt
}
func (qt *cute) SetTestCase(testCase string) AllureBuilder {
qt.allureLinks.testCase = testCase
return qt
}
func (qt *cute) Link(link *allure.Link) AllureBuilder {
qt.allureLinks.link = link
return qt
}
func (qt *cute) ID(value string) AllureBuilder {
qt.allureLabels.id = value
return qt
}
func (qt *cute) AllureID(value string) AllureBuilder {
qt.allureLabels.allureID = value
return qt
}
func (qt *cute) AddSuiteLabel(value string) AllureBuilder {
qt.allureLabels.suiteLabel = value
return qt
}
func (qt *cute) AddSubSuite(value string) AllureBuilder {
qt.allureLabels.subSuite = value
return qt
}
func (qt *cute) AddParentSuite(value string) AllureBuilder {
qt.allureLabels.parentSuite = value
return qt
}
func (qt *cute) Story(value string) AllureBuilder {
qt.allureLabels.story = value
return qt
}
func (qt *cute) Tag(value string) AllureBuilder {
qt.allureLabels.tag = value
return qt
}
func (qt *cute) Severity(value allure.SeverityType) AllureBuilder {
qt.allureLabels.severity = value
return qt
}
func (qt *cute) Owner(value string) AllureBuilder {
qt.allureLabels.owner = value
return qt
}
func (qt *cute) Lead(value string) AllureBuilder {
qt.allureLabels.lead = value
return qt
}
func (qt *cute) Label(label *allure.Label) AllureBuilder {
qt.allureLabels.label = label
return qt
}
func (qt *cute) Labels(labels ...*allure.Label) AllureBuilder {
qt.allureLabels.labels = labels
return qt
}
func (qt *cute) Description(description string) AllureBuilder {
qt.allureInfo.description = description
return qt
}
func (qt *cute) Tags(tags ...string) AllureBuilder {
qt.allureLabels.tags = tags
return qt
}
func (qt *cute) Feature(feature string) AllureBuilder {
qt.allureLabels.feature = feature
return qt
}
================================================
FILE: builder_asserts.go
================================================
package cute
import "time"
func (qt *cute) AssertBody(asserts ...AssertBody) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBody = append(qt.tests[qt.countTests].Expect.AssertBody, assertBodyWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertBody(asserts ...AssertBody) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBody = append(qt.tests[qt.countTests].Expect.AssertBody, assertBodyWithTrace(optionalAssertBody(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertBody(asserts ...AssertBody) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBody = append(qt.tests[qt.countTests].Expect.AssertBody, assertBodyWithTrace(brokenAssertBody(assert), trace))
}
return qt
}
func (qt *cute) RequireBody(asserts ...AssertBody) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBody = append(qt.tests[qt.countTests].Expect.AssertBody, assertBodyWithTrace(requireAssertBody(assert), trace))
}
return qt
}
func (qt *cute) AssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeaders = append(qt.tests[qt.countTests].Expect.AssertHeaders, assertHeadersWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeaders = append(qt.tests[qt.countTests].Expect.AssertHeaders, assertHeadersWithTrace(optionalAssertHeaders(assert), trace))
}
return qt
}
func (qt *cute) RequireHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeaders = append(qt.tests[qt.countTests].Expect.AssertHeaders, assertHeadersWithTrace(requireAssertHeaders(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeaders = append(qt.tests[qt.countTests].Expect.AssertHeaders, assertHeadersWithTrace(brokenAssertHeaders(assert), trace))
}
return qt
}
func (qt *cute) AssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponse = append(qt.tests[qt.countTests].Expect.AssertResponse, assertResponseWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponse = append(qt.tests[qt.countTests].Expect.AssertResponse, assertResponseWithTrace(optionalAssertResponse(assert), trace))
}
return qt
}
func (qt *cute) RequireResponse(asserts ...AssertResponse) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponse = append(qt.tests[qt.countTests].Expect.AssertResponse, assertResponseWithTrace(requireAssertResponse(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponse = append(qt.tests[qt.countTests].Expect.AssertResponse, assertResponseWithTrace(brokenAssertResponse(assert), trace))
}
return qt
}
func (qt *cute) AssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBodyT = append(qt.tests[qt.countTests].Expect.AssertBodyT, assertBodyTWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBodyT = append(qt.tests[qt.countTests].Expect.AssertBodyT, assertBodyTWithTrace(optionalAssertBodyT(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBodyT = append(qt.tests[qt.countTests].Expect.AssertBodyT, assertBodyTWithTrace(brokenAssertBodyT(assert), trace))
}
return qt
}
func (qt *cute) RequireBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertBodyT = append(qt.tests[qt.countTests].Expect.AssertBodyT, assertBodyTWithTrace(requireAssertBodyT(assert), trace))
}
return qt
}
func (qt *cute) AssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeadersT = append(qt.tests[qt.countTests].Expect.AssertHeadersT, assertHeadersTWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeadersT = append(qt.tests[qt.countTests].Expect.AssertHeadersT, assertHeadersTWithTrace(optionalAssertHeadersT(assert), trace))
}
return qt
}
func (qt *cute) RequireHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeadersT = append(qt.tests[qt.countTests].Expect.AssertHeadersT, assertHeadersTWithTrace(requireAssertHeadersT(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertHeadersT = append(qt.tests[qt.countTests].Expect.AssertHeadersT, assertHeadersTWithTrace(brokenAssertHeadersT(assert), trace))
}
return qt
}
func (qt *cute) AssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponseT = append(qt.tests[qt.countTests].Expect.AssertResponseT, assertResponseTWithTrace(assert, trace))
}
return qt
}
func (qt *cute) OptionalAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponseT = append(qt.tests[qt.countTests].Expect.AssertResponseT, assertResponseTWithTrace(optionalAssertResponseT(assert), trace))
}
return qt
}
func (qt *cute) BrokenAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponseT = append(qt.tests[qt.countTests].Expect.AssertResponseT, assertResponseTWithTrace(brokenAssertResponseT(assert), trace))
}
return qt
}
func (qt *cute) RequireResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder {
trace := getTrace()
for _, assert := range asserts {
if assert == nil {
panic(errorAssertIsNil)
}
qt.tests[qt.countTests].Expect.AssertResponseT = append(qt.tests[qt.countTests].Expect.AssertResponseT, assertResponseTWithTrace(requireAssertResponseT(assert), trace))
}
return qt
}
func (qt *cute) ExpectExecuteTimeout(t time.Duration) ExpectHTTPBuilder {
qt.tests[qt.countTests].Expect.ExecuteTime = t
return qt
}
func (qt *cute) ExpectStatus(code int) ExpectHTTPBuilder {
qt.tests[qt.countTests].Expect.Code = code
return qt
}
func (qt *cute) ExpectJSONSchemaString(schema string) ExpectHTTPBuilder {
qt.tests[qt.countTests].Expect.JSONSchema.String = schema
return qt
}
func (qt *cute) ExpectJSONSchemaByte(schema []byte) ExpectHTTPBuilder {
qt.tests[qt.countTests].Expect.JSONSchema.Byte = schema
return qt
}
func (qt *cute) ExpectJSONSchemaFile(filePath string) ExpectHTTPBuilder {
qt.tests[qt.countTests].Expect.JSONSchema.File = filePath
return qt
}
================================================
FILE: builder_middleware.go
================================================
package cute
func (qt *cute) StepName(name string) MiddlewareRequest {
qt.tests[qt.countTests].AllureStep.Name = name
return qt
}
func (qt *cute) BeforeExecute(fs ...BeforeExecute) MiddlewareRequest {
qt.tests[qt.countTests].Middleware.Before = append(qt.tests[qt.countTests].Middleware.Before, fs...)
return qt
}
func (qt *cute) BeforeExecuteT(fs ...BeforeExecuteT) MiddlewareRequest {
qt.tests[qt.countTests].Middleware.BeforeT = append(qt.tests[qt.countTests].Middleware.BeforeT, fs...)
return qt
}
func (qt *cute) After(fs ...AfterExecute) ExpectHTTPBuilder {
qt.tests[qt.countTests].Middleware.After = append(qt.tests[qt.countTests].Middleware.After, fs...)
return qt
}
func (qt *cute) AfterT(fs ...AfterExecuteT) ExpectHTTPBuilder {
qt.tests[qt.countTests].Middleware.AfterT = append(qt.tests[qt.countTests].Middleware.AfterT, fs...)
return qt
}
func (qt *cute) AfterExecute(fs ...AfterExecute) MiddlewareRequest {
qt.tests[qt.countTests].Middleware.After = append(qt.tests[qt.countTests].Middleware.After, fs...)
return qt
}
func (qt *cute) AfterExecuteT(fs ...AfterExecuteT) MiddlewareRequest {
qt.tests[qt.countTests].Middleware.AfterT = append(qt.tests[qt.countTests].Middleware.AfterT, fs...)
return qt
}
func (qt *cute) AfterTestExecute(fs ...AfterExecute) NextTestBuilder {
previousTest := 0
if qt.countTests != 0 {
previousTest = qt.countTests - 1
}
qt.tests[previousTest].Middleware.After = append(qt.tests[previousTest].Middleware.After, fs...)
return qt
}
func (qt *cute) AfterTestExecuteT(fs ...AfterExecuteT) NextTestBuilder {
previousTest := 0
if qt.countTests != 0 {
previousTest = qt.countTests - 1
}
qt.tests[previousTest].Middleware.AfterT = append(qt.tests[previousTest].Middleware.AfterT, fs...)
return qt
}
================================================
FILE: builder_option.go
================================================
package cute
import (
"net/http"
"time"
)
type options struct {
httpClient *http.Client
httpTimeout time.Duration
httpRoundTripper http.RoundTripper
jsonMarshaler JSONMarshaler
middleware *Middleware
}
// Option ...
type Option func(*options)
// WithHTTPClient is a function for set custom http client
func WithHTTPClient(client *http.Client) Option {
return func(o *options) {
o.httpClient = client
}
}
// WithJSONMarshaler is a function for set custom json marshaler
func WithJSONMarshaler(m JSONMarshaler) Option {
return func(o *options) {
o.jsonMarshaler = m
}
}
// WithCustomHTTPTimeout is a function for set custom http client timeout
func WithCustomHTTPTimeout(t time.Duration) Option {
return func(o *options) {
o.httpTimeout = t
}
}
// WithCustomHTTPRoundTripper is a function for set custom http round tripper
func WithCustomHTTPRoundTripper(r http.RoundTripper) Option {
return func(o *options) {
o.httpRoundTripper = r
}
}
// WithMiddlewareAfter is function for set function which will run AFTER test execution
func WithMiddlewareAfter(after ...AfterExecute) Option {
return func(o *options) {
o.middleware.After = append(o.middleware.After, after...)
}
}
// WithMiddlewareAfterT is function for set function which will run AFTER test execution
func WithMiddlewareAfterT(after ...AfterExecuteT) Option {
return func(o *options) {
o.middleware.AfterT = append(o.middleware.AfterT, after...)
}
}
// WithMiddlewareBefore is function for set function which will run BEFORE test execution
func WithMiddlewareBefore(before ...BeforeExecute) Option {
return func(o *options) {
o.middleware.Before = append(o.middleware.Before, before...)
}
}
// WithMiddlewareBeforeT is function for set function which will run BEFORE test execution
func WithMiddlewareBeforeT(beforeT ...BeforeExecuteT) Option {
return func(o *options) {
o.middleware.BeforeT = append(o.middleware.BeforeT, beforeT...)
}
}
================================================
FILE: builder_request.go
================================================
package cute
import (
"net/http"
"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.
// Deprecated: use RequestRetry instead
func (qt *cute) RequestRepeat(count int) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.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.
// Deprecated: use RequestRetryDelay instead
func (qt *cute) RequestRepeatDelay(delay time.Duration) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.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.
// Deprecated: use RequestRetryPolitic instead
func (qt *cute) RequestRepeatPolitic(politic *RequestRepeatPolitic) RequestHTTPBuilder {
if politic == nil {
panic("politic is nil in RequestRetryPolitic")
}
qt.tests[qt.countTests].Request.Retry = &RequestRetryPolitic{
Count: politic.Count,
Delay: politic.Delay,
Optional: politic.Optional,
Broken: politic.Broken,
}
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.
// Deprecated: use RequestRetryOptional instead
func (qt *cute) RequestRepeatOptional(option bool) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.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.
// Deprecated: use RequestRetryBroken instead
func (qt *cute) RequestRepeatBroken(broken bool) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.Broken = broken
return qt
}
// RequestRetry 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) RequestRetry(count int) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.Count = count
return qt
}
// RequestRetryDelay 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) RequestRetryDelay(delay time.Duration) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.Delay = delay
return qt
}
// RequestRetryPolitic 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) RequestRetryPolitic(politic *RequestRetryPolitic) RequestHTTPBuilder {
if politic == nil {
panic("politic is nil in RequestRetryPolitic")
}
qt.tests[qt.countTests].Request.Retry = politic
return qt
}
// RequestRetryOptional 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) RequestRetryOptional(option bool) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.Optional = option
return qt
}
// RequestRetryBroken 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) RequestRetryBroken(broken bool) RequestHTTPBuilder {
qt.tests[qt.countTests].Request.Retry.Broken = broken
return qt
}
// RequestSanitizerHook assigns the provided RequestSanitizerHook to the test,
// allowing URL sanitization before logging or reporting.
func (qt *cute) RequestSanitizerHook(hook RequestSanitizerHook) RequestHTTPBuilder {
qt.tests[qt.countTests].RequestSanitizer = hook
return qt
}
// ResponseSanitizerHook assigns the provided ResponseSanitizerHook to the test,
// allowing URL sanitization before logging or reporting.
func (qt *cute) ResponseSanitizerHook(hook ResponseSanitizerHook) RequestHTTPBuilder {
qt.tests[qt.countTests].ResponseSanitizer = hook
return qt
}
func (qt *cute) Request(r *http.Request) ExpectHTTPBuilder {
qt.tests[qt.countTests].Request.Base = r
return qt
}
func (qt *cute) RequestBuilder(r ...RequestBuilder) ExpectHTTPBuilder {
qt.tests[qt.countTests].Request.Builders = append(qt.tests[qt.countTests].Request.Builders, r...)
return qt
}
================================================
FILE: builder_retry.go
================================================
package cute
import "time"
// Retry is a function for configure test repeat
// if response.Code != Expect.Code or any of asserts are failed/broken than test will repeat counts with delay.
// Default delay is 1 second.
func (qt *cute) Retry(count int) MiddlewareRequest {
if count < 1 {
panic("count must be greater than 0")
}
qt.tests[qt.countTests].Retry.MaxAttempts = count
return qt
}
// RetryDelay set delay for test repeat.
// if response.Code != Expect.Code or any of asserts are failed/broken than test will repeat counts with delay.
// Default delay is 1 second.
func (qt *cute) RetryDelay(delay time.Duration) MiddlewareRequest {
if delay < 0 {
panic("delay must be greater than or equal to 0")
}
qt.tests[qt.countTests].Retry.Delay = delay
return qt
}
================================================
FILE: builder_table.go
================================================
package cute
import "net/http"
func (qt *cute) CreateTableTest() MiddlewareTable {
qt.isTableTest = true
return qt
}
func (qt *cute) PutNewTest(name string, r *http.Request, expect *Expect) TableTest {
// Validate, that first step is empty
if qt.countTests == 0 {
if qt.tests[0].Request.Base == nil &&
len(qt.tests[0].Request.Builders) == 0 {
qt.tests[0].Expect = expect
qt.tests[0].Name = name
qt.tests[0].Request.Base = r
return qt
}
}
newTest := createDefaultTest(qt.baseProps)
newTest.Expect = expect
newTest.Name = name
newTest.Request.Base = r
qt.tests = append(qt.tests, newTest)
qt.countTests++ // async?
return qt
}
func (qt *cute) PutTests(tests ...*Test) TableTest {
for _, test := range tests {
// Fill common fields
qt.fillBaseProps(test)
// Validate, that first step is empty
if qt.countTests == 0 {
if qt.tests[0].Request.Base == nil &&
len(qt.tests[0].Request.Builders) == 0 {
qt.tests[0] = test
continue
}
}
qt.tests = append(qt.tests, test)
qt.countTests++
}
return qt
}
func (qt *cute) fillBaseProps(t *Test) {
if qt.baseProps == nil {
return
}
if qt.baseProps.httpClient != nil {
t.httpClient = qt.baseProps.httpClient
}
if qt.baseProps.jsonMarshaler != nil {
t.jsonMarshaler = qt.baseProps.jsonMarshaler
}
if t.Middleware == nil {
t.Middleware = createMiddlewareFromTemplate(qt.baseProps.middleware)
} else {
t.Middleware.After = append(t.Middleware.After, qt.baseProps.middleware.After...)
t.Middleware.AfterT = append(t.Middleware.AfterT, qt.baseProps.middleware.AfterT...)
t.Middleware.Before = append(t.Middleware.Before, qt.baseProps.middleware.Before...)
t.Middleware.BeforeT = append(t.Middleware.BeforeT, qt.baseProps.middleware.BeforeT...)
}
}
func (qt *cute) NextTest() NextTestBuilder {
qt.countTests++ // async?
qt.tests = append(qt.tests, createDefaultTest(qt.baseProps))
return qt
}
================================================
FILE: builder_table_test.go
================================================
package cute
import (
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
func TestFillBaseProps_WhenBasePropsIsNil(t *testing.T) {
testObj := &Test{}
cuteObj := &cute{}
cuteObj.fillBaseProps(testObj)
require.Nil(t, testObj.httpClient)
require.Nil(t, testObj.jsonMarshaler)
require.Nil(t, testObj.Middleware)
}
func TestFillBaseProps_WhenBasePropsIsNotNil(t *testing.T) {
testObj := &Test{}
cuteObj := &cute{}
qtBaseProps := &HTTPTestMaker{
httpClient: &http.Client{},
jsonMarshaler: &jsonMarshaler{},
middleware: &Middleware{
After: []AfterExecute{
func(*http.Response, []error) error {
return nil
},
func(*http.Response, []error) error {
return nil
},
},
AfterT: []AfterExecuteT{func(T, *http.Response, []error) error { return nil }},
Before: []BeforeExecute{
func(*http.Request) error {
return nil
},
func(*http.Request) error {
return nil
},
},
BeforeT: []BeforeExecuteT{
func(T, *http.Request) error { return nil },
func(T, *http.Request) error {
return nil
},
},
},
}
cuteObj.baseProps = qtBaseProps
cuteObj.fillBaseProps(testObj)
require.Equal(t, qtBaseProps.httpClient, testObj.httpClient)
require.Equal(t, qtBaseProps.jsonMarshaler, testObj.jsonMarshaler)
require.Len(t, testObj.Middleware.After, len(qtBaseProps.middleware.After))
require.Len(t, testObj.Middleware.AfterT, len(qtBaseProps.middleware.AfterT))
require.Len(t, testObj.Middleware.Before, len(qtBaseProps.middleware.Before))
require.Len(t, testObj.Middleware.BeforeT, len(qtBaseProps.middleware.BeforeT))
}
func TestFillBaseProps_WhenBasePropsIsNotNil_After(t *testing.T) {
testObj := &Test{
Middleware: &Middleware{
After: []AfterExecute{
func(*http.Response, []error) error {
return nil
},
},
},
}
cuteObj := &cute{}
qtBaseProps := &HTTPTestMaker{
httpClient: &http.Client{},
jsonMarshaler: &jsonMarshaler{},
middleware: &Middleware{
After: []AfterExecute{
func(*http.Response, []error) error {
return nil
},
func(*http.Response, []error) error {
return nil
},
},
BeforeT: []BeforeExecuteT{
func(T, *http.Request) error { return nil },
func(T, *http.Request) error {
return nil
},
},
},
}
cuteObj.baseProps = qtBaseProps
cuteObj.fillBaseProps(testObj)
require.Equal(t, qtBaseProps.httpClient, testObj.httpClient)
require.Equal(t, qtBaseProps.jsonMarshaler, testObj.jsonMarshaler)
require.Len(t, testObj.Middleware.After, len(qtBaseProps.middleware.After)+1)
require.Len(t, testObj.Middleware.AfterT, len(qtBaseProps.middleware.AfterT))
require.Len(t, testObj.Middleware.Before, len(qtBaseProps.middleware.Before))
require.Len(t, testObj.Middleware.BeforeT, len(qtBaseProps.middleware.BeforeT))
}
func TestFillBaseProps_WhenBasePropsIsNotNil_Middleware(t *testing.T) {
testObj := &Test{
Middleware: &Middleware{
After: []AfterExecute{
func(*http.Response, []error) error {
return nil
},
func(*http.Response, []error) error {
return nil
},
},
AfterT: []AfterExecuteT{
func(T, *http.Response, []error) error {
return nil
},
func(T, *http.Response, []error) error {
return nil
},
func(T, *http.Response, []error) error {
return nil
},
},
Before: []BeforeExecute{
func(*http.Request) error {
return nil
},
func(*http.Request) error {
return nil
},
func(*http.Request) error {
return nil
},
func(*http.Request) error {
return nil
},
},
BeforeT: []BeforeExecuteT{
func(T, *http.Request) error {
return nil
},
},
},
}
cuteObj := &cute{}
qtBaseProps := &HTTPTestMaker{
httpClient: &http.Client{},
jsonMarshaler: &jsonMarshaler{},
middleware: &Middleware{
After: []AfterExecute{
func(*http.Response, []error) error {
return nil
},
func(*http.Response, []error) error {
return nil
},
},
AfterT: []AfterExecuteT{func(T, *http.Response, []error) error { return nil }},
Before: []BeforeExecute{
func(*http.Request) error {
return nil
},
func(*http.Request) error {
return nil
},
},
BeforeT: []BeforeExecuteT{
func(T, *http.Request) error { return nil },
func(T, *http.Request) error {
return nil
},
},
},
}
cuteObj.baseProps = qtBaseProps
cuteObj.fillBaseProps(testObj)
require.Equal(t, qtBaseProps.httpClient, testObj.httpClient)
require.Equal(t, qtBaseProps.jsonMarshaler, testObj.jsonMarshaler)
require.Len(t, testObj.Middleware.After, len(qtBaseProps.middleware.After)+2)
require.Len(t, testObj.Middleware.AfterT, len(qtBaseProps.middleware.AfterT)+3)
require.Len(t, testObj.Middleware.Before, len(qtBaseProps.middleware.Before)+4)
require.Len(t, testObj.Middleware.BeforeT, len(qtBaseProps.middleware.BeforeT)+1)
}
================================================
FILE: builder_test.go
================================================
package cute
import (
"net/http"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/allure"
"github.com/stretchr/testify/require"
)
func TestBuilderAfterTest(t *testing.T) {
var (
maker = NewHTTPTestMaker()
)
ht := maker.NewTestBuilder().
Create().
RequestBuilder().
NextTest().
AfterTestExecute(
func(response *http.Response, errors []error) error {
return nil
},
func(response *http.Response, errors []error) error {
return nil
}).
AfterTestExecuteT(
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
)
res := ht.(*cute)
require.Len(t, res.tests[0].Middleware.After, 2)
require.Len(t, res.tests[0].Middleware.AfterT, 3)
}
func TestBuilderAfterTestTwoStep(t *testing.T) {
var (
maker = NewHTTPTestMaker(
WithMiddlewareBefore(
func(request *http.Request) error {
return nil
},
func(request *http.Request) error {
return nil
},
),
WithMiddlewareBeforeT(
func(t T, request *http.Request) error {
return nil
},
),
WithMiddlewareAfter(
func(response *http.Response, errors []error) error {
return nil
},
),
WithMiddlewareAfterT(
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
),
)
)
ht :=
maker.NewTestBuilder().
Create().
RequestBuilder().
NextTest().
AfterTestExecute(
func(response *http.Response, errors []error) error {
return nil
},
func(response *http.Response, errors []error) error {
return nil
}).
AfterTestExecuteT(
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
).
Create().
AfterExecute(
func(response *http.Response, errors []error) error {
return nil
},
).
AfterExecuteT(
func(t T, response *http.Response, errors []error) error {
return nil
}).
RequestBuilder().
NextTest().
AfterTestExecute(
func(response *http.Response, errors []error) error {
return nil
},
)
res := ht.(*cute)
require.Len(t, res.tests[0].Middleware.After, 2+1)
require.Len(t, res.tests[0].Middleware.Before, 2)
require.Len(t, res.tests[0].Middleware.BeforeT, 1)
require.Len(t, res.tests[0].Middleware.AfterT, 3+3)
require.Len(t, res.tests[1].Middleware.After, 2+1)
require.Len(t, res.tests[1].Middleware.AfterT, 1+3)
require.Len(t, res.tests[1].Middleware.Before, 2)
require.Len(t, res.tests[1].Middleware.BeforeT, 1)
}
func TestNewTestBuilder(t *testing.T) {
var (
maker = NewHTTPTestMaker()
ht = maker.NewTestBuilder().(*cute)
)
require.NotNil(t, ht.tests)
require.Len(t, ht.tests, 1)
require.NotNil(t, ht.tests[0].Request)
require.NotNil(t, ht.tests[0].Middleware)
require.NotNil(t, ht.tests[0].AllureStep)
require.NotNil(t, ht.allureInfo)
require.NotNil(t, ht.baseProps.httpClient)
}
func TestHTTPTestMaker(t *testing.T) {
var (
maker = NewHTTPTestMaker()
ht = maker.NewTestBuilder()
title = "title"
epic = "epic"
desc = "desc"
feature = "feature"
tags = []string{"tag_1", "tag_2"}
stepName = "stepname"
req, _ = http.NewRequest(http.MethodGet, "https://site.go", nil)
executeTime = time.Duration(10)
status = 400
schemaStg = "some_json_schema"
schemaBt = []byte("some_json_schema")
schemaFile = "file_path"
id = "ID"
addSuiteLabel = "AddSuiteLabel"
addSubSuite = "AddSubSuite"
addParentSuite = "AddParentSuite"
story = "Story"
tag = "Tag"
allureID = "AllureID"
owner = "Owner"
lead = "Lead"
label = &allure.Label{Name: "kek", Value: "lol"}
setIssue = "SetIssue"
setTestCase = "SetTestCase"
repeatCount = 10
repeatDelay = time.Duration(10)
link = &allure.Link{
Name: "link",
Type: "type",
URL: "http://go.go",
}
labels = []*allure.Label{
{
Name: "label_1",
Value: "value_1",
},
{
Name: "label_2",
Value: "value_2",
},
}
assertHeaders = []AssertHeaders{
func(headers http.Header) error {
return nil
},
}
assertHeadersT = []AssertHeadersT{
func(t T, headers http.Header) error {
return nil
},
func(t T, headers http.Header) error {
return nil
},
}
assertBody = []AssertBody{
func(body []byte) error {
return nil
},
}
assertBodyT = []AssertBodyT{
func(t T, body []byte) error {
return nil
},
func(t T, body []byte) error {
return nil
},
}
assertResponse = []AssertResponse{
func(resp *http.Response) error {
return nil
},
}
assertResponseT = []AssertResponseT{
func(t T, resp *http.Response) error {
return nil
},
func(t T, resp *http.Response) error {
return nil
},
}
after = []AfterExecute{
func(response *http.Response, errors []error) error {
return nil
},
func(response *http.Response, errors []error) error {
return nil
},
}
afterT = []AfterExecuteT{
func(t T, response *http.Response, errors []error) error {
return nil
},
func(t T, response *http.Response, errors []error) error {
return nil
},
}
)
ht.
Title(title).
Tags(tags...).
Epic(epic).
Feature(feature).
ID(id).
AddSuiteLabel(addSuiteLabel).
AddSubSuite(addSubSuite).
AddParentSuite(addParentSuite).
Story(story).
Tag(tag).
Severity(allure.CRITICAL).
AllureID(allureID).
Owner(owner).
Lead(lead).
Label(label).
Labels(labels...).
SetIssue(setIssue).
SetTestCase(setTestCase).
Link(link).
Description(desc).
CreateStep(stepName).
RequestRetry(repeatCount).
RequestRetryDelay(repeatDelay).
Request(req).
ExpectExecuteTimeout(executeTime).
ExpectStatus(status).
ExpectJSONSchemaByte(schemaBt).
ExpectJSONSchemaString(schemaStg).
ExpectJSONSchemaFile(schemaFile).
AssertHeaders(assertHeaders...).
AssertHeadersT(assertHeadersT...).
AssertBody(assertBody...).
AssertBodyT(assertBodyT...).
AssertResponse(assertResponse...).
AssertResponseT(assertResponseT...).
After(after...).
AfterT(afterT...)
resHt := ht.(*cute)
resTest := resHt.tests[0]
require.Equal(t, title, resHt.allureInfo.title)
require.Equal(t, tags, resHt.allureLabels.tags)
require.Equal(t, desc, resHt.allureInfo.description)
require.Equal(t, feature, resHt.allureLabels.feature)
require.Equal(t, epic, resHt.allureLabels.epic)
require.Equal(t, stepName, resTest.AllureStep.Name)
require.Equal(t, req, resTest.Request.Base)
require.Equal(t, executeTime, resTest.Expect.ExecuteTime)
require.Equal(t, status, resTest.Expect.Code)
require.Equal(t, schemaBt, resTest.Expect.JSONSchema.Byte)
require.Equal(t, schemaStg, resTest.Expect.JSONSchema.String)
require.Equal(t, schemaFile, resTest.Expect.JSONSchema.File)
require.Equal(t, id, resHt.allureLabels.id)
require.Equal(t, addSuiteLabel, resHt.allureLabels.suiteLabel)
require.Equal(t, addSubSuite, resHt.allureLabels.subSuite)
require.Equal(t, addParentSuite, resHt.allureLabels.parentSuite)
require.Equal(t, story, resHt.allureLabels.story)
require.Equal(t, tag, resHt.allureLabels.tag)
require.Equal(t, owner, resHt.allureLabels.owner)
require.Equal(t, lead, resHt.allureLabels.lead)
require.Equal(t, label, resHt.allureLabels.label)
require.Equal(t, allureID, resHt.allureLabels.allureID)
require.Equal(t, setIssue, resHt.allureLinks.issue)
require.Equal(t, setTestCase, resHt.allureLinks.testCase)
require.Equal(t, link, resHt.allureLinks.link)
require.Equal(t, repeatCount, resTest.Request.Retry.Count)
require.Equal(t, repeatDelay, resTest.Request.Retry.Delay)
require.Equal(t, len(assertHeaders), len(resTest.Expect.AssertHeaders))
require.Equal(t, len(assertHeadersT), len(resTest.Expect.AssertHeadersT))
require.Equal(t, len(assertBody), len(resTest.Expect.AssertBody))
require.Equal(t, len(assertBodyT), len(resTest.Expect.AssertBodyT))
require.Equal(t, len(assertResponse), len(resTest.Expect.AssertResponse))
require.Equal(t, len(assertResponseT), len(resTest.Expect.AssertResponseT))
require.Equal(t, len(after), len(resTest.Middleware.After))
require.Equal(t, len(afterT), len(resTest.Middleware.AfterT))
}
func TestCreateDefaultTest(t *testing.T) {
resTest := createDefaultTest(&HTTPTestMaker{httpClient: http.DefaultClient, middleware: new(Middleware)})
require.Equal(t, &Test{
httpClient: http.DefaultClient,
Name: "",
AllureStep: new(AllureStep),
Middleware: &Middleware{
After: make([]AfterExecute, 0),
AfterT: make([]AfterExecuteT, 0),
Before: make([]BeforeExecute, 0),
BeforeT: make([]BeforeExecuteT, 0),
},
Request: &Request{
Retry: new(RequestRetryPolitic),
},
Expect: &Expect{
JSONSchema: new(ExpectJSONSchema),
},
}, resTest)
}
func TestCreateTableTest(t *testing.T) {
c := &cute{}
c.CreateTableTest()
require.True(t, c.isTableTest)
}
func TestPutNewTest(t *testing.T) {
tests := make([]*Test, 1)
tests[0] = createDefaultTest(&HTTPTestMaker{httpClient: http.DefaultClient, middleware: new(Middleware)})
var (
c = &cute{tests: tests, baseProps: &HTTPTestMaker{
middleware: &Middleware{},
}}
reqOne, _ = http.NewRequest("GET", "URL_1", nil)
expectOne = &Expect{Code: 200}
reqSecond, _ = http.NewRequest("POST", "URL_1", nil)
expectSecond = &Expect{Code: 400}
)
c.PutNewTest("name_1", reqOne, expectOne)
c.PutNewTest("name_2", reqSecond, expectSecond)
require.Equal(t, c.tests[0].Name, "name_1")
require.Equal(t, c.tests[0].Expect, expectOne)
require.Equal(t, c.tests[0].Request.Base, reqOne)
require.Equal(t, c.tests[1].Name, "name_2")
require.Equal(t, c.tests[1].Expect, expectSecond)
require.Equal(t, c.tests[1].Request.Base, reqSecond)
}
func TestPutTests(t *testing.T) {
var (
tests = createDefaultTests(&HTTPTestMaker{httpClient: http.DefaultClient, middleware: new(Middleware)})
c = &cute{tests: tests}
reqOne, _ = http.NewRequest("GET", "URL_1", nil)
expectOne = &Expect{Code: 200}
reqSecond, _ = http.NewRequest("POST", "URL_1", nil)
expectSecond = &Expect{Code: 400}
)
tests = append(tests,
&Test{
Name: "name_1",
Request: &Request{
Base: reqOne,
},
Expect: expectOne,
},
&Test{
Name: "name_2",
Request: &Request{
Base: reqSecond,
},
Expect: expectSecond,
},
)
c.PutTests(tests...)
require.Equal(t, c.tests[0].Name, "name_1")
require.Equal(t, c.tests[0].Expect, expectOne)
require.Equal(t, c.tests[0].Request.Base, reqOne)
require.Equal(t, c.tests[1].Name, "name_2")
require.Equal(t, c.tests[1].Expect, expectSecond)
require.Equal(t, c.tests[1].Request.Base, reqSecond)
}
func TestCreateHTTPTestMakerWithHttpClient(t *testing.T) {
cli := &http.Client{
Transport: nil,
CheckRedirect: nil,
Jar: nil,
Timeout: 100,
}
maker := NewHTTPTestMaker(WithHTTPClient(cli))
require.Equal(t, cli, maker.httpClient)
require.Equal(t, time.Duration(100), maker.httpClient.Timeout)
}
type rt struct {
}
func (r *rt) RoundTrip(*http.Request) (*http.Response, error) {
return nil, nil
}
func TestCreateHTTPMakerOps(t *testing.T) {
timeout := time.Second * 100
roundTripper := &rt{}
maker := NewHTTPTestMaker(
WithCustomHTTPTimeout(timeout),
WithCustomHTTPRoundTripper(roundTripper),
)
require.Equal(t, timeout, maker.httpClient.Timeout)
require.Equal(t, roundTripper, maker.httpClient.Transport)
}
================================================
FILE: cute.go
================================================
package cute
import (
"context"
"strings"
"testing"
"github.com/ozontech/allure-go/pkg/allure"
"github.com/ozontech/allure-go/pkg/framework/core/allure_manager/manager"
"github.com/ozontech/allure-go/pkg/framework/core/common"
"github.com/ozontech/allure-go/pkg/framework/provider"
)
type cute struct {
baseProps *HTTPTestMaker
parallel bool
allureInfo *allureInformation
allureLinks *allureLinks
allureLabels *allureLabels
countTests int // Общее количество тестов.
isTableTest bool
tests []*Test
}
type allureInformation struct {
title string
description string
stage string
}
type allureLabels struct {
id string
feature string
epic string
tag string
tags []string
suiteLabel string
subSuite string
parentSuite string
story string
severity allure.SeverityType
owner string
lead string
label *allure.Label
labels []*allure.Label
allureID string
layer string
}
type allureLinks struct {
issue string
testCase string
link *allure.Link
tmsLink string
tmsLinks []string
}
func (qt *cute) ExecuteTest(ctx context.Context, t tProvider) []ResultsHTTPBuilder {
var internalT allureProvider
if t == nil {
panic("could not start test without testing.T")
}
stepCtx, isStepCtx := t.(provider.StepCtx)
if isStepCtx {
return qt.executeTestsInsideStep(ctx, stepCtx)
}
tOriginal, ok := t.(*testing.T)
if ok {
newT := createAllureT(tOriginal)
if !qt.isTableTest {
defer newT.FinishTest() //nolint
}
internalT = newT
}
allureT, ok := t.(provider.T)
if ok {
internalT = allureT
}
if qt.parallel {
internalT.Parallel()
}
return qt.executeTests(ctx, internalT)
}
func createAllureT(t *testing.T) *common.Common {
var (
newT = common.NewT(t)
callers = strings.Split(t.Name(), "/")
providerCfg = manager.NewProviderConfig().
WithFullName(t.Name()).
WithPackageName("package").
WithSuiteName(t.Name()).
WithRunner(callers[0])
newProvider = manager.NewProvider(providerCfg)
)
newProvider.NewTest(t.Name(), "package")
newT.SetProvider(newProvider)
newT.Provider.TestContext()
return newT
}
// executeTests is method for run tests
// It's could be table tests or usual tests
func (qt *cute) executeTests(ctx context.Context, allureProvider allureProvider) []ResultsHTTPBuilder {
var (
res = make([]ResultsHTTPBuilder, 0)
)
// Cycle for change number of Test
for i := 0; i <= qt.countTests; i++ {
currentTest := qt.tests[i]
// Execute by new T for table tests
if qt.isTableTest {
tableTestName := currentTest.Name
allureProvider.Run(tableTestName, func(inT provider.T) {
// Set current test name
inT.Title(tableTestName)
res = append(res, qt.executeInsideAllure(ctx, inT, currentTest))
})
} else {
currentTest.Name = allureProvider.Name()
// set labels
qt.setAllureInformation(allureProvider)
res = append(res, qt.executeInsideAllure(ctx, allureProvider, currentTest))
}
}
return res
}
// executeInsideAllure is method for run test inside allure
// It's could be table tests or usual tests
func (qt *cute) executeInsideAllure(ctx context.Context, allureProvider allureProvider, currentTest *Test) ResultsHTTPBuilder {
resT := currentTest.executeInsideAllure(ctx, allureProvider)
// Remove from base struct all asserts
currentTest.clearFields()
return resT
}
// executeTestsInsideStep is method for run group of tests inside provider.StepCtx
func (qt *cute) executeTestsInsideStep(ctx context.Context, stepCtx provider.StepCtx) []ResultsHTTPBuilder {
var (
res = make([]ResultsHTTPBuilder, 0)
)
// Cycle for change number of Test
for i := 0; i <= qt.countTests; i++ {
currentTest := qt.tests[i]
result := currentTest.executeInsideStep(ctx, stepCtx)
// Remove from base struct all asserts
currentTest.clearFields()
res = append(res, result)
}
return res
}
================================================
FILE: errors/broken.go
================================================
package errors
// BrokenError is an interface for set errors like Broken errors.
// If the function returns an error, which implements this interface, the allure step will has a broken status
type BrokenError interface {
IsBroken() bool
SetBroken(bool)
Error() string
}
// NewBrokenError returns error with a Broken tag for Allure
func NewBrokenError(err string) error {
return &CuteError{
Broken: true,
Message: err,
}
}
// WrapBrokenError returns error with a Broken tag for Allure
func WrapBrokenError(err error) error {
return &CuteError{
Broken: true,
Err: err,
}
}
================================================
FILE: errors/error.go
================================================
package errors
import "fmt"
const (
// ActualField is a key for actual value in error fields
ActualField = "Actual"
// ExpectedField is a key for expected value in error fields
ExpectedField = "Expected"
)
// AssertError is a common interface for all errors in the package
type AssertError interface {
error
WithNameError
WithFields
WithAttachments
WithTrace
}
// WithNameError is interface for creates allure step.
// If function returns error, which implement this interface, allure step will create automatically
type WithNameError interface {
GetName() string
SetName(string)
}
// WithFields is interface for put parameters in allure step.
// If function returns error, which implement this interface, parameters will add to allure step
type WithFields interface {
GetFields() map[string]interface{}
PutFields(map[string]interface{})
}
// WithTrace is interface for put trace in logs
type WithTrace interface {
GetTrace() string
SetTrace(string)
}
// Attachment represents an attachment to Allure with properties like name, MIME type, and content.
type Attachment struct {
Name string // Name of the attachment.
MimeType string // MIME type of the attachment.
Content []byte // Content of the attachment.
}
// WithAttachments is an interface that defines methods for managing attachments.
type WithAttachments interface {
GetAttachments() []*Attachment
PutAttachment(a *Attachment)
}
// CuteError is a struct for error with additional fields for allure and logs
type CuteError struct {
// Optional is a flag to determine if the error is optional
// If the error is optional, it will not fail the test
Optional bool
// Require is a flag to determine if the error is required
// If the error is required, it will fail the test
Require bool
// Broken is a flag to determine if the error is broken
// If the error is broken, it will fail the test and mark the test as broken in allure
Broken bool
// Name is a name of the error
Name string
// Message is a message of the error
Message string
// Err is a wrapped error
Err error
// Trace is a trace of the error
// It could be a file path, function name, or any other information
Trace string
// Fields is a map of additional fields for the error
// It could be actual and expected values, parameters, or any other information
// ActualField and ExpectedField fields will be logged
Fields map[string]interface{}
// Attachments is a slice of attachments for the error
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{
Name: name,
Message: message,
Fields: map[string]interface{}{
ActualField: actual,
ExpectedField: expected,
},
}
}
// 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 NewEmptyAssertError(name, message)
}
// 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,
Message: message,
Fields: map[string]interface{}{},
}
}
// 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 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
}
errText := a.Message
if a.Err != nil {
errText = a.Err.Error()
}
return fmt.Sprintf("%s\nCalled from: %s", errText, a.Trace)
}
// GetName is a method to get error name
// It is used for allure step name
func (a *CuteError) GetName() string {
return a.Name
}
// SetName is a method to set error name
// It is used for allure step name
func (a *CuteError) SetName(name string) {
a.Name = name
}
// GetFields ...
func (a *CuteError) GetFields() map[string]interface{} {
return a.Fields
}
// PutFields ...
func (a *CuteError) PutFields(fields map[string]interface{}) {
for k, v := range fields {
a.Fields[k] = v
}
}
// GetAttachments ...
func (a *CuteError) GetAttachments() []*Attachment {
return a.Attachments
}
// PutAttachment ...
func (a *CuteError) PutAttachment(attachment *Attachment) {
a.Attachments = append(a.Attachments, attachment)
}
// IsOptional ...
func (a *CuteError) IsOptional() bool {
return a.Optional
}
// SetOptional ...
func (a *CuteError) SetOptional(opt bool) {
a.Optional = opt
}
// IsRequire ...
func (a *CuteError) IsRequire() bool {
return a.Require
}
// SetRequire ...
func (a *CuteError) SetRequire(b bool) {
a.Require = b
}
// IsBroken ...
func (a *CuteError) IsBroken() bool {
return a.Broken
}
// SetBroken ...
func (a *CuteError) SetBroken(b bool) {
a.Broken = b
}
// GetTrace ...
func (a *CuteError) GetTrace() string {
return a.Trace
}
// SetTrace ...
func (a *CuteError) SetTrace(trace string) {
a.Trace = trace
}
================================================
FILE: errors/optional.go
================================================
package errors
// OptionalError is an interface for set errors like Optional errors.
// If the function returns an error, which implements this interface, the allure step will has to skip status
type OptionalError interface {
IsOptional() bool
SetOptional(bool)
}
// NewOptionalError returns error with an Optional tag for Allure
func NewOptionalError(err string) error {
return &CuteError{
Optional: true,
Message: err,
}
}
// WrapOptionalError returns error with an Optional tag for Allure
func WrapOptionalError(err error) error {
return &CuteError{
Optional: true,
Err: err,
}
}
================================================
FILE: errors/require.go
================================================
package errors
// RequireError is an interface for set errors like require error.
// If the function returns an error, which implements this interface, the allure step will has failed status
type RequireError interface {
IsRequire() bool
SetRequire(bool)
}
type requireError struct {
err error
require bool
}
// NewRequireError returns error with flag for execute t.FailNow() and finish test after this error
func NewRequireError(err string) error {
return &CuteError{
Require: true,
Message: err,
}
}
// WrapRequireError returns error with flag for execute t.FailNow() and finish test after this error
func WrapRequireError(err error) error {
return &CuteError{
Require: true,
Err: err,
}
}
================================================
FILE: errors/trace.go
================================================
package errors
// NewErrorWithTrace is a function for create error with trace
func NewErrorWithTrace(err, trace string) error {
return &CuteError{
Trace: trace,
Message: err,
}
}
// WrapErrorWithTrace is a function for wrap error with trace
func WrapErrorWithTrace(err error, trace string) error {
return &CuteError{
Trace: trace,
Err: err,
}
}
================================================
FILE: examples/custom_asserts.go
================================================
package examples
import (
"errors"
"net/http"
"github.com/ozontech/allure-go/pkg/allure"
"github.com/ozontech/cute"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/stretchr/testify/require"
)
func CustomAssertBodyWithCustomError() cute.AssertBody {
return func(bytes []byte) error {
if len(bytes) == 0 {
return cuteErrors.NewAssertError("customAssertBodyWithCustomError", "body must be not empty", "len is 0", "len more 0")
}
return nil
}
}
func CustomAssertBody() cute.AssertBody {
return func(bytes []byte) error {
if len(bytes) == 0 {
return errors.New("response body is empty")
}
return nil
}
}
func CustomAssertBodyT() cute.AssertBodyT {
return func(t cute.T, bytes []byte) error {
t.WithNewParameters("example_parameter", "example")
require.GreaterOrEqual(t, len(bytes), 100)
return nil
}
}
func CustomAssertBodyWithAllureStep() cute.AssertBodyT {
return func(t cute.T, bytes []byte) error {
step := allure.NewSimpleStep("Custom assert step")
defer func() {
t.Step(step)
}()
if len(bytes) == 0 {
step.Status = allure.Failed
step.WithAttachments(allure.NewAttachment("Error", allure.Text, []byte("response body is empty")))
return nil
}
return nil
}
}
func CustomAssertHeaders() cute.AssertHeaders {
return func(headers http.Header) error {
if len(headers) == 0 {
return errors.New("response without headers")
}
return nil
}
}
func CustomAssertResponse() cute.AssertResponse {
return func(resp *http.Response) error {
if resp.ContentLength == 0 {
return errors.New("content length is zero")
}
return nil
}
}
================================================
FILE: examples/inside_step_test.go
================================================
//go:build example
// +build example
package examples
import (
"context"
"net/http"
"net/url"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/runner"
"github.com/ozontech/cute"
)
func TestInsideStep(t *testing.T) {
runner.Run(t, "Single test with allure-go Runner", func(t provider.T) {
t.WithNewStep("First step", func(sCtx provider.StepCtx) {
sCtx.NewStep("Inside first step")
})
t.WithNewStep("Step name", func(sCtx provider.StepCtx) {
u, _ := url.Parse("https://jsonplaceholder.typicode.com/posts/1/comments")
cute.NewTestBuilder().
Title("Super simple test").
Tags("simple", "suite", "some_local_tag", "json").
Parallel().
Create().
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
}),
cute.WithURL(u),
cute.WithMethod(http.MethodPost),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusCreated).
ExecuteTest(context.Background(), sCtx)
})
})
}
================================================
FILE: examples/masked_data_test.go
================================================
//go:build example
// +build example
package examples
import (
"context"
"net/http"
"net/url"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/runner"
"github.com/ozontech/cute"
)
func TestSanitizer(t *testing.T) {
runner.Run(t, "Single test with request and response sanitizer", func(t provider.T) {
t.WithNewStep("First step", func(sCtx provider.StepCtx) {
sCtx.NewStep("Inside first step")
})
t.WithNewStep("Step name", func(sCtx provider.StepCtx) {
u, _ := url.Parse("https://jsonplaceholder.typicode.com/posts/1/comments?example=11")
query := u.Query()
query.Set("name", "Vasya")
u.RawQuery = query.Encode()
cute.NewTestBuilder().
Title("Super simple test").
Tags("simple", "suite", "some_local_tag", "json").
Parallel().
Create().
RequestSanitizerHook(func(req *http.Request) {
req.URL.Path = "/path/masked"
values := req.URL.Query()
values.Set("example", "masked")
req.URL.RawQuery = values.Encode()
req.Header["some_header"] = []string{"masked"}
}).
ResponseSanitizerHook(func(resp *http.Response) {
resp.Header["some_header"] = []string{"masked"}
resp.Header["Content-Type"] = []string{"masked"}
}).
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
}),
cute.WithURL(u),
cute.WithMethod(http.MethodPost),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusCreated).
ExecuteTest(context.Background(), sCtx)
})
})
}
================================================
FILE: examples/parallel_test.go
================================================
//go:build example
// +build example
package examples
import (
"context"
"net/http"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute"
)
func Test_Async_1(t *testing.T) {
cute.NewTestBuilder().
Title("Title async test 1").
Tags("parallel_test").
Parallel().
Create().
BeforeExecuteT(
func(t cute.T, r *http.Request) error {
t.WithNewStep("insideBefore", func(stepCtx provider.StepCtx) {
time.Sleep(time.Second)
now := time.Now()
stepCtx.Logf("Test 1. Start time %v", now)
stepCtx.WithNewParameters("Test 1. Time", now)
})
return nil
},
).
AfterExecuteT(
func(t cute.T, resp *http.Response, errs []error) error {
t.WithNewStep("insideAfter", func(stepCtx provider.StepCtx) {
now := time.Now()
stepCtx.Logf("Test 1. Stop time %v", now)
stepCtx.WithNewParameters("Test 1. Stop time", now)
})
return nil
}).
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExecuteTest(context.Background(), t)
}
func Test_Async_2(t *testing.T) {
cute.NewTestBuilder().
Title("Title async test 2").
Tags("parallel_test").
Parallel().
Create().
BeforeExecuteT(
func(t cute.T, r *http.Request) error {
t.WithNewStep("insideBefore", func(stepCtx provider.StepCtx) {
now := time.Now()
stepCtx.Logf("Test 2. Start time %v", now)
stepCtx.WithNewParameters("Test 2. Start time", now)
time.Sleep(2 * time.Second)
})
return nil
},
).
AfterExecuteT(
func(t cute.T, resp *http.Response, errs []error) error {
t.WithNewStep("insideAfter", func(stepCtx provider.StepCtx) {
now := time.Now()
stepCtx.Logf("test 2. Stop time %v", now)
stepCtx.WithNewParameters("Test 2. Stop time", now)
})
return nil
}).
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectStatus(200).
ExecuteTest(context.Background(), t)
}
================================================
FILE: examples/single_test.go
================================================
//go:build example
// +build example
package examples
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/allure"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/runner"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/json"
)
func Test_Single_1(t *testing.T) {
cute.NewTestBuilder().
Title("Single test with default T").
Tag("single_test").
Description("some_description").
Parallel().
Create().
RequestRetry(3).
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMarshalBody(struct {
Name string `json:"name"`
}{
Name: "Vasya Pupkin",
}),
cute.WithQueryKV("socks", "42"),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusOK).
AssertBody(json.Diff("{\"aaa\":\"bb\"}")).
AssertBody(
json.Present("$[1].name"),
json.Present("$[0].passport"), // Example fail
json.Equal("$[0].email", "Eliseo@gardner.biz"),
CustomAssertBody(),
).
AssertBodyT(func(t cute.T, body []byte) error {
t.Step(allure.NewSimpleStep("inside Assert body. 1 ", allure.NewParameters("key", "value")...))
return nil
}).
After(
func(response *http.Response, errors []error) error {
b, err := io.ReadAll(response.Body)
if err != nil {
return err
}
email, err := json.GetValueFromJSON(b, "$[0].email")
if err != nil {
return err
}
fmt.Println("Email from test", email)
return nil
},
).
ExecuteTest(context.Background(), t)
}
func Test_Single_Broken(t *testing.T) {
cute.NewTestBuilder().
Title("Test_Single_Broken").
Create().
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.StatusOK).
NextTest().
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
).
AssertBody(func(body []byte) error {
return errors.New("it's NOT must be run")
},
).
ExecuteTest(context.Background(), t)
t.Skip()
}
func Test_Single_RepeatPolitic_Optional_Success_Test(t *testing.T) {
cute.NewTestBuilder().
Title("Test_Single_RepeatPolitic_Optional_Success_Test").
Create().
RequestRetry(2).
RequestRetryOptional(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().
RequestRetry(2).
RequestRetryOptional(false).
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").
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
).
AssertBodyT(func(t cute.T, body []byte) error {
err := errors.New("example broken error")
return cuteErrors.WrapBrokenError(err)
}).
ExpectStatus(http.StatusOK).
NextTest().
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
).
AssertBody(func(body []byte) error {
return errors.New("it's NOT must be run")
},
).
ExecuteTest(context.Background(), t)
}
func Test_Single_2_AllureRunner(t *testing.T) {
runner.Run(t, "Single test with allure-go Runner", func(t provider.T) {
var (
testMaker = cute.NewHTTPTestMaker()
testBuilder = testMaker.NewTestBuilder()
)
u, _ := url.Parse("https://jsonplaceholder.typicode.com/")
u.Path = path.Join(u.Path, "/posts/1/comments")
testBuilder.
Title("Single test with allure.T and repeat errors").
Tag("single_test").
Description("some_description").
Create().
RequestRetryDelay(3*time.Second). // delay before new try
RequestRetry(3). // count attempts
RequestBuilder(
cute.WithURL(u),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusBadGateway).
AssertBody(
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
).
OptionalAssertBody(
json.Present("$[0].photo"), // Example optional fail
).
ExecuteTest(context.Background(), t)
})
}
================================================
FILE: examples/suite/common.go
================================================
package suite
import (
"net/url"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/suite"
"github.com/stretchr/testify/require"
"github.com/ozontech/cute"
)
type ExampleSuite struct {
suite.Suite
host *url.URL
testMaker *cute.HTTPTestMaker
}
func (i *ExampleSuite) BeforeAll(t provider.T) {
// Prepare http test builder
i.testMaker = cute.NewHTTPTestMaker()
// Preparing host
host, err := url.Parse("https://jsonplaceholder.typicode.com/")
require.NoError(t, err)
i.host = host
}
func (i *ExampleSuite) BeforeEach(t provider.T) {
t.Feature("ExampleSuite")
t.Tags("some_global_tag")
}
================================================
FILE: examples/suite/main_test.go
================================================
//go:build example
// +build example
package suite
import (
"os"
"testing"
"github.com/ozontech/allure-go/pkg/framework/suite"
)
func TestExampleSuite(t *testing.T) {
os.Setenv("ALLURE_OUTPUT_PATH", "../") // custom, read Readme.md for more info
suite.RunSuite(t, new(ExampleSuite))
}
================================================
FILE: examples/suite/one_step.go
================================================
package suite
import (
"context"
"net/http"
"net/url"
"path"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/headers"
"github.com/ozontech/cute/asserts/json"
"github.com/ozontech/cute/examples"
)
/*
Example testing HTTP GET and validate body.
Validate:
1) Execute time
2) Status code
3) Validate body by json schema
4) Validate fields in json
Response:
[
{
"postId": 1,
"id": 1,
"name": "id labore ex et quam laborum",
"email": "Eliseo@gardner.biz",
"body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
},
{
"postId": 1,
"id": 2,
"name": "quo vero reiciendis velit similique earum",
"email": "Jayne_Kuhic@sydney.com",
"body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et"
},
{
"postId": 1,
"id": 3,
"name": "odio adipisci rerum aut animi",
"email": "Nikita@garfield.biz",
"body": "quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione"
},
{
"postId": 1,
"id": 4,
"name": "alias odio sit",
"email": "Lew@alysha.tv",
"body": "non et atque\noccaecati deserunt quas accusantium unde odit nobis qui voluptatem\nquia voluptas consequuntur itaque dolor\net qui rerum deleniti ut occaecati"
},
{
"postId": 1,
"id": 5,
"name": "vero eaque aliquid doloribus et culpa",
"email": "Hayden@althea.biz",
"body": "harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et"
}
]
*/
func (i *ExampleSuite) Test_OneStep(t provider.T) {
var (
testBuilder = i.testMaker.NewTestBuilder()
)
u, _ := url.Parse(i.host.String())
u.Path = path.Join(u.Path, "/posts/1/comments")
testBuilder.
Title("Test with one step").
Tags("one_stp", "some_local_tag", "suite", "json").
Feature("some_feature").
Epic("some_epic").
Description("some_description").
Parallel().
CreateStep("Example GET json request").
AfterExecuteT(func(t cute.T, resp *http.Response, errs []error) error {
if len(errs) != 0 {
return nil
}
/*
Implement some logic
*/
return nil
},
// After failed test
func(t cute.T, resp *http.Response, errs []error) error {
if len(errs) == 0 {
return nil
}
/*
Implement some logic
*/
return nil
},
).
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
"some_array_header": []string{"1", "2", "3", "some_thing"},
}),
cute.WithURL(u),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectJSONSchemaFile("file://./resources/example_valid_request.json").
ExpectStatus(http.StatusOK).
AssertBody(
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
json.NotPresent("$[1].some_not_present"),
json.LengthGreaterThan("$", 3),
json.Length("$", 5),
json.LengthLessThan("$", 100),
json.NotEqual("$[3].name", "kekekekeke"),
// Custom assert body
examples.CustomAssertBody(),
).
AssertBodyT(
// Custom assert body with testing.tb
examples.CustomAssertBodyT(),
func(t cute.T, body []byte) error {
/*
Implement here logic with TB
*/
time.Sleep(5 * time.Second)
return nil
},
).
AssertHeaders(
headers.Present("Content-Type"),
// Custom assert headers
examples.CustomAssertHeaders(),
).
AssertResponse(
examples.CustomAssertResponse(),
).
ExecuteTest(context.Background(), t)
}
================================================
FILE: examples/suite/one_step_errors.go
================================================
package suite
import (
"context"
"errors"
"net/http"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/headers"
"github.com/ozontech/cute/asserts/json"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/ozontech/cute/examples"
)
func (i *ExampleSuite) Test_OneStep_Errors(t provider.T) {
var (
testBuilder = i.testMaker.NewTestBuilder()
)
testBuilder.
Title("Test with errors").
Tags("one_step", "some_local_tag", "suite", "json").
Parallel().
CreateStep("Example GET json request").
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
"some_array_header": []string{"1", "2", "3", "some_thing"},
}),
cute.WithURI(i.host.String()+"/posts/1/comments"),
cute.WithMethod(http.MethodGet),
cute.WithMarshalBody(
map[string]interface{}{
"key": "value",
"more_key": map[string]interface{}{
"some_value": "sss",
},
},
),
).
ExpectExecuteTimeout(10*time.Second).
ExpectJSONSchemaFile("file://./resources/example_valid_request.json").
AssertBody(
json.Equal("$[0].email", "something"),
json.Present("$[1].not_present"),
json.LengthGreaterThan("$", 99999),
json.Length("$", 0),
// Custom assert body
examples.CustomAssertBody(),
examples.CustomAssertBodyWithCustomError(),
).
AssertHeaders(
headers.Present("Content-Type"),
// Custom assert headers
examples.CustomAssertHeaders(),
).
AssertResponse(
examples.CustomAssertResponse(),
).
AssertHeadersT(
func(t cute.T, headers http.Header) error {
// Example pretty print error
return cuteErrors.NewAssertError("custom_assert", "example custom assert", "empty", "not empty") //
},
).
// Example optional
OptionalAssertBody( // example optional assert
func(body []byte) error {
return errors.New("some optional error from OptionalAssert")
},
).
AssertBody(
func(body []byte) error {
return cuteErrors.NewOptionalError("some optional error from creator") // example optional error
},
).
ExecuteTest(context.Background(), t)
}
================================================
FILE: examples/suite/resources/example_valid_request.json
================================================
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": [
{
"type": "object",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
},
{
"type": "object",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
},
{
"type": "object",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
},
{
"type": "object",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
},
{
"type": "object",
"properties": {
"postId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"postId",
"id",
"name",
"email",
"body"
]
}
]
}
================================================
FILE: examples/suite/simple.go
================================================
package suite
import (
"context"
"net/http"
"net/url"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute"
)
/*
Example simple request
Validate:
1) Execute time
2) Status code
*/
func (i *ExampleSuite) Test_Simple(t provider.T) {
var (
testMaker = cute.NewHTTPTestMaker()
testBuilder = testMaker.NewTestBuilder()
)
u, _ := url.Parse("https://jsonplaceholder.typicode.com/posts/1/comments")
testBuilder.
Title("Super simple test").
Tags("simple", "suite", "some_local_tag", "json").
Parallel().
Create().
RequestBuilder(
cute.WithHeaders(map[string][]string{
"some_header": []string{"something"},
}),
cute.WithURL(u),
cute.WithMethod(http.MethodPost),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusCreated).
ExecuteTest(context.Background(), t)
}
================================================
FILE: examples/suite/two_steps.go
================================================
package suite
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute"
"github.com/ozontech/cute/examples"
)
/*
Example testing HTTP POST, validate body and make second request.
Validate:
1) Execute time
2) Status code
*/
func (i *ExampleSuite) Test_TwoSteps(t provider.T) {
var (
testBuilder = i.testMaker.NewTestBuilder()
// Request body
r = `
{
"result": {
"author": "Yours Truly",
"date": "15.11.1993",
"slides": [
{
"title": "Beer",
"type": "drink"
},
{
"title": "Apple",
"type": "fruit"
},
{
"title": "Orange",
"type": "fruit"
}
],
"Info": {
"shop": "BigShopPlus",
"address": "address"
},
"title": "Sample Show"
}
}
`
)
u, _ := url.Parse(i.host.String())
u.Path = path.Join(u.Path, "/posts/1/comments")
req, _ := http.NewRequest(http.MethodPost, u.String(), ioutil.NopCloser(strings.NewReader(r)))
req.Header = map[string][]string{
"some_auth_token": []string{fmt.Sprint(11111)},
}
testBuilder.
Title("Test in suite with two steps").
Tags("suite", "some_tag").
Parallel().
CreateStep("Creat entry /posts/1").
// CreateWithStep first step
Request(req).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusCreated).
AssertBody(
// Custom assert body
examples.CustomAssertBody(),
).
NextTest().
CreateStep("Delete entry").
// CreateWithStep second step for delete
RequestBuilder(
cute.WithURL(u),
cute.WithMethod(http.MethodDelete),
cute.WithHeaders(map[string][]string{
"some_auth_token": []string{fmt.Sprint(11111)},
}),
).
ExecuteTest(context.Background(), t)
}
================================================
FILE: examples/table_test/table_test.go
================================================
//go:build example
// +build example
package table_test
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"testing"
"time"
"github.com/ozontech/cute"
"github.com/ozontech/cute/asserts/json"
"github.com/ozontech/cute/errors"
)
func init() {
os.Setenv("ALLURE_OUTPUT_PATH", "../") // custom, read Readme.md for more info
}
func Test_Table(t *testing.T) {
u, _ := url.Parse("https://jsonplaceholder.typicode.com/posts/1/comments")
req, _ := http.NewRequest(http.MethodPost, u.String(), nil)
req.Header = map[string][]string{
"some_auth_token": []string{fmt.Sprint(11111)},
}
cute.NewTestBuilder().
Title("Example put tests in table test").
Tag("table_test").
CreateTableTest().
PutNewTest(
"Execute validation 1",
req,
&cute.Expect{
Code: 201,
}).
PutNewTest(
"Execute validation 2",
req,
&cute.Expect{
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
},
},
).
ExecuteTest(context.Background(), t)
}
func Test_Table_Array(t *testing.T) {
tests := []*cute.Test{
{
Name: "Create something",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodPost),
},
},
Expect: &cute.Expect{
Code: 200,
},
},
{
Name: "Delete something",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
func(body []byte) error {
return errors.NewAssertError("example error", "example message", nil, nil)
},
},
},
},
}
cute.NewTestBuilder().
Title("Example table test").
Tag("table_test").
Description("Execute array tests").
CreateTableTest().
PutTests(tests...).
ExecuteTest(context.Background(), t)
}
func Test_One_Execute(t *testing.T) {
test := &cute.Test{
Name: "test_1",
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: nil,
}
test.Execute(context.Background(), t)
}
func Test_Array_Retry_OptionalFirstTries(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Retry: &cute.Retry{
MaxAttempts: 10,
Delay: 1 * time.Second,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/201,202"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_2",
Retry: &cute.Retry{
MaxAttempts: 10,
Delay: 1 * time.Second,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/403,404"),
cute.WithMethod(http.MethodGet),
cute.WithMarshalBody([]byte("{\"test\":\"abc\"}")),
},
},
Expect: &cute.Expect{
Code: 404,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_Retry_OptionalFirstTries_UltimatelyFailing(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Retry: &cute.Retry{
MaxAttempts: 4,
Delay: 1 * time.Second,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/202,200"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_2",
Retry: &cute.Retry{
MaxAttempts: 3,
Delay: 1 * time.Second,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/403,401"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 404,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_TimeoutRetry(t *testing.T) {
var executeTimeout = 3000
tests := []*cute.Test{
{
Retry: &cute.Retry{
MaxAttempts: 2,
},
Name: "test_timeout",
Middleware: &cute.Middleware{
Before: []cute.BeforeExecute{
cute.BeforeExecute(func(request *http.Request) error {
query := request.URL.Query()
query.Set("sleep", strconv.Itoa(executeTimeout))
request.URL.RawQuery = query.Encode()
executeTimeout = executeTimeout - 1000
return nil
}),
},
},
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/202?sleep=3000"),
cute.WithBody([]byte("{\"test\":\"abc\"}")),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 202,
ExecuteTime: 3 * time.Second,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodPost),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_2",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
func(body []byte) error {
return errors.NewAssertError("example error", "example message", nil, nil)
},
},
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_All_Parallel(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_201",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/201"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_200_delay_5s",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/200?sleep=5000"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
},
},
{
Name: "test_202_delay_3s",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/202?sleep=3000"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 202,
},
},
{
Name: "test_203",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/203"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 203,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_Some_Parallel(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_parallel_1",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/201?sleep=1000"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_parallel_2",
Parallel: true,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/202?sleep=1000"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 202,
},
},
{
Name: "test_1_sequential",
Parallel: false,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodPost),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_2_sequential",
Parallel: false,
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 200,
AssertBody: []cute.AssertBody{
json.Equal("$[0].email", "Eliseo@gardner.biz"),
json.Present("$[1].name"),
},
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_Retry(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_1",
Parallel: true,
Retry: &cute.Retry{
MaxAttempts: 10,
Delay: 1,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/201,202"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 201,
},
},
{
Name: "test_2",
Parallel: true,
Retry: &cute.Retry{
MaxAttempts: 10,
Delay: 1,
},
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/Random/403,404"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 404,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
func Test_Array_Timeout(t *testing.T) {
tests := []*cute.Test{
{
Name: "test_timeout",
Middleware: nil,
Request: &cute.Request{
Builders: []cute.RequestBuilder{
cute.WithURI("https://httpstat.us/202?sleep=3000"),
cute.WithMethod(http.MethodGet),
},
},
Expect: &cute.Expect{
Code: 202,
ExecuteTime: 2 * time.Second,
},
},
}
for _, test := range tests {
test.Execute(context.Background(), t)
}
}
================================================
FILE: examples/two_step_test.go
================================================
//go:build example
// +build example
package examples
import (
"context"
"errors"
"fmt"
"net/http"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/allure-go/pkg/framework/runner"
"github.com/ozontech/cute"
)
func Test_TwoSteps_1(t *testing.T) {
cute.NewTestBuilder().
Title("Test with two requests.").
Tags("two_steps").
Parallel().
CreateStep("Create entry /posts/1").
// CreateWithStep first step
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusCreated).
NextTest().
CreateStep("Delete entry").
// CreateWithStep second step for delete
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodDelete),
cute.WithHeaders(map[string][]string{
"some_auth_token": []string{fmt.Sprint(11111)},
}),
).
ExecuteTest(context.Background(), t)
}
func Test_TwoSteps_2_AllureRunner(t *testing.T) {
runner.Run(t, "Test with two steps", func(t provider.T) {
testBuilder := cute.NewHTTPTestMaker().NewTestBuilder()
testBuilder.
Title("Test with two requests executed by allure-go").
Tag("two_steps").
Description("some_description").
CreateStep("Request 1").
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectStatus(http.StatusOK).
ExecuteTest(context.Background(), t)
testBuilder.
CreateStep("Request 2").
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/2/comments"),
cute.WithMethod(http.MethodGet),
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusOK).
ExecuteTest(context.Background(), t)
})
}
func Test_TwoSteps_3(t *testing.T) {
responseCode := 0
// First step.
cute.NewTestBuilder().
Title("Test with two requests and parse body.").
Tag("two_steps").
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"),
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. This test isn't run, because previous test has failed require validation
Create().
RequestBuilder(
cute.WithURI("https://jsonplaceholder.typicode.com/posts/2/comments"),
cute.WithMethod(http.MethodDelete),
).
ExecuteTest(context.Background(), t)
fmt.Println("Response code from first request", responseCode)
}
================================================
FILE: examples/upload_file_test.go
================================================
//go:build example_upload_file
// +build example_upload_file
package examples
import (
"context"
"net/http"
"testing"
"github.com/ozontech/cute"
)
func TestUploadFile(t *testing.T) {
cute.NewTestBuilder().
Title("Upload file").
Create().
RequestBuilder(
cute.WithURI("http://localhost:7000/v1/banner"),
cute.WithMethod("POST"),
cute.WithFormKV("body", []byte("{\"name\": \"Vasya\"}")), // Fill the form with the body
cute.WithFileFormKV("image", &cute.File{ // Fill the form with the file
Path: "/vasya/thebestmypicture.png",
}),
).
ExpectStatus(http.StatusOK).
ExecuteTest(context.Background(), t)
}
================================================
FILE: go.mod
================================================
module github.com/ozontech/cute
go 1.21
require (
github.com/josephburnett/jd v1.7.1
github.com/ohler55/ojg v1.21.1
github.com/ozontech/allure-go/pkg/allure v0.6.13
github.com/ozontech/allure-go/pkg/framework v0.6.31
github.com/stretchr/testify v1.8.4
github.com/xeipuuv/gojsonschema v1.2.0
moul.io/http2curl/v2 v2.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
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/josephburnett/jd v1.7.1 h1:oXBPMS+SNnILTMGj1fWLK9pexpeJUXtbVFfRku/PjBU=
github.com/josephburnett/jd v1.7.1/go.mod h1:R8ZnZnLt2D4rhW4NvBc/USTo6mzyNT6fYNIIWOJA9GY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/ohler55/ojg v1.21.1 h1:b2RLUaDcy9gvn46dmhTjezu/TDauoR0/kgKTqkwIxto=
github.com/ohler55/ojg v1.21.1/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
github.com/ozontech/allure-go/pkg/allure v0.6.13 h1:vkLSIvOEERHTxe+oq8DXDu/m+kLnVUkrXNN8xTKuKU4=
github.com/ozontech/allure-go/pkg/allure v0.6.13/go.mod h1:4oEG2yq+DGOzJS/ZjPc87C/mx3tAnlYpYonk77Ru/vQ=
github.com/ozontech/allure-go/pkg/framework v0.6.31 h1:u32AqB9/JkzcL5vSl8PSUmMZbsVTmoriDylI3FIYgX4=
github.com/ozontech/allure-go/pkg/framework v0.6.31/go.mod h1:wfqY4e4+w4BoRFDxHp7TNcdWfcCOWJV3BjrUqUughWY=
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/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/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=
================================================
FILE: init.go
================================================
package cute
var commonBuilder *HTTPTestMaker
func init() {
commonBuilder = NewHTTPTestMaker()
}
// NewTestBuilder is function for create base test builder,
// For create custom test builder use NewHTTPTestMaker()
func NewTestBuilder() AllureBuilder {
return commonBuilder.NewTestBuilder()
}
================================================
FILE: interface.go
================================================
package cute
import (
"context"
"net/http"
"time"
"github.com/ozontech/allure-go/pkg/allure"
)
// AllureBuilder is a scope of methods for create allure information (Title, Tags, etc.)
type AllureBuilder interface {
AllureInfoBuilder
AllureLabelsBuilder
AllureLinksBuilder
CreateBuilder
// Parallel signals that this Test is to be run in parallel with (and only with) other parallel tests.
// This function is not thread save. If you use multiply parallel with one T Test will panic.
Parallel() AllureBuilder
}
// AllureInfoBuilder is a scope of methods for create allure information (Title, Tags, etc.)
type AllureInfoBuilder interface {
// Title is a function for set title in allure information
Title(title string) AllureBuilder
Titlef(format string, args ...interface{}) AllureBuilder
// Description is a function for set description in allure information
Description(description string) AllureBuilder
Descriptionf(format string, args ...interface{}) AllureBuilder
Stage(stage string) AllureBuilder
Stagef(format string, args ...interface{}) AllureBuilder
}
// AllureLinksBuilder is a scope of methods to set allure links
type AllureLinksBuilder interface {
SetIssue(issue string) AllureBuilder
SetTestCase(testCase string) AllureBuilder
Link(link *allure.Link) AllureBuilder
TmsLink(tmsLink string) AllureBuilder
TmsLinks(tmsLinks ...string) AllureBuilder
}
// AllureLabelsBuilder is a scope of methods to set allure labels
type AllureLabelsBuilder interface {
Feature(feature string) AllureBuilder
Epic(epic string) AllureBuilder
AllureID(value string) AllureBuilder
Tags(tags ...string) AllureBuilder
ID(value string) AllureBuilder
AddSuiteLabel(value string) AllureBuilder
AddSubSuite(value string) AllureBuilder
AddParentSuite(value string) AllureBuilder
Story(value string) AllureBuilder
Tag(value string) AllureBuilder
Severity(value allure.SeverityType) AllureBuilder
Owner(value string) AllureBuilder
Lead(value string) AllureBuilder
Label(label *allure.Label) AllureBuilder
Labels(labels ...*allure.Label) AllureBuilder
Layer(value string) AllureBuilder
Stagef(format string, args ...interface{}) AllureBuilder
Stage(stage string) AllureBuilder
}
// CreateBuilder is functions for create Test or table tests
type CreateBuilder interface {
// Create is a function for save main information about allure and start write tests
Create() MiddlewareRequest
// CreateStep is a function for create step inside suite for Test
CreateStep(string) MiddlewareRequest
// CreateTableTest is function for create table Test
CreateTableTest() MiddlewareTable
}
// MiddlewareTable is functions for create table Test
type MiddlewareTable interface {
TableTest
BeforeTest
AfterTest
}
// MiddlewareRequest is function for create requests or add After/Before functions
type MiddlewareRequest interface {
RequestHTTPBuilder
RetryPolitic
BeforeTest
AfterTest
}
// RetryPolitic is a scope of methods to configure test repeat
type RetryPolitic interface {
// Retry is a function for configure test repeat
// if response.Code != Expect.Code or any of asserts are failed/broken than test will repeat counts with delay.
// Default delay is 1 second.
Retry(count int) MiddlewareRequest
// RetryDelay set delay for test repeat.
// if response.Code != Expect.Code or any of asserts are failed/broken than test will repeat counts with delay.
// Default delay is 1 second.
RetryDelay(timeout time.Duration) MiddlewareRequest
}
// BeforeTest are functions for processing request before test execution
// Same functions:
// Before
type BeforeTest interface {
// BeforeExecute is function for processing request before test execution
BeforeExecute(...BeforeExecute) MiddlewareRequest
// BeforeExecuteT is function for processing request before test execution
BeforeExecuteT(...BeforeExecuteT) MiddlewareRequest
}
// After are functions for processing response after test execution
// Same functions:
// AfterText
// AfterTestExecute
type After interface {
// After is function for processing response after test execution
After(...AfterExecute) ExpectHTTPBuilder
// AfterT is function for processing response after test execution
AfterT(...AfterExecuteT) ExpectHTTPBuilder
}
// AfterTest are functions for processing response after test execution
// Same functions:
// After
// AfterTestExecute
type AfterTest interface {
// AfterExecute is function for processing response after test execution
AfterExecute(...AfterExecute) MiddlewareRequest
// AfterExecuteT is function for processing response after test execution
AfterExecuteT(...AfterExecuteT) MiddlewareRequest
}
// AfterTestExecute are functions for processing response after test execution
// Same functions:
// After
// AfterText
type AfterTestExecute interface {
// AfterTestExecute is function for processing response after test execution
AfterTestExecute(...AfterExecute) NextTestBuilder
// AfterTestExecuteT is function for processing response after test execution
AfterTestExecuteT(...AfterExecuteT) NextTestBuilder
}
// TableTest is function for put request and assert for table tests
type TableTest interface {
// PutNewTest is function for put request and assert for table Test
PutNewTest(name string, r *http.Request, expect *Expect) TableTest
// PutTests is function for put requests and asserts for table Test
PutTests(params ...*Test) TableTest
ControlTest
}
// 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 set http.Request with builders
// Available builders:
// WithMethod
// WithURL
// WithHeaders
// WithHeadersKV
// WithBody
// WithMarshalBody
// WithBody
// WithURI
// WithQuery
// WithQueryKV
// WithFileForm
// WithFileFormKV
// WithForm
// WithFormKV
RequestBuilder(r ...RequestBuilder) ExpectHTTPBuilder
RequestParams
}
// RequestParams is a scope of methods to configure request
type RequestParams interface {
// 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.
// Deprecated: use RequestRetry instead
RequestRepeat(count int) RequestHTTPBuilder
RequestRetry(count int) RequestHTTPBuilder
// RequestRepeatDelay set delay for request repeat.
// if response.Code != Expect.Code, than request will repeat counts with delay.
// Default delay is 1 second.
// Deprecated: use RequestRetryDelay instead
RequestRepeatDelay(delay time.Duration) RequestHTTPBuilder
RequestRetryDelay(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.
// Deprecated: use RequestRetryPolitic instead
RequestRepeatPolitic(politic *RequestRepeatPolitic) RequestHTTPBuilder
RequestRetryPolitic(politic *RequestRetryPolitic) 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.
// Deprecated: use RequestRetryOptional instead
RequestRepeatOptional(optional bool) RequestHTTPBuilder
RequestRetryOptional(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.
// Deprecated: use RequestRetryBroken instead
RequestRepeatBroken(broken bool) RequestHTTPBuilder
RequestRetryBroken(broken bool) RequestHTTPBuilder
// RequestSanitizerHook sets a RequestSanitizerHook function for the request.
// This hook allows you to modify or mask parts of the request URL (e.g., hide sensitive data)
// before it is logged or added to the test report (Allure).
// Example usage: RequestWithSanitizeHook(func(req *http.Request) { ... }).
// Example: RequestWithSanitizeHook(func(req *http.Request) { req.URL.Path = "/masked" }).
// Example: RequestWithSanitizeHook(func(req *http.Request) { req.Header["some_header"] = []string{"masked"} }).
RequestSanitizerHook(hook RequestSanitizerHook) RequestHTTPBuilder
// ResponseSanitizerHook sets a ResponseSanitizerHook function for the request.
// This hook allows you to modify or mask parts of the response body (e.g., hide sensitive data)
// before it is logged or added to the test report (Allure).
// Example usage: ResponseWithSanitizeHook(func(resp *http.Response) { ... }).
ResponseSanitizerHook(hook ResponseSanitizerHook) RequestHTTPBuilder
}
// ExpectHTTPBuilder is a scope of methods for validate http response
type ExpectHTTPBuilder interface {
// ExpectExecuteTimeout is function for validate time of execution
// Default value - 10 seconds
ExpectExecuteTimeout(t time.Duration) ExpectHTTPBuilder
// ExpectStatus is function for validate response status code
ExpectStatus(code int) ExpectHTTPBuilder
// ExpectJSONSchemaString is function for validate response by json schema from string
ExpectJSONSchemaString(schema string) ExpectHTTPBuilder
// ExpectJSONSchemaByte is function for validate response by json schema from byte
ExpectJSONSchemaByte(schema []byte) ExpectHTTPBuilder
// ExpectJSONSchemaFile is function for validate response by json schema from file
// For get file from network use:
// "http://www.some_host.com/schema.json"
// For get local file use:
// "file://./project/me/schema.json"
ExpectJSONSchemaFile(path string) ExpectHTTPBuilder
// AssertBody is function for validate response body.
// Available asserts from asserts/json/json.go:
// Contains is a function to assert that a jsonpath expression extracts a value in an array
// Equal is a function to assert that a jsonpath expression matches the given value
// NotEqual is a function to check jsonpath expression value is not equal to given value
// Length is a function to asserts that jsonpath expression value is the expected length
// GreaterThan is a function to asserts that jsonpath expression value is greater than the given length
// LessThan is a function to asserts that jsonpath expression value is less than the given length
// Present is a function to asserts that jsonpath expression value is present
// 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 Skipped
OptionalAssertBody(asserts ...AssertBody) ExpectHTTPBuilder
// BrokenAssertBody is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertBody(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 Skipped
OptionalAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder
// BrokenAssertBodyT is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertBodyT(asserts ...AssertBodyT) ExpectHTTPBuilder
// AssertHeaders is function for validate response headers
// Available asserts from asserts/headers/headers.go:
// Present is a function to asserts header is present
// 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 Skipped
OptionalAssertHeaders(asserts ...AssertHeaders) ExpectHTTPBuilder
// BrokenAssertHeaders is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertHeaders(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 Skipped
OptionalAssertHeadersT(asserts ...AssertHeadersT) ExpectHTTPBuilder
// BrokenAssertHeadersT is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertHeadersT(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 Skipped
OptionalAssertResponse(asserts ...AssertResponse) ExpectHTTPBuilder
// BrokenAssertResponse is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertResponse(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 Skipped
OptionalAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder
// BrokenAssertResponseT is function for validate response, if it's failed, then test will be Broken.
// Mark in allure as Broken
BrokenAssertResponseT(asserts ...AssertResponseT) ExpectHTTPBuilder
After
ControlTest
}
// ControlTest is function for manipulating tests
type ControlTest interface {
NextTest() NextTestBuilder
// ExecuteTest is a function for execute Test
ExecuteTest(ctx context.Context, t tProvider) []ResultsHTTPBuilder
}
// NextTestBuilder is a scope of methods for processing response, after Test.
type NextTestBuilder interface {
AfterTestExecute
CreateBuilder
}
// ResultsHTTPBuilder is a scope of methods for processing results
type ResultsHTTPBuilder interface {
// GetHTTPResponse is a function, which returns http response
GetHTTPResponse() *http.Response
// GetErrors is a function, which returns all errors from test
GetErrors() []error
// GetName is a function, which returns name of Test
GetName() string
// GetResultState is a function, which returns state of test
// State could be ResultStateSuccess, ResultStateBroken, ResultStateFail
GetResultState() ResultState
}
// BeforeExecute is a function for processing request before test execution
type BeforeExecute func(*http.Request) error
// BeforeExecuteT is a function for processing request before test execution
type BeforeExecuteT func(T, *http.Request) error
// AfterExecute is a function for processing response after test execution
type AfterExecute func(*http.Response, []error) error
// AfterExecuteT is a function for processing response after test execution
type AfterExecuteT func(T, *http.Response, []error) error
================================================
FILE: internal/utils/body.go
================================================
package utils
import (
"bytes"
"io"
"net/http"
)
// GetBody get body from IO
func GetBody(body io.ReadCloser) ([]byte, error) {
var (
err error
buf = new(bytes.Buffer)
)
_, err = io.Copy(buf, body)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// DrainBody ...
func DrainBody(body io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
if body == nil || body == http.NoBody {
// No copying needed. Preserve the magic sentinel meaning of NoBody.
return http.NoBody, http.NoBody, nil
}
var buf bytes.Buffer
if _, err = buf.ReadFrom(body); err != nil {
return nil, body, err
}
if err = body.Close(); err != nil {
return nil, body, err
}
return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
}
================================================
FILE: internal/utils/json.go
================================================
package utils
import (
"bytes"
"encoding/json"
)
// ToJSON returns string Json representation of any object that can be marshaled.
func ToJSON(v interface{}) (string, error) {
j, err := json.Marshal(v)
return string(j), err
}
// PrettyJSON make indent to json byte array. Returns prettified json as []byte or error if is it impossible
func PrettyJSON(b []byte) ([]byte, error) {
var out bytes.Buffer
err := json.Indent(&out, b, "", " ")
return out.Bytes(), err
}
================================================
FILE: json_marshaler.go
================================================
package cute
import "encoding/json"
// JSONMarshaler is marshaler which use for marshal/unmarshal JSON to/from struct
type JSONMarshaler interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
}
type jsonMarshaler struct {
}
func (j jsonMarshaler) Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
func (j jsonMarshaler) Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
================================================
FILE: jsonschema.go
================================================
package cute
import (
"fmt"
"github.com/ozontech/cute/errors"
"github.com/xeipuuv/gojsonschema"
)
// Validate is a function to validate json by json schema.
// Automatically add information about validation to allure.
func (it *Test) validateJSONSchema(t internalT, body []byte) []error {
var (
expect gojsonschema.JSONLoader
)
switch {
case it.Expect.JSONSchema.String != "":
expect = gojsonschema.NewStringLoader(it.Expect.JSONSchema.String)
case it.Expect.JSONSchema.Byte != nil:
expect = gojsonschema.NewBytesLoader(it.Expect.JSONSchema.Byte)
case it.Expect.JSONSchema.File != "":
expect = gojsonschema.NewReferenceLoader(it.Expect.JSONSchema.File)
default:
return nil
}
return it.executeWithStep(t, "Validate body by JSON schema", func(_ T) []error {
return checkJSONSchema(expect, body)
})
}
func checkJSONSchema(expect gojsonschema.JSONLoader, data []byte) []error {
scope := make([]error, 0)
validateResult, err := gojsonschema.Validate(expect, gojsonschema.NewBytesLoader(data))
if err != nil {
return []error{errors.NewEmptyAssertError("could not validate json schema", err.Error())}
}
if !validateResult.Valid() && len(validateResult.Errors()) > 0 {
for _, resultError := range validateResult.Errors() {
scope = append(
scope,
createJSONSchemaError(resultError),
)
}
}
return scope
}
func createJSONSchemaError(err gojsonschema.ResultError) error {
fields := make(map[string]interface{})
textError := ""
if v, ok := err.Details()["context"]; ok {
textError = fmt.Sprintf("On path: %v.", v)
fields["Path"] = v
}
if v, ok := err.Details()["field"]; ok {
textError = fmt.Sprintf("%v Error field: %v.", textError, v)
fields["Field"] = v
}
textError = fmt.Sprintf("%v Error: %v.", textError, err.String())
assertError := errors.NewAssertError(
fmt.Sprintf("Error \"%v\"", err.Type()),
textError,
err.Details()["given"],
err.Details()["expected"])
assertError.(errors.WithFields).PutFields(fields)
return assertError
}
================================================
FILE: jsonschema_test.go
================================================
package cute
import (
"testing"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/stretchr/testify/require"
)
func TestValidateJSONSchemaEmptySchema(t *testing.T) {
var (
tBuilder = createDefaultTest(&HTTPTestMaker{middleware: new(Middleware)})
)
tBuilder.initEmptyFields()
errs := tBuilder.validateJSONSchema(nil, []byte{})
require.Len(t, errs, 0)
}
func TestValidateJSONSchemaFromString(t *testing.T) {
var (
tBuilder = createDefaultTest(&HTTPTestMaker{middleware: new(Middleware)})
tempT = createAllureT(t)
)
tBuilder.initEmptyFields()
body := []byte(`
{
"firstName": "Boris",
"lastName": "Britva",
"age": 77
}
`)
tBuilder.Expect.JSONSchema.String = `
{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"type": "integer",
"minimum": 0
}
}
}
`
errs := tBuilder.validateJSONSchema(tempT, body)
require.Len(t, errs, 0)
}
func TestValidateJSONSchemaFromStringWithError(t *testing.T) {
var (
tBuilder = createDefaultTest(&HTTPTestMaker{middleware: new(Middleware)})
tempT = createAllureT(t)
)
tBuilder.initEmptyFields()
body := []byte(`
{
"firstName": "Boris",
"lastName": "Britva",
"age": "1"
}
`)
tBuilder.Expect.JSONSchema.String = `
{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"type": "integer"
}
}
}
`
errs := tBuilder.validateJSONSchema(tempT, body)
require.Len(t, errs, 1)
require.Error(t, errs[0])
errWithName := errs[0].(cuteErrors.WithNameError)
require.NotEmpty(t, errWithName.GetName())
expectedError := errs[0].(cuteErrors.WithFields)
require.Equal(t, "integer", expectedError.GetFields()["Expected"])
require.Equal(t, "string", expectedError.GetFields()["Actual"])
require.Equal(t, "age", expectedError.GetFields()["Field"])
require.Equal(t, "(root).age", expectedError.GetFields()["Path"])
}
func TestValidateJSONSchemaFromByteWithTwoError(t *testing.T) {
var (
tBuilder = createDefaultTest(&HTTPTestMaker{middleware: new(Middleware)})
tempT = createAllureT(t)
)
tBuilder.initEmptyFields()
body := []byte(`
{
"firstName": "Boris",
"lastName": "Britva",
"age": "1"
}
`)
tBuilder.Expect.JSONSchema.String = `
{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "integer"
},
"age": {
"type": "integer"
}
}
}
`
errs := tBuilder.validateJSONSchema(tempT, body)
require.Len(t, errs, 2)
for _, err := range errs {
errWithName := err.(cuteErrors.WithNameError)
require.NotEmpty(t, errWithName.GetName())
expectedError := err.(cuteErrors.WithFields)
require.NotEmpty(t, expectedError.GetFields()["Actual"])
require.NotEmpty(t, expectedError.GetFields()["Expected"])
}
}
================================================
FILE: logger.go
================================================
package cute
import (
"fmt"
)
type tlogger interface {
Name() string
Logf(format string, args ...any)
Errorf(format string, args ...interface{})
}
// Info is a function to log info message
func (it *Test) Info(t tlogger, format string, args ...interface{}) {
it.logf(t, "INFO", format, args...)
}
// Error is a function to log error message
func (it *Test) Error(t tlogger, format string, args ...interface{}) {
it.errorf(t, "ERROR", format, args...)
}
// Debug is a function to log debug message
func (it *Test) Debug(t tlogger, format string, args ...interface{}) {
it.logf(t, "DEBUG", format, args...)
}
func (it *Test) logf(t tlogger, level, format string, args ...interface{}) {
name := it.Name
if it.Name == "" {
name = t.Name()
}
// If we are in a retry context, add some indication in the logs about the current attempt
if it.Retry.MaxAttempts != 1 {
t.Logf("[%s][%s](Attempt #%d) %v\n", name, level, it.Retry.currentCount, fmt.Sprintf(format, args...))
} else {
t.Logf("[%s][%s] %v\n", name, level, fmt.Sprintf(format, args...))
}
}
func (it *Test) errorf(t tlogger, level, format string, args ...interface{}) {
name := it.Name
if it.Name == "" {
name = t.Name()
}
// If we are in a retry context, add some indication in the logs about the current attempt
if it.Retry.MaxAttempts != 1 {
t.Logf("[%s][%s](Attempt #%d) %v\n", name, level, it.Retry.currentCount, fmt.Sprintf(format, args...))
} else {
t.Logf("[%s][%s] %v\n", name, level, fmt.Sprintf(format, args...))
}
}
================================================
FILE: provider.go
================================================
package cute
import (
"github.com/ozontech/allure-go/pkg/allure"
"github.com/ozontech/allure-go/pkg/framework/provider"
)
// T is internal testing.T provider
type T interface {
tProvider
logProvider
stepProvider
attachmentProvider
parametersProvider
}
type allureProvider interface {
internalT
Parallel()
Broken()
BrokenNow()
Run(testName string, testBody func(provider.T), tags ...string) (res *allure.Result)
infoAllureProvider
labelsAllureProvider
linksAllureProvider
}
type internalT interface {
Broken()
BrokenNow()
tProvider
logProvider
stepProvider
attachmentProvider
}
type tProvider interface {
Fail()
FailNow()
Name() string
Log(args ...interface{})
Logf(format string, args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
}
type logProvider interface {
LogStep(args ...interface{})
LogfStep(format string, args ...interface{})
}
type stepProvider interface {
Step(step *allure.Step)
WithNewStep(stepName string, step func(ctx provider.StepCtx), params ...*allure.Parameter)
}
type attachmentProvider interface {
WithAttachments(attachments ...*allure.Attachment)
WithNewAttachment(name string, mimeType allure.MimeType, content []byte)
}
type parametersProvider interface {
WithParameters(parameters ...*allure.Parameter)
WithNewParameters(kv ...interface{})
}
type infoAllureProvider interface {
Title(args ...interface{})
Titlef(format string, args ...interface{})
Description(args ...interface{})
Descriptionf(format string, args ...interface{})
Stage(args ...interface{})
Stagef(format string, args ...interface{})
}
type labelsAllureProvider interface {
ID(value string)
AllureID(value string)
Epic(value string)
Layer(value string)
AddSuiteLabel(value string)
AddSubSuite(value string)
AddParentSuite(value string)
Feature(value string)
Story(value string)
Tag(value string)
Tags(values ...string)
Severity(value allure.SeverityType)
Owner(value string)
Lead(value string)
Label(label *allure.Label)
Labels(labels ...*allure.Label)
}
type linksAllureProvider interface {
SetIssue(issue string)
SetTestCase(testCase string)
Link(link *allure.Link)
TmsLink(tmsCase string)
TmsLinks(tmsCases ...string)
}
================================================
FILE: request.go
================================================
package cute
import (
"net/url"
)
// RequestBuilder is a function for set options in request
type RequestBuilder func(o *requestOptions)
// File is struct for upload file in form field
// If you set Path, file will read from file system
// If you set Name and Body, file will set from this fields
type File struct {
Path string
Name string
Body []byte
}
type requestOptions struct {
method string
url *url.URL
uri string
headers map[string][]string
query map[string][]string
body []byte
bodyMarshal interface{}
fileForms map[string]*File
forms map[string][]byte
}
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),
}
}
// WithMethod is a function for set method (GET, POST ...) in request
func WithMethod(method string) func(o *requestOptions) {
return func(o *requestOptions) {
o.method = method
}
}
// WithURL is a function for set url in request
func WithURL(url *url.URL) func(o *requestOptions) {
return func(o *requestOptions) {
o.url = url
}
}
// WithURI is a function for set url in request
func WithURI(uri string) func(o *requestOptions) {
return func(o *requestOptions) {
o.uri = uri
}
}
// WithHeaders is a function for set or merge headers in request
func WithHeaders(headers map[string][]string) func(o *requestOptions) {
return func(o *requestOptions) {
for key, values := range headers {
o.headers[key] = append(o.headers[key], values...)
}
}
}
// WithHeadersKV is a function for set headers in request
func WithHeadersKV(name string, value string) func(o *requestOptions) {
return func(o *requestOptions) {
o.headers[name] = []string{value}
}
}
// 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) {
o.body = body
}
}
// WithMarshalBody is a function for marshal body and set body in request
func WithMarshalBody(body interface{}) func(o *requestOptions) {
return func(o *requestOptions) {
o.bodyMarshal = body
}
}
// WithFileFormKV is a function for set file form in request
func WithFileFormKV(name string, file *File) func(o *requestOptions) {
return func(o *requestOptions) {
o.fileForms[name] = file
}
}
// WithFileForm is a function for set file form in request
func WithFileForm(fileForms map[string]*File) func(o *requestOptions) {
return func(o *requestOptions) {
for name, file := range fileForms {
o.fileForms[name] = file
}
}
}
// WithFormKV is a function for set body in form request
func WithFormKV(name string, body []byte) func(o *requestOptions) {
return func(o *requestOptions) {
o.forms[name] = body
}
}
// WithForm is a function for set body in form request
func WithForm(forms map[string][]byte) func(o *requestOptions) {
return func(o *requestOptions) {
for name, body := range forms {
o.forms[name] = body
}
}
}
================================================
FILE: request_test.go
================================================
package cute
import (
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/require"
)
func TestRequest(t *testing.T) {
var (
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{}{
"some": "value",
},
"key_twi": "more",
}
uri = "https://goo.com"
u, _ = url.Parse("https://ho.com")
body = []byte("body")
)
WithHeaders(headers)(req)
WithMarshalBody(mBody)(req)
WithURI(uri)(req)
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)
}
================================================
FILE: result.go
================================================
package cute
import (
"net/http"
)
// ResultState is state of test
type ResultState int
// ResultState is state of test
const (
ResultStateSuccess ResultState = iota
ResultStateBroken
ResultStateFail
// resultStateFailNow is state for require validations (execute failNow)
resultStateFailNow
)
type testResults struct {
name string
state ResultState
resp *http.Response
errors []error
}
func newTestResult(name string, resp *http.Response, state ResultState, errs []error) ResultsHTTPBuilder {
return &testResults{
name: name,
resp: resp,
state: state,
errors: errs,
}
}
func (r *testResults) GetHTTPResponse() *http.Response {
return r.resp
}
func (r *testResults) GetErrors() []error {
return r.errors
}
func (r *testResults) GetName() string {
return r.name
}
func (r *testResults) GetResultState() ResultState {
if r.state == resultStateFailNow {
return ResultStateFail
}
return r.state
}
================================================
FILE: result_test.go
================================================
package cute
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
func TestResult(t *testing.T) {
var (
firstErr = errors.New("first_error")
secondErr = errors.New("second_error")
resp = &http.Response{
Status: "OK",
StatusCode: 200,
}
name = "Name"
testResults ResultsHTTPBuilder = &testResults{
name: name,
state: ResultStateBroken,
resp: resp,
errors: []error{
firstErr,
secondErr,
},
}
)
require.Equal(t, name, testResults.GetName())
require.Equal(t, resp, testResults.GetHTTPResponse())
require.Equal(t, []error{firstErr, secondErr}, testResults.GetErrors())
}
================================================
FILE: roundtripper.go
================================================
package cute
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/ozontech/allure-go/pkg/allure"
"moul.io/http2curl/v2"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/ozontech/cute/internal/utils"
)
func (it *Test) makeRequest(t internalT, req *http.Request) (*http.Response, []error) {
var (
delay = defaultDelayRepeat
countRepeat = 1
resp *http.Response
err error
scope = make([]error, 0)
)
if it.Request.Retry.Delay != 0 {
delay = it.Request.Retry.Delay
}
if it.Request.Retry.Count != 0 {
countRepeat = it.Request.Retry.Count
}
for i := 1; i <= countRepeat; i++ {
it.executeWithStep(t, it.createTitle(i, countRepeat, req), func(t T) []error {
resp, err = it.doRequest(t, req)
if err != nil {
if it.Request.Retry.Broken {
err = wrapBrokenError(err)
}
if it.Request.Retry.Optional {
err = wrapOptionalError(err)
}
return []error{err}
}
return nil
})
if err == nil {
break
}
scope = append(scope, err)
if i != countRepeat {
time.Sleep(delay)
}
}
return resp, scope
}
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, cuteErrors.NewCuteError("[Internal] Could not copy request", err)
}
resp, httpErr := it.httpClient.Do(req)
// if the timeout is triggered, we properly log the timeout error on allure and in traces
if errors.Is(httpErr, context.DeadlineExceeded) {
// Add information (method, host, curl) about request to Allure step
// should be after httpClient.Do and from resp.Request, because in roundTripper request may be changed
if addErr := it.addInformationRequest(t, req); addErr != nil {
// Ignore err return, because it's connected with test logic
it.Error(t, "Could not log information about request. error %v", addErr)
}
return nil, cuteErrors.NewEmptyAssertError(
"Request timeout",
fmt.Sprintf("expected request to be completed in %v, but was not", it.Expect.ExecuteTime))
}
// http client has case wheh it return response and error in one time
// we have to check this case
if resp == nil {
if httpErr != nil {
return nil, cuteErrors.NewCuteError("[HTTP] Could not do request", httpErr)
}
// if response is nil, we can't get information about request and response
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)
if err != nil {
it.Error(t, "Could not drain body from baseReq.Body. error %v", err)
// Ignore err return, because it's connected with test logic
}
// Add information (method, host, curl) about request to Allure step
// should be after httpClient.Do and from resp.Request, because in roundTripper request may be changed
if addErr := it.addInformationRequest(t, resp.Request); addErr != nil {
it.Error(t, "Could not log information about request. error %v", addErr)
// Ignore err return, because it's connected with test logic
}
if httpErr != nil {
return nil, cuteErrors.NewCuteError("[HTTP] Could not do request", httpErr)
}
// 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, "Could not log information about response. error %v", addErr)
}
if validErr := it.validateResponseCode(resp); validErr != nil {
return resp, validErr
}
return resp, nil
}
func (it *Test) validateResponseCode(resp *http.Response) error {
if it.Expect.Code != 0 && it.Expect.Code != resp.StatusCode {
return cuteErrors.NewAssertError(
"Assert response code",
fmt.Sprintf("Response code expect %v, but was %v", it.Expect.Code, resp.StatusCode),
resp.StatusCode,
it.Expect.Code)
}
return nil
}
func (it *Test) addInformationRequest(t T, req *http.Request) error {
var (
saveBody io.ReadCloser
err error
)
if it.RequestSanitizer != nil {
it.RequestSanitizer(req)
}
it.lastRequestURL = req.URL.String()
curl, err := http2curl.GetCurlCommand(req)
if err != nil {
return err
}
if c := curl.String(); len(c) <= 2048 {
it.Info(t, "[Request] "+c)
} else {
it.Info(t, "[Request] Do request")
}
// Do not change to JSONMarshaler
// In this case we can keep default for keep JSON, independence from JSONMarshaler
headers, err := utils.ToJSON(req.Header)
if err != nil {
return err
}
t.WithParameters(
allure.NewParameters(
"method", req.Method,
"host", req.Host,
"headers", headers,
"curl", curl.String(),
)...,
)
if req.Body != nil {
saveBody, req.Body, err = utils.DrainBody(req.Body)
if err != nil {
return err
}
body, err := utils.GetBody(saveBody)
if err != nil {
return err
}
if len(body) != 0 {
t.WithNewParameters("body", string(body))
}
}
return nil
}
func copyRequest(ctx context.Context, req *http.Request) (*http.Request, error) {
var (
err error
clone = req.Clone(ctx)
)
req.Body, clone.Body, err = utils.DrainBody(req.Body)
if err != nil {
return nil, err
}
return clone, nil
}
func (it *Test) addInformationResponse(t T, response *http.Response) error {
var (
saveBody io.ReadCloser
err error
)
if it.ResponseSanitizer != nil {
it.ResponseSanitizer(response)
}
headers, _ := utils.ToJSON(response.Header)
if headers != "" {
t.WithNewParameters("response_headers", headers)
}
t.WithNewParameters("response_code", fmt.Sprint(response.StatusCode))
it.Info(t, "[Response] Status: "+response.Status)
if response.Body == nil {
return nil
}
saveBody, response.Body, err = utils.DrainBody(response.Body)
// if could not get body from response, no add to allure
if err != nil {
return err
}
body, err := utils.GetBody(saveBody)
// if could not get body from response, no add to allure
if err != nil {
return err
}
// if body is empty - skip
if len(body) == 0 {
return nil
}
responseType := allure.Text
if _, ok := response.Header["Content-Type"]; ok {
if len(response.Header["Content-Type"]) > 0 {
if strings.Contains(response.Header["Content-Type"][0], "application/json") {
responseType = allure.JSON
} else {
responseType = allure.MimeType(response.Header["Content-Type"][0])
}
}
}
if responseType == allure.JSON {
body, _ = utils.PrettyJSON(body)
}
t.WithAttachments(allure.NewAttachment("response", responseType, body))
return nil
}
func (it *Test) createTitle(try, countRepeat int, req *http.Request) string {
toProcess := req
// We have to execute sanitizer hook because
// we need to log it and it can contain sensitive data
if it.RequestSanitizer != nil {
clone, err := copyRequest(req.Context(), req)
// ignore error, because we want to log request
// and it does not matter if we can copy request
if err == nil {
it.RequestSanitizer(clone)
toProcess = clone
}
}
title := toProcess.Method + " " + toProcess.URL.String()
if countRepeat == 1 {
return title
}
return fmt.Sprintf("[%v/%v] %v", try, countRepeat, title)
}
================================================
FILE: step.go
================================================
package cute
import (
"fmt"
"github.com/ozontech/allure-go/pkg/allure"
"github.com/ozontech/allure-go/pkg/framework/provider"
"github.com/ozontech/cute/errors"
)
func (it *Test) executeWithStep(t internalT, stepName string, execute func(t T) []error) []error {
var (
errs []error
)
// Add attempt indication in Allure if more than 1 attempt
if it.Retry.MaxAttempts != 1 {
stepName = fmt.Sprintf("[Attempt #%d] %v", it.Retry.currentCount, stepName)
}
t.WithNewStep(stepName, func(stepCtx provider.StepCtx) {
errs = execute(stepCtx)
processStepErrors(stepCtx, errs)
})
return errs
}
func processStepErrors(stepCtx provider.StepCtx, errs []error) {
var (
step = stepCtx.CurrentStep()
statuses = make([]allure.Status, 0)
)
if len(errs) == 0 {
return
}
for _, err := range errs {
currentStatus := allure.Failed
currentStep := step
if tErr, ok := err.(errors.OptionalError); ok {
if tErr.IsOptional() {
currentStatus = allure.Skipped
}
}
if tErr, ok := err.(errors.BrokenError); ok {
if tErr.IsBroken() {
currentStatus = allure.Broken
}
}
if tErr, ok := err.(errors.WithNameError); ok {
currentStep = allure.NewSimpleStep(tErr.GetName())
currentStep.Status = currentStatus
currentStep.WithParent(step)
}
if tErr, ok := err.(errors.WithFields); ok {
for k, v := range tErr.GetFields() {
if v == nil {
continue
}
currentStep.WithNewParameters(k, v)
}
}
if tErr, ok := err.(errors.WithAttachments); ok {
for _, v := range tErr.GetAttachments() {
if v == nil {
continue
}
currentStep.WithAttachments(allure.NewAttachment(v.Name, allure.MimeType(v.MimeType), v.Content))
}
}
statuses = append(statuses, currentStatus)
currentStep.WithAttachments(allure.NewAttachment("Error", allure.Text, []byte(err.Error())))
}
// If one error was not optional, parent step should be failed
for _, status := range statuses {
step.Status = status
if status == allure.Failed {
break
}
}
}
================================================
FILE: test.go
================================================
package cute
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"testing"
"time"
"github.com/ozontech/allure-go/pkg/framework/provider"
cuteErrors "github.com/ozontech/cute/errors"
"github.com/ozontech/cute/internal/utils"
)
const (
defaultExecuteTestTime = 10 * time.Second
defaultDelayRepeat = 1 * time.Second
)
var (
errorRequestMethodEmpty = errors.New("request method must be not empty")
errorRequestURLEmpty = errors.New("url request must be not empty")
)
// RequestSanitizerHook is a function used to modify the request URL
// before it is logged or attached to test reports (e.g., for hiding secrets).
type RequestSanitizerHook func(req *http.Request)
// ResponseSanitizerHook is a function used to modify the response
// before it is logged or attached to test reports (e.g., for hiding secrets).
type ResponseSanitizerHook func(resp *http.Response)
// Test is a main struct of test.
// You may field Request and Expect for create simple test
// Parallel can be used to control the parallelism of a Test
type Test struct {
httpClient *http.Client
jsonMarshaler JSONMarshaler
lastRequestURL string
Name string
Parallel bool
Retry *Retry
AllureStep *AllureStep
Middleware *Middleware
Request *Request
Expect *Expect
RequestSanitizer RequestSanitizerHook
ResponseSanitizer ResponseSanitizerHook
}
// Retry is a struct to control the retry of a whole single test (not only the request)
// The test will be retried up to MaxAttempts times
// The retries will only be executed if the test is having errors
// If the test is successful at any iteration between attempt 1 and MaxAttempts, the loop will break and return the result as successful
// The status of the test (success or fail) will be based on either the first attempt that is successful, or, if no attempt
// is successful, it will be based on the latest execution
// Delay is the number of seconds to wait before attempting to run the test again. It will only wait if Delay is set.
type Retry struct {
currentCount int
MaxAttempts int
Delay time.Duration
}
// Request is struct with HTTP request.
// You may use your *http.Request or create new with help Builders
type Request struct {
Base *http.Request
Builders []RequestBuilder
Retry *RequestRetryPolitic
}
// RequestRetryPolitic 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 RequestRetryPolitic struct {
Count int
Delay time.Duration
Optional bool
Broken bool
}
// RequestRepeatPolitic is struct for repeat politic
// Deprecated: use RequestRetryPolitic
type RequestRepeatPolitic struct {
Count int
Delay time.Duration
Optional bool
Broken bool
}
// Middleware is struct for executeInsideAllure something before or after test
type Middleware struct {
After []AfterExecute
AfterT []AfterExecuteT
Before []BeforeExecute
BeforeT []BeforeExecuteT
}
// AllureStep is struct with test name
type AllureStep struct {
Name string
}
// Expect is structs with validate politics for response
type Expect struct {
ExecuteTime time.Duration
Code int
JSONSchema *ExpectJSONSchema
AssertBody []AssertBody
AssertHeaders []AssertHeaders
AssertResponse []AssertResponse
AssertBodyT []AssertBodyT
AssertHeadersT []AssertHeadersT
AssertResponseT []AssertResponseT
}
// ExpectJSONSchema is structs with JSON politics for response
type ExpectJSONSchema struct {
String string
Byte []byte
File string
}
// Execute is common method for run test from builder
func (it *Test) Execute(ctx context.Context, t tProvider) ResultsHTTPBuilder {
var (
internalT allureProvider
res ResultsHTTPBuilder
)
if t == nil {
panic("could not start test without testing.T")
}
stepCtx, isStepCtx := t.(provider.StepCtx)
if isStepCtx {
return it.executeInsideStep(ctx, stepCtx)
}
tOriginal, ok := t.(*testing.T)
if ok {
tOriginal.Helper()
internalT = createAllureT(tOriginal)
}
allureT, ok := t.(provider.T)
if ok {
internalT = allureT
}
internalT.Run(it.Name, func(inT provider.T) {
if it.Parallel {
inT.Parallel()
}
res = it.executeInsideAllure(ctx, inT)
})
return res
}
func (it *Test) clearFields() {
it.AllureStep = new(AllureStep)
it.Middleware = new(Middleware)
it.Expect = new(Expect)
it.Request = new(Request)
it.Request.Retry = new(RequestRetryPolitic)
it.Expect.JSONSchema = new(ExpectJSONSchema)
}
func (it *Test) initEmptyFields() {
if it.httpClient == nil {
it.httpClient = http.DefaultClient
}
if it.jsonMarshaler == nil {
it.jsonMarshaler = jsonMarshaler{}
}
if it.AllureStep == nil {
it.AllureStep = new(AllureStep)
}
if it.Middleware == nil {
it.Middleware = new(Middleware)
}
if it.Expect == nil {
it.Expect = new(Expect)
}
if it.Request == nil {
it.Request = new(Request)
}
if it.Request.Retry == nil {
it.Request.Retry = new(RequestRetryPolitic)
}
if it.Expect.JSONSchema == nil {
it.Expect.JSONSchema = new(ExpectJSONSchema)
}
if it.Retry == nil {
it.Retry = &Retry{
// we set the default value to 1, because we count the first attempt as 1
MaxAttempts: 1,
currentCount: 1,
}
}
// We need to set the current count to 1 here, because we count the first attempt as 1
it.Retry.currentCount = 1
}
// executeInsideStep is method for start test with provider.StepCtx
// It's test inside the step
func (it *Test) executeInsideStep(ctx context.Context, t internalT) ResultsHTTPBuilder {
// Set empty fields in test
it.initEmptyFields()
// we don't want to defer the finish message, because it will be logged in processTestErrors
it.Info(t, "Start test")
return it.startRepeatableTest(ctx, t)
}
func (it *Test) executeInsideAllure(ctx context.Context, allureProvider allureProvider) ResultsHTTPBuilder {
// Set empty fields in test
it.initEmptyFields()
// we don't want to defer the finish message, because it will be logged in processTestErrors
it.Info(allureProvider, "Start test")
if it.AllureStep.Name != "" {
// Execute test inside step
return it.startTestInsideStep(ctx, allureProvider)
} else {
// Execute Test
return it.startRepeatableTest(ctx, allureProvider)
}
}
// startRepeatableTest is method for start test with repeatable execution
func (it *Test) startRepeatableTest(ctx context.Context, t internalT) ResultsHTTPBuilder {
var (
resp *http.Response
errs []error
resultState ResultState
)
for ; it.Retry.currentCount <= it.Retry.MaxAttempts; it.Retry.currentCount++ {
resp, errs = it.startTest(ctx, t)
resultState = it.processTestErrors(t, errs)
// we don't want to keep errors if we will retry test
// we have to return to user only errors from last try
// if the test is successful, we break the loop
if resultState == ResultStateSuccess {
break
}
// if we have a delay, we wait before the next attempt
// and we only wait if we are not at the last attempt
if it.Retry.currentCount != it.Retry.MaxAttempts && it.Retry.Delay != 0 {
it.Info(t, "The test had errors, retrying...")
time.Sleep(it.Retry.Delay)
}
}
switch resultState {
case ResultStateBroken:
t.BrokenNow()
it.Info(t, "Test broken")
case ResultStateFail:
t.Fail()
it.Error(t, "Test failed")
case resultStateFailNow:
t.FailNow()
it.Error(t, "Test failed")
case ResultStateSuccess:
it.Info(t, "Test finished successfully")
}
return newTestResult(it.Name, resp, resultState, errs)
}
func (it *Test) startTestInsideStep(ctx context.Context, t internalT) ResultsHTTPBuilder {
var (
result ResultsHTTPBuilder
)
t.WithNewStep(it.AllureStep.Name, func(stepCtx provider.StepCtx) {
it.Info(t, "Start step %v", it.AllureStep.Name)
defer it.Info(t, "Finish step %v", it.AllureStep.Name)
result = it.startRepeatableTest(ctx, stepCtx)
if result.GetResultState() == ResultStateFail {
stepCtx.Fail()
}
})
return result
}
// processTestErrors returns flag, which mean finish test or not.
// If test has only optional errors, than test will be success
// If test has broken errors, than test will be broken on allure
// If test has require errors, than test will be failed on allure
func (it *Test) processTestErrors(t internalT, errs []error) ResultState {
if len(errs) == 0 {
it.Info(t, "Test finished successfully")
return ResultStateSuccess
}
var (
countNotOptionalErrors = 0
state ResultState
)
for _, err := range errs {
message := fmt.Sprintf("error %v", err.Error())
if tErr, ok := err.(cuteErrors.OptionalError); ok {
if tErr.IsOptional() {
it.Info(t, "[OPTIONAL ERROR] %v", err.Error())
state = ResultStateSuccess
continue
}
}
if tErr, ok := err.(cuteErrors.BrokenError); ok {
if tErr.IsBroken() {
it.Error(t, "[BROKEN ERROR], error %v", err.Error())
state = ResultStateBroken
continue
}
}
if tErr, ok := err.(cuteErrors.RequireError); ok {
if tErr.IsRequire() {
state = resultStateFailNow
}
}
if tErr, ok := err.(cuteErrors.WithFields); ok {
actual := tErr.GetFields()[cuteErrors.ActualField]
expected := tErr.GetFields()[cuteErrors.ExpectedField]
if actual != nil || expected != nil {
message = fmt.Sprintf("%s\nActual %v\nExpected %v", message, actual, expected)
}
}
it.Error(t, message)
countNotOptionalErrors++
}
if countNotOptionalErrors != 0 {
state = ResultStateFail
it.Error(t, "Test finished with %v errors", countNotOptionalErrors)
}
return state
}
func (it *Test) startTest(ctx context.Context, t internalT) (*http.Response, []error) {
var (
resp *http.Response
err error
)
// CreateWithStep executeInsideAllure timer
if it.Expect.ExecuteTime == 0 {
it.Expect.ExecuteTime = defaultExecuteTestTime
}
ctx, cancel := context.WithTimeout(ctx, it.Expect.ExecuteTime)
defer cancel()
// CreateWithStep request
req, err := it.createRequest(ctx)
if err != nil {
return nil, []error{err}
}
// Execute Before
if errs := it.beforeTest(t, req); len(errs) > 0 {
return nil, errs
}
it.Info(t, "Start make request")
// Make request
resp, errs := it.makeRequest(t, req)
if len(errs) > 0 {
return resp, errs
}
it.Info(t, "Finish make request")
// Validate response body
errs = it.validateResponse(t, resp)
// Execute After
afterTestErrs := it.afterTest(t, resp, errs)
// Return results
errs = append(errs, afterTestErrs...)
if len(errs) > 0 {
return resp, errs
}
return resp, nil
}
func (it *Test) afterTest(t internalT, resp *http.Response, errs []error) []error {
if len(it.Middleware.After) == 0 && len(it.Middleware.AfterT) == 0 {
return nil
}
return it.executeWithStep(t, "After", func(t T) []error {
scope := make([]error, 0)
for _, execute := range it.Middleware.After {
if err := execute(resp, errs); err != nil {
scope = append(scope, err)
}
}
for _, executeSuite := range it.Middleware.AfterT {
if err := executeSuite(t, resp, errs); err != nil {
scope = append(scope, err)
}
}
return scope
})
}
func (it *Test) beforeTest(t internalT, req *http.Request) []error {
if len(it.Middleware.Before) == 0 && len(it.Middleware.BeforeT) == 0 {
return nil
}
return it.executeWithStep(t, "Before", func(t T) []error {
scope := make([]error, 0)
for _, execute := range it.Middleware.Before {
if err := execute(req); err != nil {
scope = append(scope, err)
}
}
for _, executeT := range it.Middleware.BeforeT {
if err := executeT(t, req); err != nil {
scope = append(scope, err)
}
}
return scope
})
}
// createRequest builds the final *http.Request to be executed by the test.
func (it *Test) createRequest(ctx context.Context) (*http.Request, error) {
var (
req = it.Request.Base
err error
)
if req == nil {
req, err = it.buildRequest(ctx)
if err != nil {
return nil, err
}
}
// Validate Request
if err := it.validateRequest(req); err != nil {
return nil, err
}
return req, nil
}
// buildRequest
// Priority for create body:
// 1. requestOptions.body <- low priority
// 2. requestOptions.bodyMarshal
// 3. requestOptions.forms and requestOptions.fileForms <- high priority.
func (it *Test) buildRequest(ctx context.Context) (*http.Request, error) {
var (
req *http.Request
err error
o = newRequestOptions()
)
// Set builder parameters
for _, builder := range it.Request.Builders {
builder(o)
}
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 = it.jsonMarshaler.Marshal(o.bodyMarshal)
if err != nil {
return nil, err
}
}
// Set multipart
if len(o.fileForms) != 0 || len(o.forms) != 0 {
var (
buffer = new(bytes.Buffer)
mp = multipart.NewWriter(buffer)
)
// set file forms
for fieldName, file := range o.fileForms {
err = createFormFile(mp, fieldName, file)
if err != nil {
return nil, err
}
}
// set forms
for fieldName, fieldBody := range o.forms {
err = createFormField(mp, fieldName, fieldBody)
if err != nil {
return nil, err
}
}
if err = mp.Close(); err != nil {
return nil, err
}
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, 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
}
func createFormFile(mp *multipart.Writer, fieldName string, file *File) error {
var (
data = file.Body
name = file.Name
)
// read file, if path is not empty
if len(file.Path) != 0 {
f, err := os.Open(file.Path)
if err != nil {
return err
}
data, err = io.ReadAll(f)
if err != nil {
return err
}
name = f.Name()
}
field, err := mp.CreateFormFile(fieldName, name)
if err != nil {
return fmt.Errorf("error when creating %v file form field, %w", fieldName, err)
}
_, err = field.Write(data)
if err != nil {
return fmt.Errorf("error when writing %v file form field, %w", fieldName, err)
}
return nil
}
func createFormField(mp *multipart.Writer, fieldName string, body []byte) error {
field, err := mp.CreateFormField(fieldName)
if err != nil {
return fmt.Errorf("error when creating %v form field, %w", fieldName, err)
}
_, err = field.Write(body)
if err != nil {
return fmt.Errorf("error when writing %v form field, %w", fieldName, err)
}
return nil
}
func (it *Test) validateRequest(req *http.Request) error {
if req.URL == nil {
return errorRequestURLEmpty
}
if req.Method == "" {
return errorRequestMethodEmpty
}
return nil
}
func (it *Test) validateResponse(t internalT, resp *http.Response) []error {
var (
err error
saveBody io.ReadCloser
scope = make([]error, 0)
)
// Execute asserts for headers
if errs := it.assertHeaders(t, resp.Header); len(errs) > 0 {
scope = append(scope, errs...)
}
// Prepare body for validate
if resp.Body == nil {
// todo create errors if body is empty, but assert is not empty
return scope
}
saveBody, resp.Body, err = utils.DrainBody(resp.Body)
if err != nil {
return append(scope, fmt.Errorf("could not drain response body. error %w", err))
}
body, err := utils.GetBody(saveBody)
if err != nil {
return append(scope, fmt.Errorf("could not get response body. error %w", err))
}
// Execute asserts for body
if errs := it.assertBody(t, body); len(errs) > 0 {
// add assert
scope = append(scope, errs...)
}
// Validate response by json schema
if errs := it.validateJSONSchema(t, body); len(errs) > 0 {
scope = append(scope, errs...)
}
// Execute asserts for response body
if errs := it.assertResponse(t, resp); len(errs) > 0 {
scope = append(scope, errs...)
}
return scope
}
================================================
FILE: test_test.go
================================================
package cute
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/ozontech/allure-go/pkg/framework/core/common"
"github.com/stretchr/testify/require"
"github.com/ozontech/cute/internal/utils"
)
func TestCreateRequest(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://go.com", nil)
require.NoError(t, err)
ht := &Test{
Request: &Request{
Base: req,
},
}
resReq, err := ht.createRequest(context.Background())
require.NoError(t, err)
require.Equal(t, req, resReq)
}
func TestCreateRequestBuilder(t *testing.T) {
var (
body = []byte("HELLO")
headers = map[string][]string{
"Good": []string{"Day"},
"Mad": []string{"Max"},
}
url = "http://go.com"
)
req, err := http.NewRequest(http.MethodGet, url, io.NopCloser(bytes.NewReader(body)))
require.NoError(t, err)
req.Header = headers
ht := &Test{
Request: &Request{
Builders: []RequestBuilder{
WithURI(url),
WithMethod("GET"),
WithHeaders(headers),
WithBody(body),
},
},
}
resReq, err := ht.createRequest(context.Background())
require.NoError(t, err)
require.Equal(t, req, resReq)
}
func TestCreateRequestBuilder_MarshalBody(t *testing.T) {
var (
str = struct {
name string
}{
"hello",
}
)
ht := &Test{
jsonMarshaler: jsonMarshaler{},
Request: &Request{
Builders: []RequestBuilder{
WithMarshalBody(str),
},
},
}
resReq, err := ht.createRequest(context.Background())
require.NoError(t, err)
getBody, err := utils.GetBody(resReq.Body)
require.NoError(t, err)
require.NotEmpty(t, getBody)
}
func TestValidateRequestEmptyUrl(t *testing.T) {
ht := &Test{}
require.Error(t, ht.validateRequest(&http.Request{}))
}
func TestValidateRequestEmptyMethod(t *testing.T) {
ht := &Test{}
u, _ := url.Parse("https://go.com")
require.Error(t, ht.validateRequest(&http.Request{
URL: u,
}))
}
func TestValidateResponseEmpty(t *testing.T) {
ht := &Test{
Expect: new(Expect),
}
temp := common.NewT(t)
errs := ht.validateResponse(temp, &http.Response{})
require.Empty(t, errs)
}
func TestValidateResponseCode(t *testing.T) {
ht := &Test{
Expect: &Expect{Code: 200},
}
temp := common.NewT(t)
errs := ht.validateResponse(temp, &http.Response{StatusCode: http.StatusOK})
require.Empty(t, errs)
}
func TestValidateResponseWithErrors(t *testing.T) {
var (
ht = &Test{
Expect: &Expect{
Code: 200,
JSONSchema: new(ExpectJSONSchema),
AssertHeaders: []AssertHeaders{
func(headers http.Header) error {
return errors.New("two error")
},
},
AssertResponse: []AssertResponse{
func(response *http.Response) error {
if response.StatusCode != http.StatusOK || len(response.Header["auth"]) == 0 { //nolint
return errors.New("bad response")
}
return nil
},
},
},
}
reader = bytes.NewReader([]byte(`{"a":"ab","b":"bc"}`))
temp = createAllureT(t)
resp = &http.Response{
StatusCode: http.StatusBadRequest,
Header: map[string][]string{
"key": []string{"value"},
"auth": []string{"sometoken"},
},
Body: io.NopCloser(reader),
}
)
ht.initEmptyFields()
errs := ht.validateResponse(temp, resp)
require.Len(t, errs, 2)
}
type mockRoundTripper struct{}
func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Request: req,
Body: io.NopCloser(strings.NewReader("mock response")),
}, nil
}
func TestSanitizeURLHook(t *testing.T) {
client := &http.Client{
Transport: &mockRoundTripper{},
}
test := &Test{
httpClient: client,
Retry: &Retry{
currentCount: 0,
MaxAttempts: 0,
Delay: 0,
},
Request: &Request{
Builders: []RequestBuilder{
WithMethod(http.MethodGet),
WithURI("http://localhost/api?key=123"),
},
Retry: &RequestRetryPolitic{
Count: 1,
Delay: 2,
},
},
RequestSanitizer: sanitizeKeyParam("****"),
}
req, err := test.createRequest(context.Background())
require.NoError(t, err)
require.NotNil(t, req)
newT := createAllureT(t)
err = test.addInformationRequest(newT, req)
require.NoError(t, err)
decodedQuery, err := url.QueryUnescape(req.URL.RawQuery)
require.NoError(t, err)
require.Equal(t, "key=****", decodedQuery)
}
func TestSanitizeURL_LastRequestURL(t *testing.T) {
client := &http.Client{
Transport: &mockRoundTripper{},
}
test := &Test{
httpClient: client,
Request: &Request{
Builders: []RequestBuilder{
WithMethod(http.MethodGet),
WithURI("http://localhost/api?key=123"),
},
},
RequestSanitizer: sanitizeKeyParam("****"),
}
allureT := createAllureT(t)
test.Execute(context.Background(), allureT)
decodedURL, err := url.QueryUnescape(test.lastRequestURL)
require.NoError(t, err)
require.Contains(t, decodedURL, "key=****", "Expected masked key in lastRequestURL")
}
func sanitizeKeyParam(mask string) RequestSanitizerHook {
return func(req *http.Request) {
q := req.URL.Query()
q.Set("key", mask)
req.URL.RawQuery = q.Encode()
}
}
func TestSanitizeURL_RealRequest(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
t.Logf("Server received URL: %s, Body: %s", r.URL.String(), string(body))
require.Contains(t, r.URL.String(), "key=123", "Sanitizer must not change real request")
w.WriteHeader(200)
}))
defer ts.Close()
client := &http.Client{}
test := &Test{
httpClient: client,
Request: &Request{
Builders: []RequestBuilder{
WithMethod(http.MethodGet),
WithURI(ts.URL + "/api?key=123"),
},
},
RequestSanitizer: sanitizeKeyParam("****"),
}
allureT := createAllureT(t)
test.Execute(context.Background(), allureT)
}