mirror of
https://github.com/peridotbuild/peridot.git
synced 2024-09-24 11:04:10 +00:00
govendor
This commit is contained in:
parent
18b15013eb
commit
d9a69cdfa2
13
deps.bzl
13
deps.bzl
@ -1265,6 +1265,12 @@ def go_dependencies():
|
|||||||
sum = "h1:JyZjdMQu9Kl/wLXe9xA6s1X+tF6BWsQPFGJMEeCfWzE=",
|
sum = "h1:JyZjdMQu9Kl/wLXe9xA6s1X+tF6BWsQPFGJMEeCfWzE=",
|
||||||
version = "v0.2.0",
|
version = "v0.2.0",
|
||||||
)
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_jarcoal_httpmock",
|
||||||
|
importpath = "github.com/jarcoal/httpmock",
|
||||||
|
sum = "h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=",
|
||||||
|
version = "v1.3.1",
|
||||||
|
)
|
||||||
|
|
||||||
go_repository(
|
go_repository(
|
||||||
name = "com_github_jaschaephraim_lrserver",
|
name = "com_github_jaschaephraim_lrserver",
|
||||||
@ -1577,6 +1583,13 @@ def go_dependencies():
|
|||||||
sum = "h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=",
|
sum = "h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=",
|
||||||
version = "v1.0.4",
|
version = "v1.0.4",
|
||||||
)
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_maxatome_go_testdeep",
|
||||||
|
importpath = "github.com/maxatome/go-testdeep",
|
||||||
|
sum = "h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=",
|
||||||
|
version = "v1.12.0",
|
||||||
|
)
|
||||||
|
|
||||||
go_repository(
|
go_repository(
|
||||||
name = "com_github_microsoft_go_winio",
|
name = "com_github_microsoft_go_winio",
|
||||||
importpath = "github.com/Microsoft/go-winio",
|
importpath = "github.com/Microsoft/go-winio",
|
||||||
|
1
go.mod
1
go.mod
@ -23,6 +23,7 @@ require (
|
|||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1
|
||||||
|
github.com/jarcoal/httpmock v1.3.1
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.16.0
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/sassoftware/go-rpmutils v0.2.0
|
github.com/sassoftware/go-rpmutils v0.2.0
|
||||||
|
3
go.sum
3
go.sum
@ -777,6 +777,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
|||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
||||||
|
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||||
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 h1:24NdJ5N6gtrcoeS4JwLMeruKFmg20QdF/5UnX5S/j18=
|
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71 h1:24NdJ5N6gtrcoeS4JwLMeruKFmg20QdF/5UnX5S/j18=
|
||||||
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g=
|
github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
@ -867,6 +869,7 @@ github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
|
|||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
|
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
22
vendor/github.com/jarcoal/httpmock/.gitignore
generated
vendored
Normal file
22
vendor/github.com/jarcoal/httpmock/.gitignore
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
18
vendor/github.com/jarcoal/httpmock/BUILD
generated
vendored
Normal file
18
vendor/github.com/jarcoal/httpmock/BUILD
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "httpmock",
|
||||||
|
srcs = [
|
||||||
|
"any.go",
|
||||||
|
"doc.go",
|
||||||
|
"env.go",
|
||||||
|
"file.go",
|
||||||
|
"match.go",
|
||||||
|
"response.go",
|
||||||
|
"transport.go",
|
||||||
|
],
|
||||||
|
importmap = "go.resf.org/peridot/vendor/github.com/jarcoal/httpmock",
|
||||||
|
importpath = "github.com/jarcoal/httpmock",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//vendor/github.com/jarcoal/httpmock/internal"],
|
||||||
|
)
|
20
vendor/github.com/jarcoal/httpmock/LICENSE
generated
vendored
Normal file
20
vendor/github.com/jarcoal/httpmock/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Jared Morse
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
257
vendor/github.com/jarcoal/httpmock/README.md
generated
vendored
Normal file
257
vendor/github.com/jarcoal/httpmock/README.md
generated
vendored
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# httpmock [![Build Status](https://github.com/jarcoal/httpmock/workflows/Build/badge.svg?branch=v1)](https://github.com/jarcoal/httpmock/actions?query=workflow%3ABuild) [![Coverage Status](https://coveralls.io/repos/github/jarcoal/httpmock/badge.svg?branch=v1)](https://coveralls.io/github/jarcoal/httpmock?branch=v1) [![GoDoc](https://godoc.org/github.com/jarcoal/httpmock?status.svg)](https://godoc.org/github.com/jarcoal/httpmock) [![Version](https://img.shields.io/github/tag/jarcoal/httpmock.svg)](https://github.com/jarcoal/httpmock/releases) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go/#testing)
|
||||||
|
|
||||||
|
Easy mocking of http responses from external resources.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Currently supports Go 1.13 to 1.21 and is regularly tested against tip.
|
||||||
|
|
||||||
|
`v1` branch has to be used instead of `master`.
|
||||||
|
|
||||||
|
In your go files, simply use:
|
||||||
|
```go
|
||||||
|
import "github.com/jarcoal/httpmock"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then next `go mod tidy` or `go test` invocation will automatically
|
||||||
|
populate your `go.mod` with the latest httpmock release, now
|
||||||
|
[![Version](https://img.shields.io/github/tag/jarcoal/httpmock.svg)](https://github.com/jarcoal/httpmock/releases).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Simple Example:
|
||||||
|
```go
|
||||||
|
func TestFetchArticles(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
// Exact URL match
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
|
||||||
|
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
|
||||||
|
|
||||||
|
// Regexp match (could use httpmock.RegisterRegexpResponder instead)
|
||||||
|
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
|
||||||
|
httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))
|
||||||
|
|
||||||
|
// do stuff that makes a request to articles
|
||||||
|
...
|
||||||
|
|
||||||
|
// get count info
|
||||||
|
httpmock.GetTotalCallCount()
|
||||||
|
|
||||||
|
// get the amount of calls for the registered responder
|
||||||
|
info := httpmock.GetCallCountInfo()
|
||||||
|
info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles
|
||||||
|
info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12
|
||||||
|
info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/<any-number>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Example:
|
||||||
|
```go
|
||||||
|
func TestFetchArticles(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
// our database of articles
|
||||||
|
articles := make([]map[string]interface{}, 0)
|
||||||
|
|
||||||
|
// mock to list out the articles
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := httpmock.NewJsonResponse(200, articles)
|
||||||
|
if err != nil {
|
||||||
|
return httpmock.NewStringResponse(500, ""), nil
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// return an article related to the request with the help of regexp submatch (\d+)
|
||||||
|
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`,
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
// Get ID from request
|
||||||
|
id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
|
||||||
|
return httpmock.NewJsonResponse(200, map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"name": "My Great Article",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock to add a new article
|
||||||
|
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
article := make(map[string]interface{})
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
|
||||||
|
return httpmock.NewStringResponse(400, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
articles = append(articles, article)
|
||||||
|
|
||||||
|
resp, err := httpmock.NewJsonResponse(200, article)
|
||||||
|
if err != nil {
|
||||||
|
return httpmock.NewStringResponse(500, ""), nil
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock to add a specific article, send a Bad Request response
|
||||||
|
// when the request body contains `"type":"toy"`
|
||||||
|
httpmock.RegisterMatcherResponder("POST", "https://api.mybiz.com/articles",
|
||||||
|
httpmock.BodyContainsString(`"type":"toy"`),
|
||||||
|
httpmock.NewStringResponder(400, `{"reason":"Invalid article type"}`))
|
||||||
|
|
||||||
|
// do stuff that adds and checks articles
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Algorithm
|
||||||
|
|
||||||
|
When `GET http://example.tld/some/path?b=12&a=foo&a=bar` request is
|
||||||
|
caught, all standard responders are checked against the following URL
|
||||||
|
or paths, the first match stops the search:
|
||||||
|
|
||||||
|
1. `http://example.tld/some/path?b=12&a=foo&a=bar` (original URL)
|
||||||
|
1. `http://example.tld/some/path?a=bar&a=foo&b=12` (sorted query params)
|
||||||
|
1. `http://example.tld/some/path` (without query params)
|
||||||
|
1. `/some/path?b=12&a=foo&a=bar` (original URL without scheme and host)
|
||||||
|
1. `/some/path?a=bar&a=foo&b=12` (same, but sorted query params)
|
||||||
|
1. `/some/path` (path only)
|
||||||
|
|
||||||
|
If no standard responder matched, the regexp responders are checked,
|
||||||
|
in the same order, the first match stops the search.
|
||||||
|
|
||||||
|
|
||||||
|
### [go-testdeep](https://go-testdeep.zetta.rocks/) + [tdsuite](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsuite) example:
|
||||||
|
```go
|
||||||
|
// article_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/maxatome/go-testdeep/helpers/tdsuite"
|
||||||
|
"github.com/maxatome/go-testdeep/td"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MySuite struct{}
|
||||||
|
|
||||||
|
func (s *MySuite) Setup(t *td.T) error {
|
||||||
|
// block all HTTP requests
|
||||||
|
httpmock.Activate()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) PostTest(t *td.T, testName string) error {
|
||||||
|
// remove any mocks after each test
|
||||||
|
httpmock.Reset()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) Destroy(t *td.T) error {
|
||||||
|
httpmock.DeactivateAndReset()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMySuite(t *testing.T) {
|
||||||
|
tdsuite.Run(t, &MySuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestArticles(assert, require *td.T) {
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
|
||||||
|
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
|
||||||
|
|
||||||
|
// do stuff that makes a request to articles.json
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### [Ginkgo](https://onsi.github.io/ginkgo/) example:
|
||||||
|
```go
|
||||||
|
// article_suite_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
)
|
||||||
|
// ...
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
// block all HTTP requests
|
||||||
|
httpmock.Activate()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = BeforeEach(func() {
|
||||||
|
// remove any mocks
|
||||||
|
httpmock.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
httpmock.DeactivateAndReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// article_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Articles", func() {
|
||||||
|
It("returns a list of articles", func() {
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
|
||||||
|
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
|
||||||
|
|
||||||
|
// do stuff that makes a request to articles.json
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Ginkgo](https://onsi.github.io/ginkgo/) + [Resty](https://github.com/go-resty/resty) Example:
|
||||||
|
```go
|
||||||
|
// article_suite_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/go-resty/resty"
|
||||||
|
)
|
||||||
|
// ...
|
||||||
|
var _ = BeforeSuite(func() {
|
||||||
|
// block all HTTP requests
|
||||||
|
httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = BeforeEach(func() {
|
||||||
|
// remove any mocks
|
||||||
|
httpmock.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
httpmock.DeactivateAndReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// article_test.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
// ...
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/go-resty/resty"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Articles", func() {
|
||||||
|
It("returns a list of articles", func() {
|
||||||
|
fixture := `{"status":{"message": "Your message", "code": 200}}`
|
||||||
|
responder := httpmock.NewStringResponder(200, fixture)
|
||||||
|
fakeUrl := "https://api.mybiz.com/articles.json"
|
||||||
|
httpmock.RegisterResponder("GET", fakeUrl, responder)
|
||||||
|
|
||||||
|
// fetch the article into struct
|
||||||
|
articleObject := &models.Article{}
|
||||||
|
_, err := resty.R().SetResult(articleObject).Get(fakeUrl)
|
||||||
|
|
||||||
|
// do stuff with the article object ...
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
6
vendor/github.com/jarcoal/httpmock/any.go
generated
vendored
Normal file
6
vendor/github.com/jarcoal/httpmock/any.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package httpmock
|
||||||
|
|
||||||
|
type any = interface{}
|
83
vendor/github.com/jarcoal/httpmock/doc.go
generated
vendored
Normal file
83
vendor/github.com/jarcoal/httpmock/doc.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
Package httpmock provides tools for mocking HTTP responses.
|
||||||
|
|
||||||
|
Simple Example:
|
||||||
|
|
||||||
|
func TestFetchArticles(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
// Exact URL match
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
|
||||||
|
httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
|
||||||
|
|
||||||
|
// Regexp match (could use httpmock.RegisterRegexpResponder instead)
|
||||||
|
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
|
||||||
|
httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))
|
||||||
|
|
||||||
|
// do stuff that makes a request to articles
|
||||||
|
|
||||||
|
// get count info
|
||||||
|
httpmock.GetTotalCallCount()
|
||||||
|
|
||||||
|
// get the amount of calls for the registered responder
|
||||||
|
info := httpmock.GetCallCountInfo()
|
||||||
|
info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles
|
||||||
|
info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12
|
||||||
|
info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/<any-number>
|
||||||
|
}
|
||||||
|
|
||||||
|
Advanced Example:
|
||||||
|
|
||||||
|
func TestFetchArticles(t *testing.T) {
|
||||||
|
httpmock.Activate()
|
||||||
|
defer httpmock.DeactivateAndReset()
|
||||||
|
|
||||||
|
// our database of articles
|
||||||
|
articles := make([]map[string]any, 0)
|
||||||
|
|
||||||
|
// mock to list out the articles
|
||||||
|
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := httpmock.NewJsonResponse(200, articles)
|
||||||
|
if err != nil {
|
||||||
|
return httpmock.NewStringResponse(500, ""), nil
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// return an article related to the request with the help of regexp submatch (\d+)
|
||||||
|
httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`,
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
// Get ID from request
|
||||||
|
id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
|
||||||
|
return httpmock.NewJsonResponse(200, map[string]any{
|
||||||
|
"id": id,
|
||||||
|
"name": "My Great Article",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// mock to add a new article
|
||||||
|
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
|
||||||
|
func(req *http.Request) (*http.Response, error) {
|
||||||
|
article := make(map[string]any)
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
|
||||||
|
return httpmock.NewStringResponse(400, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
articles = append(articles, article)
|
||||||
|
|
||||||
|
resp, err := httpmock.NewJsonResponse(200, article)
|
||||||
|
if err != nil {
|
||||||
|
return httpmock.NewStringResponse(500, ""), nil
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// do stuff that adds and checks articles
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package httpmock
|
13
vendor/github.com/jarcoal/httpmock/env.go
generated
vendored
Normal file
13
vendor/github.com/jarcoal/httpmock/env.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package httpmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envVarName = "GONOMOCKS"
|
||||||
|
|
||||||
|
// Disabled allows to test whether httpmock is enabled or not. It
|
||||||
|
// depends on GONOMOCKS environment variable.
|
||||||
|
func Disabled() bool {
|
||||||
|
return os.Getenv(envVarName) != ""
|
||||||
|
}
|
63
vendor/github.com/jarcoal/httpmock/file.go
generated
vendored
Normal file
63
vendor/github.com/jarcoal/httpmock/file.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package httpmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil" //nolint: staticcheck
|
||||||
|
)
|
||||||
|
|
||||||
|
// File is a file name. The contents of this file is loaded on demand
|
||||||
|
// by the following methods.
|
||||||
|
//
|
||||||
|
// Note that:
|
||||||
|
//
|
||||||
|
// file := httpmock.File("file.txt")
|
||||||
|
// fmt.Printf("file: %s\n", file)
|
||||||
|
//
|
||||||
|
// prints the content of file "file.txt" as [File.String] method is used.
|
||||||
|
//
|
||||||
|
// To print the file name, and not its content, simply do:
|
||||||
|
//
|
||||||
|
// file := httpmock.File("file.txt")
|
||||||
|
// fmt.Printf("file: %s\n", string(file))
|
||||||
|
type File string
|
||||||
|
|
||||||
|
// MarshalJSON implements [encoding/json.Marshaler].
|
||||||
|
//
|
||||||
|
// Useful to be used in conjunction with [NewJsonResponse] or
|
||||||
|
// [NewJsonResponder] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewJsonResponder(200, httpmock.File("body.json"))
|
||||||
|
func (f File) MarshalJSON() ([]byte, error) {
|
||||||
|
return f.bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) bytes() ([]byte, error) {
|
||||||
|
return ioutil.ReadFile(string(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the content of file as a []byte. If an error occurs
|
||||||
|
// during the opening or reading of the file, it panics.
|
||||||
|
//
|
||||||
|
// Useful to be used in conjunction with [NewBytesResponse] or
|
||||||
|
// [NewBytesResponder] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes())
|
||||||
|
func (f File) Bytes() []byte {
|
||||||
|
b, err := f.bytes()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Cannot read %s: %s", string(f), err))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements [fmt.Stringer] and returns the content of file as
|
||||||
|
// a string. If an error occurs during the opening or reading of the
|
||||||
|
// file, it panics.
|
||||||
|
//
|
||||||
|
// Useful to be used in conjunction with [NewStringResponse] or
|
||||||
|
// [NewStringResponder] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewStringResponder(200, httpmock.File("body.txt").String())
|
||||||
|
func (f File) String() string {
|
||||||
|
return string(f.Bytes())
|
||||||
|
}
|
17
vendor/github.com/jarcoal/httpmock/internal/BUILD
generated
vendored
Normal file
17
vendor/github.com/jarcoal/httpmock/internal/BUILD
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "internal",
|
||||||
|
srcs = [
|
||||||
|
"error.go",
|
||||||
|
"route_key.go",
|
||||||
|
"stack_tracer.go",
|
||||||
|
"submatches.go",
|
||||||
|
],
|
||||||
|
importmap = "go.resf.org/peridot/vendor/github.com/jarcoal/httpmock/internal",
|
||||||
|
importpath = "github.com/jarcoal/httpmock/internal",
|
||||||
|
visibility = [
|
||||||
|
"//third_party:__subpackages__",
|
||||||
|
"//vendor/github.com/jarcoal/httpmock:__subpackages__",
|
||||||
|
],
|
||||||
|
)
|
41
vendor/github.com/jarcoal/httpmock/internal/error.go
generated
vendored
Normal file
41
vendor/github.com/jarcoal/httpmock/internal/error.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoResponderFound is returned when no responders are found for a
|
||||||
|
// given HTTP method and URL.
|
||||||
|
var NoResponderFound = errors.New("no responder found") // nolint: revive
|
||||||
|
|
||||||
|
// ErrorNoResponderFoundMistake encapsulates a NoResponderFound
|
||||||
|
// error probably due to a user error on the method or URL path.
|
||||||
|
type ErrorNoResponderFoundMistake struct {
|
||||||
|
Kind string // "method", "URL" or "matcher"
|
||||||
|
Orig string // original wrong method/URL, without any matching responder
|
||||||
|
Suggested string // suggested method/URL with a matching responder
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*ErrorNoResponderFoundMistake)(nil)
|
||||||
|
|
||||||
|
// Unwrap implements the interface needed by errors.Unwrap.
|
||||||
|
func (e *ErrorNoResponderFoundMistake) Unwrap() error {
|
||||||
|
return NoResponderFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error interface.
|
||||||
|
func (e *ErrorNoResponderFoundMistake) Error() string {
|
||||||
|
if e.Kind == "matcher" {
|
||||||
|
return fmt.Sprintf("%s despite %s",
|
||||||
|
NoResponderFound,
|
||||||
|
e.Suggested,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%[1]s for %[2]s %[3]q, but one matches %[2]s %[4]q",
|
||||||
|
NoResponderFound,
|
||||||
|
e.Kind,
|
||||||
|
e.Orig,
|
||||||
|
e.Suggested,
|
||||||
|
)
|
||||||
|
}
|
15
vendor/github.com/jarcoal/httpmock/internal/route_key.go
generated
vendored
Normal file
15
vendor/github.com/jarcoal/httpmock/internal/route_key.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type RouteKey struct {
|
||||||
|
Method string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NoResponder RouteKey
|
||||||
|
|
||||||
|
func (r RouteKey) String() string {
|
||||||
|
if r == NoResponder {
|
||||||
|
return "NO_RESPONDER"
|
||||||
|
}
|
||||||
|
return r.Method + " " + r.URL
|
||||||
|
}
|
91
vendor/github.com/jarcoal/httpmock/internal/stack_tracer.go
generated
vendored
Normal file
91
vendor/github.com/jarcoal/httpmock/internal/stack_tracer.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StackTracer struct {
|
||||||
|
CustomFn func(...interface{})
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error interface.
|
||||||
|
func (s StackTracer) Error() string {
|
||||||
|
if s.Err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements the interface needed by errors.Unwrap.
|
||||||
|
func (s StackTracer) Unwrap() error {
|
||||||
|
return s.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckStackTracer checks for specific error returned by
|
||||||
|
// NewNotFoundResponder function or Trace Responder method.
|
||||||
|
func CheckStackTracer(req *http.Request, err error) error {
|
||||||
|
if nf, ok := err.(StackTracer); ok {
|
||||||
|
if nf.CustomFn != nil {
|
||||||
|
pc := make([]uintptr, 128)
|
||||||
|
npc := runtime.Callers(2, pc)
|
||||||
|
pc = pc[:npc]
|
||||||
|
|
||||||
|
var mesg bytes.Buffer
|
||||||
|
var netHTTPBegin, netHTTPEnd bool
|
||||||
|
|
||||||
|
// Start recording at first net/http call if any...
|
||||||
|
for {
|
||||||
|
frames := runtime.CallersFrames(pc)
|
||||||
|
|
||||||
|
var lastFn string
|
||||||
|
for {
|
||||||
|
frame, more := frames.Next()
|
||||||
|
|
||||||
|
if !netHTTPEnd {
|
||||||
|
if netHTTPBegin {
|
||||||
|
netHTTPEnd = !strings.HasPrefix(frame.Function, "net/http.")
|
||||||
|
} else {
|
||||||
|
netHTTPBegin = strings.HasPrefix(frame.Function, "net/http.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if netHTTPEnd {
|
||||||
|
if lastFn != "" {
|
||||||
|
if mesg.Len() == 0 {
|
||||||
|
if nf.Err != nil {
|
||||||
|
mesg.WriteString(nf.Err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&mesg, "%s %s", req.Method, req.URL)
|
||||||
|
}
|
||||||
|
mesg.WriteString("\nCalled from ")
|
||||||
|
} else {
|
||||||
|
mesg.WriteString("\n ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&mesg, "%s()\n at %s:%d", lastFn, frame.File, frame.Line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastFn = frame.Function
|
||||||
|
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one net/http frame found
|
||||||
|
if mesg.Len() > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
netHTTPEnd = true // retry without looking at net/http frames
|
||||||
|
}
|
||||||
|
|
||||||
|
nf.CustomFn(mesg.String())
|
||||||
|
}
|
||||||
|
err = nf.Err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
22
vendor/github.com/jarcoal/httpmock/internal/submatches.go
generated
vendored
Normal file
22
vendor/github.com/jarcoal/httpmock/internal/submatches.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type submatchesKeyType struct{}
|
||||||
|
|
||||||
|
var submatchesKey submatchesKeyType
|
||||||
|
|
||||||
|
func SetSubmatches(req *http.Request, submatches []string) *http.Request {
|
||||||
|
if len(submatches) > 0 {
|
||||||
|
return req.WithContext(context.WithValue(req.Context(), submatchesKey, submatches))
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubmatches(req *http.Request) []string {
|
||||||
|
sm, _ := req.Context().Value(submatchesKey).([]string)
|
||||||
|
return sm
|
||||||
|
}
|
519
vendor/github.com/jarcoal/httpmock/match.go
generated
vendored
Normal file
519
vendor/github.com/jarcoal/httpmock/match.go
generated
vendored
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
package httpmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil" //nolint: staticcheck
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/jarcoal/httpmock/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ignorePackages = map[string]bool{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
IgnoreMatcherHelper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreMatcherHelper should be called by external helpers building
|
||||||
|
// [Matcher], typically in an init() function, to avoid they appear in
|
||||||
|
// the autogenerated [Matcher] names.
|
||||||
|
func IgnoreMatcherHelper(skip ...int) {
|
||||||
|
sk := 2
|
||||||
|
if len(skip) > 0 {
|
||||||
|
sk += skip[0]
|
||||||
|
}
|
||||||
|
if pkg := getPackage(sk); pkg != "" {
|
||||||
|
ignorePackages[pkg] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from github.com/maxatome/go-testdeep/internal/trace.getPackage.
|
||||||
|
func getPackage(skip int) string {
|
||||||
|
if pc, _, _, ok := runtime.Caller(skip); ok {
|
||||||
|
if fn := runtime.FuncForPC(pc); fn != nil {
|
||||||
|
return extractPackage(fn.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractPackage extracts package part from a fully qualified function name:
|
||||||
|
//
|
||||||
|
// "foo/bar/test.fn" → "foo/bar/test"
|
||||||
|
// "foo/bar/test.X.fn" → "foo/bar/test"
|
||||||
|
// "foo/bar/test.(*X).fn" → "foo/bar/test"
|
||||||
|
// "foo/bar/test.(*X).fn.func1" → "foo/bar/test"
|
||||||
|
// "weird" → ""
|
||||||
|
//
|
||||||
|
// Derived from github.com/maxatome/go-testdeep/internal/trace.SplitPackageFunc.
|
||||||
|
func extractPackage(fn string) string {
|
||||||
|
sp := strings.LastIndexByte(fn, '/')
|
||||||
|
if sp < 0 {
|
||||||
|
sp = 0 // std package
|
||||||
|
}
|
||||||
|
|
||||||
|
dp := strings.IndexByte(fn[sp:], '.')
|
||||||
|
if dp < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn[:sp+dp]
|
||||||
|
}
|
||||||
|
|
||||||
|
// calledFrom returns a string like "@PKG.FUNC() FILE:LINE".
|
||||||
|
func calledFrom(skip int) string {
|
||||||
|
pc := make([]uintptr, 128)
|
||||||
|
npc := runtime.Callers(skip+1, pc)
|
||||||
|
pc = pc[:npc]
|
||||||
|
|
||||||
|
frames := runtime.CallersFrames(pc)
|
||||||
|
|
||||||
|
var lastFrame runtime.Frame
|
||||||
|
|
||||||
|
for {
|
||||||
|
frame, more := frames.Next()
|
||||||
|
|
||||||
|
// If testing package is encountered, it is too late
|
||||||
|
if strings.HasPrefix(frame.Function, "testing.") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastFrame = frame
|
||||||
|
// Stop if httpmock is not the caller
|
||||||
|
if !ignorePackages[extractPackage(frame.Function)] || !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastFrame.Line == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(" @%s() %s:%d",
|
||||||
|
lastFrame.Function, lastFrame.File, lastFrame.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc type is the function to use to check a [Matcher]
|
||||||
|
// matches an incoming request. When httpmock calls a function of this
|
||||||
|
// type, it is guaranteed req.Body is never nil. If req.Body is nil in
|
||||||
|
// the original request, it is temporarily replaced by an instance
|
||||||
|
// returning always [io.EOF] for each Read() call, during the call.
|
||||||
|
type MatcherFunc func(req *http.Request) bool
|
||||||
|
|
||||||
|
func matcherFuncOr(mfs []MatcherFunc) MatcherFunc {
|
||||||
|
return func(req *http.Request) bool {
|
||||||
|
for _, mf := range mfs {
|
||||||
|
rearmBody(req)
|
||||||
|
if mf(req) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matcherFuncAnd(mfs []MatcherFunc) MatcherFunc {
|
||||||
|
if len(mfs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(req *http.Request) bool {
|
||||||
|
for _, mf := range mfs {
|
||||||
|
rearmBody(req)
|
||||||
|
if !mf(req) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returns true if mf is nil, otherwise it returns mf(req).
|
||||||
|
func (mf MatcherFunc) Check(req *http.Request) bool {
|
||||||
|
return mf == nil || mf(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or combines mf and all mfs in a new [MatcherFunc]. This new
|
||||||
|
// [MatcherFunc] succeeds if one of mf or mfs succeeds. Note that as a
|
||||||
|
// a nil [MatcherFunc] is considered succeeding, if mf or one of mfs
|
||||||
|
// items is nil, nil is returned.
|
||||||
|
func (mf MatcherFunc) Or(mfs ...MatcherFunc) MatcherFunc {
|
||||||
|
if len(mfs) == 0 || mf == nil {
|
||||||
|
return mf
|
||||||
|
}
|
||||||
|
cmfs := make([]MatcherFunc, len(mfs)+1)
|
||||||
|
cmfs[0] = mf
|
||||||
|
for i, cur := range mfs {
|
||||||
|
if cur == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmfs[i+1] = cur
|
||||||
|
}
|
||||||
|
return matcherFuncOr(cmfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And combines mf and all mfs in a new [MatcherFunc]. This new
|
||||||
|
// [MatcherFunc] succeeds if all of mf and mfs succeed. Note that a
|
||||||
|
// [MatcherFunc] also succeeds if it is nil, so if mf and all mfs
|
||||||
|
// items are nil, nil is returned.
|
||||||
|
func (mf MatcherFunc) And(mfs ...MatcherFunc) MatcherFunc {
|
||||||
|
if len(mfs) == 0 {
|
||||||
|
return mf
|
||||||
|
}
|
||||||
|
cmfs := make([]MatcherFunc, 0, len(mfs)+1)
|
||||||
|
if mf != nil {
|
||||||
|
cmfs = append(cmfs, mf)
|
||||||
|
}
|
||||||
|
for _, cur := range mfs {
|
||||||
|
if cur != nil {
|
||||||
|
cmfs = append(cmfs, cur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matcherFuncAnd(cmfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matcher type defines a match case. The zero Matcher{} corresponds
|
||||||
|
// to the default case. Otherwise, use [NewMatcher] or any helper
|
||||||
|
// building a [Matcher] like [BodyContainsBytes], [BodyContainsBytes],
|
||||||
|
// [HeaderExists], [HeaderIs], [HeaderContains] or any of
|
||||||
|
// [github.com/maxatome/tdhttpmock] functions.
|
||||||
|
type Matcher struct {
|
||||||
|
name string
|
||||||
|
fn MatcherFunc // can be nil → means always true
|
||||||
|
}
|
||||||
|
|
||||||
|
var matcherID int64
|
||||||
|
|
||||||
|
// NewMatcher returns a [Matcher]. If name is empty and fn is non-nil,
|
||||||
|
// a name is automatically generated. When fn is nil, it is a default
|
||||||
|
// [Matcher]: its name can be empty.
|
||||||
|
//
|
||||||
|
// Automatically generated names have the form:
|
||||||
|
//
|
||||||
|
// ~HEXANUMBER@PKG.FUNC() FILE:LINE
|
||||||
|
//
|
||||||
|
// Legend:
|
||||||
|
// - HEXANUMBER is a unique 10 digit hexadecimal number, always increasing;
|
||||||
|
// - PKG is the NewMatcher caller package (except if
|
||||||
|
// [IgnoreMatcherHelper] has been previously called, in this case it
|
||||||
|
// is the caller of the caller package and so on);
|
||||||
|
// - FUNC is the function name of the caller in the previous PKG package;
|
||||||
|
// - FILE and LINE are the location of the call in FUNC function.
|
||||||
|
func NewMatcher(name string, fn MatcherFunc) Matcher {
|
||||||
|
if name == "" && fn != nil {
|
||||||
|
// Auto-name the matcher
|
||||||
|
name = fmt.Sprintf("~%010x%s", atomic.AddInt64(&matcherID, 1), calledFrom(1))
|
||||||
|
}
|
||||||
|
return Matcher{
|
||||||
|
name: name,
|
||||||
|
fn: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyContainsBytes returns a [Matcher] checking that request body
|
||||||
|
// contains subslice.
|
||||||
|
//
|
||||||
|
// The name of the returned [Matcher] is auto-generated (see [NewMatcher]).
|
||||||
|
// To name it explicitly, use [Matcher.WithName] as in:
|
||||||
|
//
|
||||||
|
// BodyContainsBytes([]byte("foo")).WithName("10-body-contains-foo")
|
||||||
|
//
|
||||||
|
// See also [github.com/maxatome/tdhttpmock.Body],
|
||||||
|
// [github.com/maxatome/tdhttpmock.JSONBody] and
|
||||||
|
// [github.com/maxatome/tdhttpmock.XMLBody] for powerful body testing.
|
||||||
|
func BodyContainsBytes(subslice []byte) Matcher {
|
||||||
|
return NewMatcher("",
|
||||||
|
func(req *http.Request) bool {
|
||||||
|
rearmBody(req)
|
||||||
|
b, err := ioutil.ReadAll(req.Body)
|
||||||
|
return err == nil && bytes.Contains(b, subslice)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyContainsString returns a [Matcher] checking that request body
|
||||||
|
// contains substr.
|
||||||
|
//
|
||||||
|
// The name of the returned [Matcher] is auto-generated (see [NewMatcher]).
|
||||||
|
// To name it explicitly, use [Matcher.WithName] as in:
|
||||||
|
//
|
||||||
|
// BodyContainsString("foo").WithName("10-body-contains-foo")
|
||||||
|
//
|
||||||
|
// See also [github.com/maxatome/tdhttpmock.Body],
|
||||||
|
// [github.com/maxatome/tdhttpmock.JSONBody] and
|
||||||
|
// [github.com/maxatome/tdhttpmock.XMLBody] for powerful body testing.
|
||||||
|
func BodyContainsString(substr string) Matcher {
|
||||||
|
return NewMatcher("",
|
||||||
|
func(req *http.Request) bool {
|
||||||
|
rearmBody(req)
|
||||||
|
b, err := ioutil.ReadAll(req.Body)
|
||||||
|
return err == nil && bytes.Contains(b, []byte(substr))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderExists returns a [Matcher] checking that request contains
|
||||||
|
// key header.
|
||||||
|
//
|
||||||
|
// The name of the returned [Matcher] is auto-generated (see [NewMatcher]).
|
||||||
|
// To name it explicitly, use [Matcher.WithName] as in:
|
||||||
|
//
|
||||||
|
// HeaderExists("X-Custom").WithName("10-custom-exists")
|
||||||
|
//
|
||||||
|
// See also [github.com/maxatome/tdhttpmock.Header] for powerful
|
||||||
|
// header testing.
|
||||||
|
func HeaderExists(key string) Matcher {
|
||||||
|
return NewMatcher("",
|
||||||
|
func(req *http.Request) bool {
|
||||||
|
_, ok := req.Header[key]
|
||||||
|
return ok
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderIs returns a [Matcher] checking that request contains
|
||||||
|
// key header set to value.
|
||||||
|
//
|
||||||
|
// The name of the returned [Matcher] is auto-generated (see [NewMatcher]).
|
||||||
|
// To name it explicitly, use [Matcher.WithName] as in:
|
||||||
|
//
|
||||||
|
// HeaderIs("X-Custom", "VALUE").WithName("10-custom-is-value")
|
||||||
|
//
|
||||||
|
// See also [github.com/maxatome/tdhttpmock.Header] for powerful
|
||||||
|
// header testing.
|
||||||
|
func HeaderIs(key, value string) Matcher {
|
||||||
|
return NewMatcher("",
|
||||||
|
func(req *http.Request) bool {
|
||||||
|
return req.Header.Get(key) == value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderContains returns a [Matcher] checking that request contains key
|
||||||
|
// header itself containing substr.
|
||||||
|
//
|
||||||
|
// The name of the returned [Matcher] is auto-generated (see [NewMatcher]).
|
||||||
|
// To name it explicitly, use [Matcher.WithName] as in:
|
||||||
|
//
|
||||||
|
// HeaderContains("X-Custom", "VALUE").WithName("10-custom-contains-value")
|
||||||
|
//
|
||||||
|
// See also [github.com/maxatome/tdhttpmock.Header] for powerful
|
||||||
|
// header testing.
|
||||||
|
func HeaderContains(key, substr string) Matcher {
|
||||||
|
return NewMatcher("",
|
||||||
|
func(req *http.Request) bool {
|
||||||
|
return strings.Contains(req.Header.Get(key), substr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the m's name.
|
||||||
|
func (m Matcher) Name() string {
|
||||||
|
return m.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName returns a new [Matcher] based on m with name name.
|
||||||
|
func (m Matcher) WithName(name string) Matcher {
|
||||||
|
return NewMatcher(name, m.fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returns true if req is matched by m.
|
||||||
|
func (m Matcher) Check(req *http.Request) bool {
|
||||||
|
return m.fn.Check(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or combines m and all ms in a new [Matcher]. This new [Matcher]
|
||||||
|
// succeeds if one of m or ms succeeds. Note that as a [Matcher]
|
||||||
|
// succeeds if internal fn is nil, if m's internal fn or any of ms
|
||||||
|
// item's internal fn is nil, the returned [Matcher] always
|
||||||
|
// succeeds. The name of returned [Matcher] is m's one.
|
||||||
|
func (m Matcher) Or(ms ...Matcher) Matcher {
|
||||||
|
if len(ms) == 0 || m.fn == nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
mfs := make([]MatcherFunc, 1, len(ms)+1)
|
||||||
|
mfs[0] = m.fn
|
||||||
|
for _, cur := range ms {
|
||||||
|
if cur.fn == nil {
|
||||||
|
return Matcher{}
|
||||||
|
}
|
||||||
|
mfs = append(mfs, cur.fn)
|
||||||
|
}
|
||||||
|
m.fn = matcherFuncOr(mfs)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// And combines m and all ms in a new [Matcher]. This new [Matcher]
|
||||||
|
// succeeds if all of m and ms succeed. Note that a [Matcher] also
|
||||||
|
// succeeds if [Matcher] [MatcherFunc] is nil. The name of returned
|
||||||
|
// [Matcher] is m's one if the empty/default [Matcher] is returned.
|
||||||
|
func (m Matcher) And(ms ...Matcher) Matcher {
|
||||||
|
if len(ms) == 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
mfs := make([]MatcherFunc, 0, len(ms)+1)
|
||||||
|
if m.fn != nil {
|
||||||
|
mfs = append(mfs, m.fn)
|
||||||
|
}
|
||||||
|
for _, cur := range ms {
|
||||||
|
if cur.fn != nil {
|
||||||
|
mfs = append(mfs, cur.fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.fn = matcherFuncAnd(mfs)
|
||||||
|
if m.fn != nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return Matcher{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchResponder struct {
|
||||||
|
matcher Matcher
|
||||||
|
responder Responder
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchResponders []matchResponder
|
||||||
|
|
||||||
|
// add adds or replaces a matchResponder.
|
||||||
|
func (mrs matchResponders) add(mr matchResponder) matchResponders {
|
||||||
|
// default is always at end
|
||||||
|
if mr.matcher.fn == nil {
|
||||||
|
if len(mrs) > 0 && (mrs)[len(mrs)-1].matcher.fn == nil {
|
||||||
|
mrs[len(mrs)-1] = mr
|
||||||
|
return mrs
|
||||||
|
}
|
||||||
|
return append(mrs, mr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cur := range mrs {
|
||||||
|
if cur.matcher.name == mr.matcher.name {
|
||||||
|
mrs[i] = mr
|
||||||
|
return mrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cur := range mrs {
|
||||||
|
if cur.matcher.fn == nil || cur.matcher.name > mr.matcher.name {
|
||||||
|
mrs = append(mrs, matchResponder{})
|
||||||
|
copy(mrs[i+1:], mrs[i:len(mrs)-1])
|
||||||
|
mrs[i] = mr
|
||||||
|
return mrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(mrs, mr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mrs matchResponders) checkEmptiness() matchResponders {
|
||||||
|
if len(mrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return mrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mrs matchResponders) shrink() matchResponders {
|
||||||
|
mrs[len(mrs)-1] = matchResponder{}
|
||||||
|
mrs = mrs[:len(mrs)-1]
|
||||||
|
return mrs.checkEmptiness()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mrs matchResponders) remove(name string) matchResponders {
|
||||||
|
// Special case, even if default has been renamed, we consider ""
|
||||||
|
// matching this default
|
||||||
|
if name == "" {
|
||||||
|
// default is always at end
|
||||||
|
if len(mrs) > 0 && mrs[len(mrs)-1].matcher.fn == nil {
|
||||||
|
return mrs.shrink()
|
||||||
|
}
|
||||||
|
return mrs.checkEmptiness()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cur := range mrs {
|
||||||
|
if cur.matcher.name == name {
|
||||||
|
copy(mrs[i:], mrs[i+1:])
|
||||||
|
return mrs.shrink()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mrs.checkEmptiness()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mrs matchResponders) findMatchResponder(req *http.Request) *matchResponder {
|
||||||
|
if len(mrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mrs[0].matcher.fn == nil { // nil match is always the last
|
||||||
|
return &mrs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBody := &bodyCopyOnRead{body: req.Body}
|
||||||
|
req.Body = copyBody
|
||||||
|
defer func() {
|
||||||
|
copyBody.rearm()
|
||||||
|
req.Body = copyBody.body
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, mr := range mrs {
|
||||||
|
copyBody.rearm()
|
||||||
|
if mr.matcher.Check(req) {
|
||||||
|
return &mr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchRouteKey struct {
|
||||||
|
internal.RouteKey
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m matchRouteKey) String() string {
|
||||||
|
if m.name == "" {
|
||||||
|
return m.RouteKey.String()
|
||||||
|
}
|
||||||
|
return m.RouteKey.String() + " <" + m.name + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
func rearmBody(req *http.Request) {
|
||||||
|
if req != nil {
|
||||||
|
if body, ok := req.Body.(interface{ rearm() }); ok {
|
||||||
|
body.rearm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type buffer struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b buffer) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bodyCopyOnRead mutates body into a buffer on first Read(), except
|
||||||
|
// if body is nil or http.NoBody. In this case, EOF is returned for
|
||||||
|
// each Read() and body stays untouched.
|
||||||
|
type bodyCopyOnRead struct {
|
||||||
|
body io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bodyCopyOnRead) rearm() {
|
||||||
|
if buf, ok := b.body.(buffer); ok {
|
||||||
|
buf.Seek(0, io.SeekStart) //nolint:errcheck
|
||||||
|
} // else b.body contains the original body, so don't touch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bodyCopyOnRead) copy() {
|
||||||
|
if _, ok := b.body.(buffer); !ok && b.body != nil && b.body != http.NoBody {
|
||||||
|
buf, _ := ioutil.ReadAll(b.body)
|
||||||
|
b.body.Close()
|
||||||
|
b.body = buffer{bytes.NewReader(buf)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bodyCopyOnRead) Read(p []byte) (n int, err error) {
|
||||||
|
b.copy()
|
||||||
|
if b.body == nil {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return b.body.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bodyCopyOnRead) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
756
vendor/github.com/jarcoal/httpmock/response.go
generated
vendored
Normal file
756
vendor/github.com/jarcoal/httpmock/response.go
generated
vendored
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
package httpmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jarcoal/httpmock/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fromThenKeyType is used by Then().
|
||||||
|
type fromThenKeyType struct{}
|
||||||
|
|
||||||
|
var fromThenKey = fromThenKeyType{}
|
||||||
|
|
||||||
|
type suggestedInfo struct {
|
||||||
|
kind string
|
||||||
|
suggested string
|
||||||
|
}
|
||||||
|
|
||||||
|
// suggestedMethodKeyType is used by NewNotFoundResponder().
|
||||||
|
type suggestedKeyType struct{}
|
||||||
|
|
||||||
|
var suggestedKey = suggestedKeyType{}
|
||||||
|
|
||||||
|
// Responder is a callback that receives an [*http.Request] and returns
|
||||||
|
// a mocked response.
|
||||||
|
type Responder func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (r Responder) times(name string, n int, fn ...func(...any)) Responder {
|
||||||
|
count := 0
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
count++
|
||||||
|
if count > n {
|
||||||
|
err := internal.StackTracer{
|
||||||
|
Err: fmt.Errorf("Responder not found for %s %s (coz %s and already called %d times)", req.Method, req.URL, name, count),
|
||||||
|
}
|
||||||
|
if len(fn) > 0 {
|
||||||
|
err.CustomFn = fn[0]
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times returns a [Responder] callable n times before returning an
|
||||||
|
// error. If the [Responder] is called more than n times and fn is
|
||||||
|
// passed and non-nil, it acts as the fn parameter of
|
||||||
|
// [NewNotFoundResponder], allowing to dump the stack trace to
|
||||||
|
// localize the origin of the call.
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// // This responder is callable 3 times, then an error is returned and
|
||||||
|
// // the stacktrace of the call logged using t.Log()
|
||||||
|
// httpmock.RegisterResponder("GET", "/foo/bar",
|
||||||
|
// httpmock.NewStringResponder(200, "{}").Times(3, t.Log),
|
||||||
|
// )
|
||||||
|
func (r Responder) Times(n int, fn ...func(...any)) Responder {
|
||||||
|
return r.times("Times", n, fn...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once returns a new [Responder] callable once before returning an
|
||||||
|
// error. If the [Responder] is called 2 or more times and fn is passed
|
||||||
|
// and non-nil, it acts as the fn parameter of [NewNotFoundResponder],
|
||||||
|
// allowing to dump the stack trace to localize the origin of the
|
||||||
|
// call.
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// // This responder is callable only once, then an error is returned and
|
||||||
|
// // the stacktrace of the call logged using t.Log()
|
||||||
|
// httpmock.RegisterResponder("GET", "/foo/bar",
|
||||||
|
// httpmock.NewStringResponder(200, "{}").Once(t.Log),
|
||||||
|
// )
|
||||||
|
func (r Responder) Once(fn ...func(...any)) Responder {
|
||||||
|
return r.times("Once", 1, fn...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace returns a new [Responder] that allows to easily trace the calls
|
||||||
|
// of the original [Responder] using fn. It can be used in conjunction
|
||||||
|
// with the testing package as in the example below with the help of
|
||||||
|
// [*testing.T.Log] method:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// httpmock.RegisterResponder("GET", "/foo/bar",
|
||||||
|
// httpmock.NewStringResponder(200, "{}").Trace(t.Log),
|
||||||
|
// )
|
||||||
|
func (r Responder) Trace(fn func(...any)) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := r(req)
|
||||||
|
return resp, internal.StackTracer{
|
||||||
|
CustomFn: fn,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay returns a new [Responder] that calls the original r Responder
|
||||||
|
// after a delay of d.
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "time"
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// httpmock.RegisterResponder("GET", "/foo/bar",
|
||||||
|
// httpmock.NewStringResponder(200, "{}").Delay(100*time.Millisecond),
|
||||||
|
// )
|
||||||
|
func (r Responder) Delay(d time.Duration) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
time.Sleep(d)
|
||||||
|
return r(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errThenDone = errors.New("ThenDone")
|
||||||
|
|
||||||
|
// similar is simple but a bit tricky. Here we consider two Responder
|
||||||
|
// are similar if they share the same function, but not necessarily
|
||||||
|
// the same environment. It is only used by Then below.
|
||||||
|
func (r Responder) similar(other Responder) bool {
|
||||||
|
return reflect.ValueOf(r).Pointer() == reflect.ValueOf(other).Pointer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then returns a new [Responder] that calls r on first invocation, then
|
||||||
|
// next on following ones, except when Then is chained, in this case
|
||||||
|
// next is called only once:
|
||||||
|
//
|
||||||
|
// A := httpmock.NewStringResponder(200, "A")
|
||||||
|
// B := httpmock.NewStringResponder(200, "B")
|
||||||
|
// C := httpmock.NewStringResponder(200, "C")
|
||||||
|
//
|
||||||
|
// httpmock.RegisterResponder("GET", "/pipo", A.Then(B).Then(C))
|
||||||
|
//
|
||||||
|
// http.Get("http://foo.bar/pipo") // A is called
|
||||||
|
// http.Get("http://foo.bar/pipo") // B is called
|
||||||
|
// http.Get("http://foo.bar/pipo") // C is called
|
||||||
|
// http.Get("http://foo.bar/pipo") // C is called, and so on
|
||||||
|
//
|
||||||
|
// A panic occurs if next is the result of another Then call (because
|
||||||
|
// allowing it could cause inextricable problems at runtime). Then
|
||||||
|
// calls can be chained, but cannot call each other by
|
||||||
|
// parameter. Example:
|
||||||
|
//
|
||||||
|
// A.Then(B).Then(C) // is OK
|
||||||
|
// A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call
|
||||||
|
//
|
||||||
|
// See also [ResponderFromMultipleResponses].
|
||||||
|
func (r Responder) Then(next Responder) (x Responder) {
|
||||||
|
var done int
|
||||||
|
var mu sync.Mutex
|
||||||
|
x = func(req *http.Request) (*http.Response, error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
ctx := req.Context()
|
||||||
|
thenCalledUs, _ := ctx.Value(fromThenKey).(bool)
|
||||||
|
if !thenCalledUs {
|
||||||
|
req = req.WithContext(context.WithValue(ctx, fromThenKey, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch done {
|
||||||
|
case 0:
|
||||||
|
resp, err := r(req)
|
||||||
|
if err != errThenDone {
|
||||||
|
if !x.similar(r) { // r is NOT a Then
|
||||||
|
done = 1
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
done = 2 // next is NEVER a Then, as it is forbidden by design
|
||||||
|
return next(req)
|
||||||
|
}
|
||||||
|
if thenCalledUs {
|
||||||
|
return nil, errThenDone
|
||||||
|
}
|
||||||
|
return next(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if next.similar(x) {
|
||||||
|
panic("Then() does not accept another Then() Responder as parameter")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContentLength returns a new [Responder] based on r that ensures
|
||||||
|
// the returned [*http.Response] ContentLength field and
|
||||||
|
// Content-Length header are set to the right value.
|
||||||
|
//
|
||||||
|
// If r returns an [*http.Response] with a nil Body or equal to
|
||||||
|
// [http.NoBody], the length is always set to 0.
|
||||||
|
//
|
||||||
|
// If r returned response.Body implements:
|
||||||
|
//
|
||||||
|
// Len() int
|
||||||
|
//
|
||||||
|
// then the length is set to the Body.Len() returned value. All
|
||||||
|
// httpmock generated bodies implement this method. Beware that
|
||||||
|
// [strings.Builder], [strings.Reader], [bytes.Buffer] and
|
||||||
|
// [bytes.Reader] types used with [io.NopCloser] do not implement
|
||||||
|
// Len() anymore.
|
||||||
|
//
|
||||||
|
// Otherwise, r returned response.Body is entirely copied into an
|
||||||
|
// internal buffer to get its length, then it is closed. The Body of
|
||||||
|
// the [*http.Response] returned by the [Responder] returned by
|
||||||
|
// SetContentLength can then be read again to return its content as
|
||||||
|
// usual. But keep in mind that each time this [Responder] is called,
|
||||||
|
// r is called first. So this one has to carefully handle its body: it
|
||||||
|
// is highly recommended to use [NewRespBodyFromString] or
|
||||||
|
// [NewRespBodyFromBytes] to set the body once (as
|
||||||
|
// [NewStringResponder] and [NewBytesResponder] do behind the scene),
|
||||||
|
// or to build the body each time r is called.
|
||||||
|
//
|
||||||
|
// The following calls are all correct:
|
||||||
|
//
|
||||||
|
// responder = httpmock.NewStringResponder(200, "BODY").SetContentLength()
|
||||||
|
// responder = httpmock.NewBytesResponder(200, []byte("BODY")).SetContentLength()
|
||||||
|
// responder = ResponderFromResponse(&http.Response{
|
||||||
|
// // build a body once, but httpmock knows how to "rearm" it once read
|
||||||
|
// Body: NewRespBodyFromString("BODY"),
|
||||||
|
// StatusCode: 200,
|
||||||
|
// }).SetContentLength()
|
||||||
|
// responder = httpmock.Responder(func(req *http.Request) (*http.Response, error) {
|
||||||
|
// // build a new body for each call
|
||||||
|
// return &http.Response{
|
||||||
|
// StatusCode: 200,
|
||||||
|
// Body: io.NopCloser(strings.NewReader("BODY")),
|
||||||
|
// }, nil
|
||||||
|
// }).SetContentLength()
|
||||||
|
//
|
||||||
|
// But the following is not correct:
|
||||||
|
//
|
||||||
|
// responder = httpmock.ResponderFromResponse(&http.Response{
|
||||||
|
// StatusCode: 200,
|
||||||
|
// Body: io.NopCloser(strings.NewReader("BODY")),
|
||||||
|
// }).SetContentLength()
|
||||||
|
//
|
||||||
|
// it will only succeed for the first responder call. The following
|
||||||
|
// calls will deliver responses with an empty body, as it will already
|
||||||
|
// been read by the first call.
|
||||||
|
func (r Responder) SetContentLength() Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := r(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nr := *resp
|
||||||
|
switch nr.Body {
|
||||||
|
case nil:
|
||||||
|
nr.Body = http.NoBody
|
||||||
|
fallthrough
|
||||||
|
case http.NoBody:
|
||||||
|
nr.ContentLength = 0
|
||||||
|
default:
|
||||||
|
bl, ok := nr.Body.(interface{ Len() int })
|
||||||
|
if !ok {
|
||||||
|
copyBody := &dummyReadCloser{orig: nr.Body}
|
||||||
|
bl, nr.Body = copyBody, copyBody
|
||||||
|
}
|
||||||
|
nr.ContentLength = int64(bl.Len())
|
||||||
|
}
|
||||||
|
if nr.Header == nil {
|
||||||
|
nr.Header = http.Header{}
|
||||||
|
}
|
||||||
|
nr.Header = nr.Header.Clone()
|
||||||
|
nr.Header.Set("Content-Length", strconv.FormatInt(nr.ContentLength, 10))
|
||||||
|
return &nr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderAdd returns a new [Responder] based on r that ensures the
|
||||||
|
// returned [*http.Response] includes h header. It adds each h entry
|
||||||
|
// to the header. It appends to any existing values associated with
|
||||||
|
// each h key. Each key is case insensitive; it is canonicalized by
|
||||||
|
// [http.CanonicalHeaderKey].
|
||||||
|
//
|
||||||
|
// See also [Responder.HeaderSet] and [Responder.SetContentLength].
|
||||||
|
func (r Responder) HeaderAdd(h http.Header) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := r(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nr := *resp
|
||||||
|
if nr.Header == nil {
|
||||||
|
nr.Header = make(http.Header, len(h))
|
||||||
|
}
|
||||||
|
nr.Header = nr.Header.Clone()
|
||||||
|
for k, v := range h {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
if v == nil {
|
||||||
|
if _, ok := nr.Header[k]; !ok {
|
||||||
|
nr.Header[k] = nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nr.Header[k] = append(nr.Header[k], v...)
|
||||||
|
}
|
||||||
|
return &nr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderSet returns a new [Responder] based on r that ensures the
|
||||||
|
// returned [*http.Response] includes h header. It sets the header
|
||||||
|
// entries associated with each h key. It replaces any existing values
|
||||||
|
// associated each h key. Each key is case insensitive; it is
|
||||||
|
// canonicalized by [http.CanonicalHeaderKey].
|
||||||
|
//
|
||||||
|
// See also [Responder.HeaderAdd] and [Responder.SetContentLength].
|
||||||
|
func (r Responder) HeaderSet(h http.Header) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := r(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nr := *resp
|
||||||
|
if nr.Header == nil {
|
||||||
|
nr.Header = make(http.Header, len(h))
|
||||||
|
}
|
||||||
|
nr.Header = nr.Header.Clone()
|
||||||
|
for k, v := range h {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
if v == nil {
|
||||||
|
nr.Header[k] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nr.Header[k] = append([]string(nil), v...)
|
||||||
|
}
|
||||||
|
return &nr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponderFromResponse wraps an [*http.Response] in a [Responder].
|
||||||
|
//
|
||||||
|
// Be careful, except for responses generated by httpmock
|
||||||
|
// ([NewStringResponse] and [NewBytesResponse] functions) for which
|
||||||
|
// there is no problems, it is the caller responsibility to ensure the
|
||||||
|
// response body can be read several times and concurrently if needed,
|
||||||
|
// as it is shared among all [Responder] returned responses.
|
||||||
|
//
|
||||||
|
// For home-made responses, [NewRespBodyFromString] and
|
||||||
|
// [NewRespBodyFromBytes] functions can be used to produce response
|
||||||
|
// bodies that can be read several times and concurrently.
|
||||||
|
func ResponderFromResponse(resp *http.Response) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
res := *resp
|
||||||
|
|
||||||
|
// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
|
||||||
|
if body, ok := resp.Body.(*dummyReadCloser); ok {
|
||||||
|
res.Body = body.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Request = req
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponderFromMultipleResponses wraps an [*http.Response] list in a
|
||||||
|
// [Responder].
|
||||||
|
//
|
||||||
|
// Each response will be returned in the order of the provided list.
|
||||||
|
// If the [Responder] is called more than the size of the provided
|
||||||
|
// list, an error will be thrown.
|
||||||
|
//
|
||||||
|
// Be careful, except for responses generated by httpmock
|
||||||
|
// ([NewStringResponse] and [NewBytesResponse] functions) for which
|
||||||
|
// there is no problems, it is the caller responsibility to ensure the
|
||||||
|
// response body can be read several times and concurrently if needed,
|
||||||
|
// as it is shared among all [Responder] returned responses.
|
||||||
|
//
|
||||||
|
// For home-made responses, [NewRespBodyFromString] and
|
||||||
|
// [NewRespBodyFromBytes] functions can be used to produce response
|
||||||
|
// bodies that can be read several times and concurrently.
|
||||||
|
//
|
||||||
|
// If all responses have been returned and fn is passed and non-nil,
|
||||||
|
// it acts as the fn parameter of [NewNotFoundResponder], allowing to
|
||||||
|
// dump the stack trace to localize the origin of the call.
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// "testing"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// // This responder is callable only once, then an error is returned and
|
||||||
|
// // the stacktrace of the call logged using t.Log()
|
||||||
|
// httpmock.RegisterResponder("GET", "/foo/bar",
|
||||||
|
// httpmock.ResponderFromMultipleResponses(
|
||||||
|
// []*http.Response{
|
||||||
|
// httpmock.NewStringResponse(200, `{"name":"bar"}`),
|
||||||
|
// httpmock.NewStringResponse(404, `{"mesg":"Not found"}`),
|
||||||
|
// },
|
||||||
|
// t.Log),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See also [Responder.Then].
|
||||||
|
func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...any)) Responder {
|
||||||
|
responseIndex := 0
|
||||||
|
mutex := sync.Mutex{}
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
defer func() { responseIndex++ }()
|
||||||
|
if responseIndex >= len(responses) {
|
||||||
|
err := internal.StackTracer{
|
||||||
|
Err: fmt.Errorf("not enough responses provided: responder called %d time(s) but %d response(s) provided", responseIndex+1, len(responses)),
|
||||||
|
}
|
||||||
|
if len(fn) > 0 {
|
||||||
|
err.CustomFn = fn[0]
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res := *responses[responseIndex]
|
||||||
|
// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
|
||||||
|
if body, ok := responses[responseIndex].Body.(*dummyReadCloser); ok {
|
||||||
|
res.Body = body.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Request = req
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorResponder creates a [Responder] that returns an empty request and the
|
||||||
|
// given error. This can be used to e.g. imitate more deep http errors for the
|
||||||
|
// client.
|
||||||
|
func NewErrorResponder(err error) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotFoundResponder creates a [Responder] typically used in
|
||||||
|
// conjunction with [RegisterNoResponder] function and [testing]
|
||||||
|
// package, to be proactive when a [Responder] is not found. fn is
|
||||||
|
// called with a unique string parameter containing the name of the
|
||||||
|
// missing route and the stack trace to localize the origin of the
|
||||||
|
// call. If fn returns (= if it does not panic), the [Responder] returns
|
||||||
|
// an error of the form: "Responder not found for GET http://foo.bar/path".
|
||||||
|
// Note that fn can be nil.
|
||||||
|
//
|
||||||
|
// It is useful when writing tests to ensure that all routes have been
|
||||||
|
// mocked.
|
||||||
|
//
|
||||||
|
// Example of use:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/jarcoal/httpmock"
|
||||||
|
// )
|
||||||
|
// ...
|
||||||
|
// func TestMyApp(t *testing.T) {
|
||||||
|
// ...
|
||||||
|
// // Calls testing.Fatal with the name of Responder-less route and
|
||||||
|
// // the stack trace of the call.
|
||||||
|
// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal))
|
||||||
|
//
|
||||||
|
// Will abort the current test and print something like:
|
||||||
|
//
|
||||||
|
// transport_test.go:735: Called from net/http.Get()
|
||||||
|
// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714
|
||||||
|
// github.com/jarcoal/httpmock.TestCheckStackTracer()
|
||||||
|
// at /go/src/testing/testing.go:865
|
||||||
|
// testing.tRunner()
|
||||||
|
// at /go/src/runtime/asm_amd64.s:1337
|
||||||
|
func NewNotFoundResponder(fn func(...any)) Responder {
|
||||||
|
return func(req *http.Request) (*http.Response, error) {
|
||||||
|
var extra string
|
||||||
|
suggested, _ := req.Context().Value(suggestedKey).(*suggestedInfo)
|
||||||
|
if suggested != nil {
|
||||||
|
if suggested.kind == "matcher" {
|
||||||
|
extra = fmt.Sprintf(` despite %s`, suggested.suggested)
|
||||||
|
} else {
|
||||||
|
extra = fmt.Sprintf(`, but one matches %s %q`, suggested.kind, suggested.suggested)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, internal.StackTracer{
|
||||||
|
CustomFn: fn,
|
||||||
|
Err: fmt.Errorf("Responder not found for %s %s%s", req.Method, req.URL, extra),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringResponse creates an [*http.Response] with a body based on
|
||||||
|
// the given string. Also accepts an HTTP status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewStringResponse(200, httpmock.File("body.txt").String())
|
||||||
|
func NewStringResponse(status int, body string) *http.Response {
|
||||||
|
return &http.Response{
|
||||||
|
Status: strconv.Itoa(status),
|
||||||
|
StatusCode: status,
|
||||||
|
Body: NewRespBodyFromString(body),
|
||||||
|
Header: http.Header{},
|
||||||
|
ContentLength: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringResponder creates a [Responder] from a given body (as a
|
||||||
|
// string) and status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewStringResponder(200, httpmock.File("body.txt").String())
|
||||||
|
func NewStringResponder(status int, body string) Responder {
|
||||||
|
return ResponderFromResponse(NewStringResponse(status, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBytesResponse creates an [*http.Response] with a body based on the
|
||||||
|
// given bytes. Also accepts an HTTP status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewBytesResponse(200, httpmock.File("body.raw").Bytes())
|
||||||
|
func NewBytesResponse(status int, body []byte) *http.Response {
|
||||||
|
return &http.Response{
|
||||||
|
Status: strconv.Itoa(status),
|
||||||
|
StatusCode: status,
|
||||||
|
Body: NewRespBodyFromBytes(body),
|
||||||
|
Header: http.Header{},
|
||||||
|
ContentLength: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBytesResponder creates a [Responder] from a given body (as a byte
|
||||||
|
// slice) and status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes())
|
||||||
|
func NewBytesResponder(status int, body []byte) Responder {
|
||||||
|
return ResponderFromResponse(NewBytesResponse(status, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJsonResponse creates an [*http.Response] with a body that is a
|
||||||
|
// JSON encoded representation of the given any. Also accepts
|
||||||
|
// an HTTP status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewJsonResponse(200, httpmock.File("body.json"))
|
||||||
|
func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: revive
|
||||||
|
encoded, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := NewBytesResponse(status, encoded)
|
||||||
|
response.Header.Set("Content-Type", "application/json")
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJsonResponder creates a [Responder] from a given body (as an
|
||||||
|
// any that is encoded to JSON) and status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewJsonResponder(200, httpmock.File("body.json"))
|
||||||
|
func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revive
|
||||||
|
resp, err := NewJsonResponse(status, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ResponderFromResponse(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJsonResponderOrPanic is like [NewJsonResponder] but panics in
|
||||||
|
// case of error.
|
||||||
|
//
|
||||||
|
// It simplifies the call of [RegisterResponder], avoiding the use of a
|
||||||
|
// temporary variable and an error check, and so can be used as
|
||||||
|
// [NewStringResponder] or [NewBytesResponder] in such context:
|
||||||
|
//
|
||||||
|
// httpmock.RegisterResponder(
|
||||||
|
// "GET",
|
||||||
|
// "/test/path",
|
||||||
|
// httpmock.NewJsonResponderOrPanic(200, &MyBody),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewJsonResponderOrPanic(200, httpmock.File("body.json"))
|
||||||
|
func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive
|
||||||
|
responder, err := NewJsonResponder(status, body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return responder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXmlResponse creates an [*http.Response] with a body that is an
|
||||||
|
// XML encoded representation of the given any. Also accepts an HTTP
|
||||||
|
// status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewXmlResponse(200, httpmock.File("body.xml"))
|
||||||
|
func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: revive
|
||||||
|
var (
|
||||||
|
encoded []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if f, ok := body.(File); ok {
|
||||||
|
encoded, err = f.bytes()
|
||||||
|
} else {
|
||||||
|
encoded, err = xml.Marshal(body)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := NewBytesResponse(status, encoded)
|
||||||
|
response.Header.Set("Content-Type", "application/xml")
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXmlResponder creates a [Responder] from a given body (as an
|
||||||
|
// any that is encoded to XML) and status code.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewXmlResponder(200, httpmock.File("body.xml"))
|
||||||
|
func NewXmlResponder(status int, body any) (Responder, error) { // nolint: revive
|
||||||
|
resp, err := NewXmlResponse(status, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ResponderFromResponse(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXmlResponderOrPanic is like [NewXmlResponder] but panics in case
|
||||||
|
// of error.
|
||||||
|
//
|
||||||
|
// It simplifies the call of [RegisterResponder], avoiding the use of a
|
||||||
|
// temporary variable and an error check, and so can be used as
|
||||||
|
// [NewStringResponder] or [NewBytesResponder] in such context:
|
||||||
|
//
|
||||||
|
// httpmock.RegisterResponder(
|
||||||
|
// "GET",
|
||||||
|
// "/test/path",
|
||||||
|
// httpmock.NewXmlResponderOrPanic(200, &MyBody),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewXmlResponderOrPanic(200, httpmock.File("body.xml"))
|
||||||
|
func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive
|
||||||
|
responder, err := NewXmlResponder(status, body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return responder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRespBodyFromString creates an [io.ReadCloser] from a string that
|
||||||
|
// is suitable for use as an HTTP response body.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewRespBodyFromString(httpmock.File("body.txt").String())
|
||||||
|
func NewRespBodyFromString(body string) io.ReadCloser {
|
||||||
|
return &dummyReadCloser{orig: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRespBodyFromBytes creates an [io.ReadCloser] from a byte slice
|
||||||
|
// that is suitable for use as an HTTP response body.
|
||||||
|
//
|
||||||
|
// To pass the content of an existing file as body use [File] as in:
|
||||||
|
//
|
||||||
|
// httpmock.NewRespBodyFromBytes(httpmock.File("body.txt").Bytes())
|
||||||
|
func NewRespBodyFromBytes(body []byte) io.ReadCloser {
|
||||||
|
return &dummyReadCloser{orig: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lenReadSeeker interface {
|
||||||
|
io.ReadSeeker
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyReadCloser struct {
|
||||||
|
orig any // string or []byte
|
||||||
|
body lenReadSeeker // instanciated on demand from orig
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a new instance resetting d.body to nil.
|
||||||
|
func (d *dummyReadCloser) copy() *dummyReadCloser {
|
||||||
|
return &dummyReadCloser{orig: d.orig}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup ensures d.body is correctly initialized.
|
||||||
|
func (d *dummyReadCloser) setup() {
|
||||||
|
if d.body == nil {
|
||||||
|
switch body := d.orig.(type) {
|
||||||
|
case string:
|
||||||
|
d.body = strings.NewReader(body)
|
||||||
|
case []byte:
|
||||||
|
d.body = bytes.NewReader(body)
|
||||||
|
case io.ReadCloser:
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, body) //nolint: errcheck
|
||||||
|
body.Close()
|
||||||
|
d.body = bytes.NewReader(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyReadCloser) Read(p []byte) (n int, err error) {
|
||||||
|
d.setup()
|
||||||
|
return d.body.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyReadCloser) Close() error {
|
||||||
|
d.setup()
|
||||||
|
d.body.Seek(0, io.SeekEnd) // nolint: errcheck
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyReadCloser) Len() int {
|
||||||
|
d.setup()
|
||||||
|
return d.body.Len()
|
||||||
|
}
|
1867
vendor/github.com/jarcoal/httpmock/transport.go
generated
vendored
Normal file
1867
vendor/github.com/jarcoal/httpmock/transport.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -726,6 +726,10 @@ github.com/imdario/mergo
|
|||||||
# github.com/inconshreveable/mousetrap v1.1.0
|
# github.com/inconshreveable/mousetrap v1.1.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/inconshreveable/mousetrap
|
github.com/inconshreveable/mousetrap
|
||||||
|
# github.com/jarcoal/httpmock v1.3.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
github.com/jarcoal/httpmock
|
||||||
|
github.com/jarcoal/httpmock/internal
|
||||||
# github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71
|
# github.com/jaschaephraim/lrserver v0.0.0-20171129202958-50d19f603f71
|
||||||
## explicit
|
## explicit
|
||||||
github.com/jaschaephraim/lrserver
|
github.com/jaschaephraim/lrserver
|
||||||
|
Loading…
Reference in New Issue
Block a user