This commit is contained in:
Mustafa Gezen 2023-08-29 02:33:49 +02:00
parent 18b15013eb
commit d9a69cdfa2
Signed by: mustafa
GPG Key ID: DCDF010D946438C1
20 changed files with 3831 additions and 0 deletions

View File

@ -1265,6 +1265,12 @@ def go_dependencies():
sum = "h1:JyZjdMQu9Kl/wLXe9xA6s1X+tF6BWsQPFGJMEeCfWzE=",
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(
name = "com_github_jaschaephraim_lrserver",
@ -1577,6 +1583,13 @@ def go_dependencies():
sum = "h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=",
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(
name = "com_github_microsoft_go_winio",
importpath = "github.com/Microsoft/go-winio",

1
go.mod
View File

@ -23,6 +23,7 @@ require (
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/grpc-gateway/v2 v2.17.1
github.com/jarcoal/httpmock v1.3.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/sassoftware/go-rpmutils v0.2.0

3
go.sum
View File

@ -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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
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/go.mod h1:ozZLfjiLmXytkIUh200wMeuoQJ4ww06wN+KZtFP6j3g=
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.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
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.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
)
}

View 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
}

View 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
}

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

4
vendor/modules.txt vendored
View File

@ -726,6 +726,10 @@ github.com/imdario/mergo
# github.com/inconshreveable/mousetrap v1.1.0
## explicit; go 1.18
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
## explicit
github.com/jaschaephraim/lrserver