...

Source file src/github.com/jarcoal/httpmock/response.go

Documentation: github.com/jarcoal/httpmock

     1  package httpmock
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"encoding/xml"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/jarcoal/httpmock/internal"
    19  )
    20  
    21  // fromThenKeyType is used by Then().
    22  type fromThenKeyType struct{}
    23  
    24  var fromThenKey = fromThenKeyType{}
    25  
    26  type suggestedInfo struct {
    27  	kind      string
    28  	suggested string
    29  }
    30  
    31  // suggestedMethodKeyType is used by NewNotFoundResponder().
    32  type suggestedKeyType struct{}
    33  
    34  var suggestedKey = suggestedKeyType{}
    35  
    36  // Responder is a callback that receives an http request and returns
    37  // a mocked response.
    38  type Responder func(*http.Request) (*http.Response, error)
    39  
    40  func (r Responder) times(name string, n int, fn ...func(...any)) Responder {
    41  	count := 0
    42  	return func(req *http.Request) (*http.Response, error) {
    43  		count++
    44  		if count > n {
    45  			err := internal.StackTracer{
    46  				Err: fmt.Errorf("Responder not found for %s %s (coz %s and already called %d times)", req.Method, req.URL, name, count),
    47  			}
    48  			if len(fn) > 0 {
    49  				err.CustomFn = fn[0]
    50  			}
    51  			return nil, err
    52  		}
    53  		return r(req)
    54  	}
    55  }
    56  
    57  // Times returns a Responder callable n times before returning an
    58  // error. If the Responder is called more than n times and fn is
    59  // passed and non-nil, it acts as the fn parameter of
    60  // NewNotFoundResponder, allowing to dump the stack trace to localize
    61  // the origin of the call.
    62  //   import (
    63  //     "testing"
    64  //     "github.com/jarcoal/httpmock"
    65  //   )
    66  //   ...
    67  //   func TestMyApp(t *testing.T) {
    68  //     ...
    69  //     // This responder is callable 3 times, then an error is returned and
    70  //     // the stacktrace of the call logged using t.Log()
    71  //     httpmock.RegisterResponder("GET", "/foo/bar",
    72  //       httpmock.NewStringResponder(200, "{}").Times(3, t.Log),
    73  //     )
    74  func (r Responder) Times(n int, fn ...func(...any)) Responder {
    75  	return r.times("Times", n, fn...)
    76  }
    77  
    78  // Once returns a new Responder callable once before returning an
    79  // error. If the Responder is called 2 or more times and fn is passed
    80  // and non-nil, it acts as the fn parameter of NewNotFoundResponder,
    81  // allowing to dump the stack trace to localize the origin of the
    82  // call.
    83  //   import (
    84  //     "testing"
    85  //     "github.com/jarcoal/httpmock"
    86  //   )
    87  //   ...
    88  //   func TestMyApp(t *testing.T) {
    89  //     ...
    90  //     // This responder is callable only once, then an error is returned and
    91  //     // the stacktrace of the call logged using t.Log()
    92  //     httpmock.RegisterResponder("GET", "/foo/bar",
    93  //       httpmock.NewStringResponder(200, "{}").Once(t.Log),
    94  //     )
    95  func (r Responder) Once(fn ...func(...any)) Responder {
    96  	return r.times("Once", 1, fn...)
    97  }
    98  
    99  // Trace returns a new Responder that allows to easily trace the calls
   100  // of the original Responder using fn. It can be used in conjunction
   101  // with the testing package as in the example below with the help of
   102  // (*testing.T).Log method:
   103  //   import (
   104  //     "testing"
   105  //     "github.com/jarcoal/httpmock"
   106  //   )
   107  //   ...
   108  //   func TestMyApp(t *testing.T) {
   109  //     ...
   110  //     httpmock.RegisterResponder("GET", "/foo/bar",
   111  //       httpmock.NewStringResponder(200, "{}").Trace(t.Log),
   112  //     )
   113  func (r Responder) Trace(fn func(...any)) Responder {
   114  	return func(req *http.Request) (*http.Response, error) {
   115  		resp, err := r(req)
   116  		return resp, internal.StackTracer{
   117  			CustomFn: fn,
   118  			Err:      err,
   119  		}
   120  	}
   121  }
   122  
   123  // Delay returns a new Responder that calls the original r Responder
   124  // after a delay of d.
   125  //   import (
   126  //     "testing"
   127  //     "time"
   128  //     "github.com/jarcoal/httpmock"
   129  //   )
   130  //   ...
   131  //   func TestMyApp(t *testing.T) {
   132  //     ...
   133  //     httpmock.RegisterResponder("GET", "/foo/bar",
   134  //       httpmock.NewStringResponder(200, "{}").Delay(100*time.Millisecond),
   135  //     )
   136  func (r Responder) Delay(d time.Duration) Responder {
   137  	return func(req *http.Request) (*http.Response, error) {
   138  		time.Sleep(d)
   139  		return r(req)
   140  	}
   141  }
   142  
   143  var errThenDone = errors.New("ThenDone")
   144  
   145  // similar is simple but a bit tricky. Here we consider two Responder
   146  // are similar if they share the same function, but not necessarily
   147  // the same environment. It is only used by Then below.
   148  func (r Responder) similar(other Responder) bool {
   149  	return reflect.ValueOf(r).Pointer() == reflect.ValueOf(other).Pointer()
   150  }
   151  
   152  // Then returns a new Responder that calls r on first invocation, then
   153  // next on following ones, except when Then is chained, in this case
   154  // next is called only once:
   155  //   A := httpmock.NewStringResponder(200, "A")
   156  //   B := httpmock.NewStringResponder(200, "B")
   157  //   C := httpmock.NewStringResponder(200, "C")
   158  //
   159  //   httpmock.RegisterResponder("GET", "/pipo", A.Then(B).Then(C))
   160  //
   161  //   http.Get("http://foo.bar/pipo") // A is called
   162  //   http.Get("http://foo.bar/pipo") // B is called
   163  //   http.Get("http://foo.bar/pipo") // C is called
   164  //   http.Get("http://foo.bar/pipo") // C is called, and so on
   165  //
   166  // A panic occurs if next is the result of another Then call (because
   167  // allowing it could cause inextricable problems at runtime). Then
   168  // calls can be chained, but cannot call each other by
   169  // parameter. Example:
   170  //   A.Then(B).Then(C) // is OK
   171  //   A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call
   172  func (r Responder) Then(next Responder) (x Responder) {
   173  	var done int
   174  	var mu sync.Mutex
   175  	x = func(req *http.Request) (*http.Response, error) {
   176  		mu.Lock()
   177  		defer mu.Unlock()
   178  
   179  		ctx := req.Context()
   180  		thenCalledUs, _ := ctx.Value(fromThenKey).(bool)
   181  		if !thenCalledUs {
   182  			req = req.WithContext(context.WithValue(ctx, fromThenKey, true))
   183  		}
   184  
   185  		switch done {
   186  		case 0:
   187  			resp, err := r(req)
   188  			if err != errThenDone {
   189  				if !x.similar(r) { // r is NOT a Then
   190  					done = 1
   191  				}
   192  				return resp, err
   193  			}
   194  			fallthrough
   195  
   196  		case 1:
   197  			done = 2 // next is NEVER a Then, as it is forbidden by design
   198  			return next(req)
   199  		}
   200  		if thenCalledUs {
   201  			return nil, errThenDone
   202  		}
   203  		return next(req)
   204  	}
   205  
   206  	if next.similar(x) {
   207  		panic("Then() does not accept another Then() Responder as parameter")
   208  	}
   209  	return
   210  }
   211  
   212  // ResponderFromResponse wraps an *http.Response in a Responder.
   213  //
   214  // Be careful, except for responses generated by httpmock
   215  // (NewStringResponse and NewBytesResponse functions) for which there
   216  // is no problems, it is the caller responsibility to ensure the
   217  // response body can be read several times and concurrently if needed,
   218  // as it is shared among all Responder returned responses.
   219  //
   220  // For home-made responses, NewRespBodyFromString and
   221  // NewRespBodyFromBytes functions can be used to produce response
   222  // bodies that can be read several times and concurrently.
   223  func ResponderFromResponse(resp *http.Response) Responder {
   224  	return func(req *http.Request) (*http.Response, error) {
   225  		res := *resp
   226  
   227  		// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
   228  		if body, ok := resp.Body.(*dummyReadCloser); ok {
   229  			res.Body = body.copy()
   230  		}
   231  
   232  		res.Request = req
   233  		return &res, nil
   234  	}
   235  }
   236  
   237  // ResponderFromMultipleResponses wraps an *http.Response list in a Responder.
   238  //
   239  // Each response will be returned in the order of the provided list.
   240  // If the responder is called more than the size of the provided list, an error
   241  // will be thrown.
   242  //
   243  // Be careful, except for responses generated by httpmock
   244  // (NewStringResponse and NewBytesResponse functions) for which there
   245  // is no problems, it is the caller responsibility to ensure the
   246  // response body can be read several times and concurrently if needed,
   247  // as it is shared among all Responder returned responses.
   248  //
   249  // For home-made responses, NewRespBodyFromString and
   250  // NewRespBodyFromBytes functions can be used to produce response
   251  // bodies that can be read several times and concurrently.
   252  //
   253  // If all responses have been returned and fn is passed
   254  // and non-nil, it acts as the fn parameter of NewNotFoundResponder,
   255  // allowing to dump the stack trace to localize the origin of the
   256  // call.
   257  //   import (
   258  //     "github.com/jarcoal/httpmock"
   259  //     "testing"
   260  //   )
   261  //   ...
   262  //   func TestMyApp(t *testing.T) {
   263  //     ...
   264  //     // This responder is callable only once, then an error is returned and
   265  //     // the stacktrace of the call logged using t.Log()
   266  //     httpmock.RegisterResponder("GET", "/foo/bar",
   267  //       httpmock.ResponderFromMultipleResponses(
   268  //         []*http.Response{
   269  //           httpmock.NewStringResponse(200, `{"name":"bar"}`),
   270  //           httpmock.NewStringResponse(404, `{"mesg":"Not found"}`),
   271  //         },
   272  //         t.Log),
   273  //     )
   274  //   }
   275  func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...any)) Responder {
   276  	responseIndex := 0
   277  	mutex := sync.Mutex{}
   278  	return func(req *http.Request) (*http.Response, error) {
   279  		mutex.Lock()
   280  		defer mutex.Unlock()
   281  		defer func() { responseIndex++ }()
   282  		if responseIndex >= len(responses) {
   283  			err := internal.StackTracer{
   284  				Err: fmt.Errorf("not enough responses provided: responder called %d time(s) but %d response(s) provided", responseIndex+1, len(responses)),
   285  			}
   286  			if len(fn) > 0 {
   287  				err.CustomFn = fn[0]
   288  			}
   289  			return nil, err
   290  		}
   291  		res := *responses[responseIndex]
   292  		// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
   293  		if body, ok := responses[responseIndex].Body.(*dummyReadCloser); ok {
   294  			res.Body = body.copy()
   295  		}
   296  
   297  		res.Request = req
   298  		return &res, nil
   299  	}
   300  }
   301  
   302  // NewErrorResponder creates a Responder that returns an empty request and the
   303  // given error. This can be used to e.g. imitate more deep http errors for the
   304  // client.
   305  func NewErrorResponder(err error) Responder {
   306  	return func(req *http.Request) (*http.Response, error) {
   307  		return nil, err
   308  	}
   309  }
   310  
   311  // NewNotFoundResponder creates a Responder typically used in
   312  // conjunction with RegisterNoResponder() function and testing
   313  // package, to be proactive when a Responder is not found. fn is
   314  // called with a unique string parameter containing the name of the
   315  // missing route and the stack trace to localize the origin of the
   316  // call. If fn returns (= if it does not panic), the responder returns
   317  // an error of the form: "Responder not found for GET http://foo.bar/path".
   318  // Note that fn can be nil.
   319  //
   320  // It is useful when writing tests to ensure that all routes have been
   321  // mocked.
   322  //
   323  // Example of use:
   324  //   import (
   325  //     "testing"
   326  //     "github.com/jarcoal/httpmock"
   327  //   )
   328  //   ...
   329  //   func TestMyApp(t *testing.T) {
   330  //      ...
   331  //      // Calls testing.Fatal with the name of Responder-less route and
   332  //      // the stack trace of the call.
   333  //      httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal))
   334  //
   335  // Will abort the current test and print something like:
   336  //   transport_test.go:735: Called from net/http.Get()
   337  //         at /go/src/github.com/jarcoal/httpmock/transport_test.go:714
   338  //       github.com/jarcoal/httpmock.TestCheckStackTracer()
   339  //         at /go/src/testing/testing.go:865
   340  //       testing.tRunner()
   341  //         at /go/src/runtime/asm_amd64.s:1337
   342  func NewNotFoundResponder(fn func(...any)) Responder {
   343  	return func(req *http.Request) (*http.Response, error) {
   344  		var extra string
   345  		suggested, _ := req.Context().Value(suggestedKey).(*suggestedInfo)
   346  		if suggested != nil {
   347  			extra = fmt.Sprintf(`, but one matches %s %q`, suggested.kind, suggested.suggested)
   348  		}
   349  		return nil, internal.StackTracer{
   350  			CustomFn: fn,
   351  			Err:      fmt.Errorf("Responder not found for %s %s%s", req.Method, req.URL, extra),
   352  		}
   353  	}
   354  }
   355  
   356  // NewStringResponse creates an *http.Response with a body based on
   357  // the given string.  Also accepts an http status code.
   358  //
   359  // To pass the content of an existing file as body use httpmock.File as in:
   360  //   httpmock.NewStringResponse(200, httpmock.File("body.txt").String())
   361  func NewStringResponse(status int, body string) *http.Response {
   362  	return &http.Response{
   363  		Status:        strconv.Itoa(status),
   364  		StatusCode:    status,
   365  		Body:          NewRespBodyFromString(body),
   366  		Header:        http.Header{},
   367  		ContentLength: -1,
   368  	}
   369  }
   370  
   371  // NewStringResponder creates a Responder from a given body (as a string) and status code.
   372  //
   373  // To pass the content of an existing file as body use httpmock.File as in:
   374  //   httpmock.NewStringResponder(200, httpmock.File("body.txt").String())
   375  func NewStringResponder(status int, body string) Responder {
   376  	return ResponderFromResponse(NewStringResponse(status, body))
   377  }
   378  
   379  // NewBytesResponse creates an *http.Response with a body based on the
   380  // given bytes.  Also accepts an http status code.
   381  //
   382  // To pass the content of an existing file as body use httpmock.File as in:
   383  //   httpmock.NewBytesResponse(200, httpmock.File("body.raw").Bytes())
   384  func NewBytesResponse(status int, body []byte) *http.Response {
   385  	return &http.Response{
   386  		Status:        strconv.Itoa(status),
   387  		StatusCode:    status,
   388  		Body:          NewRespBodyFromBytes(body),
   389  		Header:        http.Header{},
   390  		ContentLength: -1,
   391  	}
   392  }
   393  
   394  // NewBytesResponder creates a Responder from a given body (as a byte
   395  // slice) and status code.
   396  //
   397  // To pass the content of an existing file as body use httpmock.File as in:
   398  //   httpmock.NewBytesResponder(200, httpmock.File("body.raw").Bytes())
   399  func NewBytesResponder(status int, body []byte) Responder {
   400  	return ResponderFromResponse(NewBytesResponse(status, body))
   401  }
   402  
   403  // NewJsonResponse creates an *http.Response with a body that is a
   404  // json encoded representation of the given any.  Also accepts
   405  // an http status code.
   406  //
   407  // To pass the content of an existing file as body use httpmock.File as in:
   408  //   httpmock.NewJsonResponse(200, httpmock.File("body.json"))
   409  func NewJsonResponse(status int, body any) (*http.Response, error) { // nolint: revive
   410  	encoded, err := json.Marshal(body)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	response := NewBytesResponse(status, encoded)
   415  	response.Header.Set("Content-Type", "application/json")
   416  	return response, nil
   417  }
   418  
   419  // NewJsonResponder creates a Responder from a given body (as an
   420  // any that is encoded to json) and status code.
   421  //
   422  // To pass the content of an existing file as body use httpmock.File as in:
   423  //   httpmock.NewJsonResponder(200, httpmock.File("body.json"))
   424  func NewJsonResponder(status int, body any) (Responder, error) { // nolint: revive
   425  	resp, err := NewJsonResponse(status, body)
   426  	if err != nil {
   427  		return nil, err
   428  	}
   429  	return ResponderFromResponse(resp), nil
   430  }
   431  
   432  // NewJsonResponderOrPanic is like NewJsonResponder but panics in case of error.
   433  //
   434  // It simplifies the call of RegisterResponder, avoiding the use of a
   435  // temporary variable and an error check, and so can be used as
   436  // NewStringResponder or NewBytesResponder in such context:
   437  //   httpmock.RegisterResponder(
   438  //     "GET",
   439  //     "/test/path",
   440  //     httpmock.NewJSONResponderOrPanic(200, &MyBody),
   441  //   )
   442  //
   443  // To pass the content of an existing file as body use httpmock.File as in:
   444  //   httpmock.NewJsonResponderOrPanic(200, httpmock.File("body.json"))
   445  func NewJsonResponderOrPanic(status int, body any) Responder { // nolint: revive
   446  	responder, err := NewJsonResponder(status, body)
   447  	if err != nil {
   448  		panic(err)
   449  	}
   450  	return responder
   451  }
   452  
   453  // NewXmlResponse creates an *http.Response with a body that is an xml
   454  // encoded representation of the given any.  Also accepts an
   455  // http status code.
   456  //
   457  // To pass the content of an existing file as body use httpmock.File as in:
   458  //   httpmock.NewXmlResponse(200, httpmock.File("body.xml"))
   459  func NewXmlResponse(status int, body any) (*http.Response, error) { // nolint: revive
   460  	var (
   461  		encoded []byte
   462  		err     error
   463  	)
   464  	if f, ok := body.(File); ok {
   465  		encoded, err = f.bytes()
   466  	} else {
   467  		encoded, err = xml.Marshal(body)
   468  	}
   469  	if err != nil {
   470  		return nil, err
   471  	}
   472  	response := NewBytesResponse(status, encoded)
   473  	response.Header.Set("Content-Type", "application/xml")
   474  	return response, nil
   475  }
   476  
   477  // NewXmlResponder creates a Responder from a given body (as an
   478  // any that is encoded to xml) and status code.
   479  //
   480  // To pass the content of an existing file as body use httpmock.File as in:
   481  //   httpmock.NewXmlResponder(200, httpmock.File("body.xml"))
   482  func NewXmlResponder(status int, body any) (Responder, error) { // nolint: revive
   483  	resp, err := NewXmlResponse(status, body)
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  	return ResponderFromResponse(resp), nil
   488  }
   489  
   490  // NewXmlResponderOrPanic is like NewXmlResponder but panics in case of error.
   491  //
   492  // It simplifies the call of RegisterResponder, avoiding the use of a
   493  // temporary variable and an error check, and so can be used as
   494  // NewStringResponder or NewBytesResponder in such context:
   495  //   httpmock.RegisterResponder(
   496  //     "GET",
   497  //     "/test/path",
   498  //     httpmock.NewXmlResponderOrPanic(200, &MyBody),
   499  //   )
   500  //
   501  // To pass the content of an existing file as body use httpmock.File as in:
   502  //   httpmock.NewXmlResponderOrPanic(200, httpmock.File("body.xml"))
   503  func NewXmlResponderOrPanic(status int, body any) Responder { // nolint: revive
   504  	responder, err := NewXmlResponder(status, body)
   505  	if err != nil {
   506  		panic(err)
   507  	}
   508  	return responder
   509  }
   510  
   511  // NewRespBodyFromString creates an io.ReadCloser from a string that
   512  // is suitable for use as an http response body.
   513  //
   514  // To pass the content of an existing file as body use httpmock.File as in:
   515  //   httpmock.NewRespBodyFromString(httpmock.File("body.txt").String())
   516  func NewRespBodyFromString(body string) io.ReadCloser {
   517  	return &dummyReadCloser{orig: body}
   518  }
   519  
   520  // NewRespBodyFromBytes creates an io.ReadCloser from a byte slice
   521  // that is suitable for use as an http response body.
   522  //
   523  // To pass the content of an existing file as body use httpmock.File as in:
   524  //   httpmock.NewRespBodyFromBytes(httpmock.File("body.txt").Bytes())
   525  func NewRespBodyFromBytes(body []byte) io.ReadCloser {
   526  	return &dummyReadCloser{orig: body}
   527  }
   528  
   529  type dummyReadCloser struct {
   530  	orig any           // string or []byte
   531  	body io.ReadSeeker // instanciated on demand from orig
   532  }
   533  
   534  // copy returns a new instance resetting d.body to nil.
   535  func (d *dummyReadCloser) copy() *dummyReadCloser {
   536  	return &dummyReadCloser{orig: d.orig}
   537  }
   538  
   539  // setup ensures d.body is correctly initialized.
   540  func (d *dummyReadCloser) setup() {
   541  	if d.body == nil {
   542  		switch body := d.orig.(type) {
   543  		case string:
   544  			d.body = strings.NewReader(body)
   545  		case []byte:
   546  			d.body = bytes.NewReader(body)
   547  		}
   548  	}
   549  }
   550  
   551  func (d *dummyReadCloser) Read(p []byte) (n int, err error) {
   552  	d.setup()
   553  	return d.body.Read(p)
   554  }
   555  
   556  func (d *dummyReadCloser) Close() error {
   557  	d.setup()
   558  	d.body.Seek(0, io.SeekEnd) // nolint: errcheck
   559  	return nil
   560  }
   561  

View as plain text