...

Source file src/github.com/onsi/gomega/ghttp/handlers.go

Documentation: github.com/onsi/gomega/ghttp

     1  // untested sections: 3
     2  
     3  package ghttp
     4  
     5  import (
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"reflect"
    12  	"strings"
    13  
    14  	"github.com/onsi/gomega"
    15  	. "github.com/onsi/gomega"
    16  	"github.com/onsi/gomega/internal/gutil"
    17  	"github.com/onsi/gomega/types"
    18  	"google.golang.org/protobuf/proto"
    19  	"google.golang.org/protobuf/protoadapt"
    20  	"google.golang.org/protobuf/runtime/protoiface"
    21  )
    22  
    23  type GHTTPWithGomega struct {
    24  	gomega Gomega
    25  }
    26  
    27  func NewGHTTPWithGomega(gomega Gomega) *GHTTPWithGomega {
    28  	return &GHTTPWithGomega{
    29  		gomega: gomega,
    30  	}
    31  }
    32  
    33  // CombineHandler takes variadic list of handlers and produces one handler
    34  // that calls each handler in order.
    35  func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
    36  	return func(w http.ResponseWriter, req *http.Request) {
    37  		for _, handler := range handlers {
    38  			handler(w, req)
    39  		}
    40  	}
    41  }
    42  
    43  // VerifyRequest returns a handler that verifies that a request uses the specified method to connect to the specified path
    44  // You may also pass in an optional rawQuery string which is tested against the request's `req.URL.RawQuery`
    45  //
    46  // For path, you may pass in a string, in which case strict equality will be applied
    47  // Alternatively you can pass in a matcher (ContainSubstring("/foo") and MatchRegexp("/foo/[a-f0-9]+") for example)
    48  func (g GHTTPWithGomega) VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
    49  	return func(w http.ResponseWriter, req *http.Request) {
    50  		g.gomega.Expect(req.Method).Should(Equal(method), "Method mismatch")
    51  		switch p := path.(type) {
    52  		case types.GomegaMatcher:
    53  			g.gomega.Expect(req.URL.Path).Should(p, "Path mismatch")
    54  		default:
    55  			g.gomega.Expect(req.URL.Path).Should(Equal(path), "Path mismatch")
    56  		}
    57  		if len(rawQuery) > 0 {
    58  			values, err := url.ParseQuery(rawQuery[0])
    59  			g.gomega.Expect(err).ShouldNot(HaveOccurred(), "Expected RawQuery is malformed")
    60  
    61  			g.gomega.Expect(req.URL.Query()).Should(Equal(values), "RawQuery mismatch")
    62  		}
    63  	}
    64  }
    65  
    66  // VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
    67  // specified value
    68  func (g GHTTPWithGomega) VerifyContentType(contentType string) http.HandlerFunc {
    69  	return func(w http.ResponseWriter, req *http.Request) {
    70  		g.gomega.Expect(req.Header.Get("Content-Type")).Should(Equal(contentType))
    71  	}
    72  }
    73  
    74  // VerifyMimeType returns a handler that verifies that a request has a specified mime type set
    75  // in Content-Type header
    76  func (g GHTTPWithGomega) VerifyMimeType(mimeType string) http.HandlerFunc {
    77  	return func(w http.ResponseWriter, req *http.Request) {
    78  		g.gomega.Expect(strings.Split(req.Header.Get("Content-Type"), ";")[0]).Should(Equal(mimeType))
    79  	}
    80  }
    81  
    82  // VerifyBasicAuth returns a handler that verifies the request contains a BasicAuth Authorization header
    83  // matching the passed in username and password
    84  func (g GHTTPWithGomega) VerifyBasicAuth(username string, password string) http.HandlerFunc {
    85  	return func(w http.ResponseWriter, req *http.Request) {
    86  		auth := req.Header.Get("Authorization")
    87  		g.gomega.Expect(auth).ShouldNot(Equal(""), "Authorization header must be specified")
    88  
    89  		decoded, err := base64.StdEncoding.DecodeString(auth[6:])
    90  		g.gomega.Expect(err).ShouldNot(HaveOccurred())
    91  
    92  		g.gomega.Expect(string(decoded)).Should(Equal(fmt.Sprintf("%s:%s", username, password)), "Authorization mismatch")
    93  	}
    94  }
    95  
    96  // VerifyHeader returns a handler that verifies the request contains the passed in headers.
    97  // The passed in header keys are first canonicalized via http.CanonicalHeaderKey.
    98  //
    99  // The request must contain *all* the passed in headers, but it is allowed to have additional headers
   100  // beyond the passed in set.
   101  func (g GHTTPWithGomega) VerifyHeader(header http.Header) http.HandlerFunc {
   102  	return func(w http.ResponseWriter, req *http.Request) {
   103  		for key, values := range header {
   104  			key = http.CanonicalHeaderKey(key)
   105  			g.gomega.Expect(req.Header[key]).Should(Equal(values), "Header mismatch for key: %s", key)
   106  		}
   107  	}
   108  }
   109  
   110  // VerifyHeaderKV returns a handler that verifies the request contains a header matching the passed in key and values
   111  // (recall that a `http.Header` is a mapping from string (key) to []string (values))
   112  // It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
   113  func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
   114  	return g.VerifyHeader(http.Header{key: values})
   115  }
   116  
   117  // VerifyHost returns a handler that verifies the host of a request matches the expected host
   118  // Host is a special header in net/http, which is not set on the request.Header but rather on the Request itself
   119  //
   120  // Host may be a string or a matcher
   121  func (g GHTTPWithGomega) VerifyHost(host interface{}) http.HandlerFunc {
   122  	return func(w http.ResponseWriter, req *http.Request) {
   123  		switch p := host.(type) {
   124  		case types.GomegaMatcher:
   125  			g.gomega.Expect(req.Host).Should(p, "Host mismatch")
   126  		default:
   127  			g.gomega.Expect(req.Host).Should(Equal(host), "Host mismatch")
   128  		}
   129  	}
   130  }
   131  
   132  // VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
   133  // It does this using Equal().
   134  func (g GHTTPWithGomega) VerifyBody(expectedBody []byte) http.HandlerFunc {
   135  	return CombineHandlers(
   136  		func(w http.ResponseWriter, req *http.Request) {
   137  			body, err := gutil.ReadAll(req.Body)
   138  			req.Body.Close()
   139  			g.gomega.Expect(err).ShouldNot(HaveOccurred())
   140  			g.gomega.Expect(body).Should(Equal(expectedBody), "Body Mismatch")
   141  		},
   142  	)
   143  }
   144  
   145  // VerifyJSON returns a handler that verifies that the body of the request is a valid JSON representation
   146  // matching the passed in JSON string.  It does this using Gomega's MatchJSON method
   147  //
   148  // VerifyJSON also verifies that the request's content type is application/json
   149  func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc {
   150  	return CombineHandlers(
   151  		g.VerifyMimeType("application/json"),
   152  		func(w http.ResponseWriter, req *http.Request) {
   153  			body, err := gutil.ReadAll(req.Body)
   154  			req.Body.Close()
   155  			g.gomega.Expect(err).ShouldNot(HaveOccurred())
   156  			g.gomega.Expect(body).Should(MatchJSON(expectedJSON), "JSON Mismatch")
   157  		},
   158  	)
   159  }
   160  
   161  // VerifyJSONRepresenting is similar to VerifyJSON.  Instead of taking a JSON string, however, it
   162  // takes an arbitrary JSON-encodable object and verifies that the requests's body is a JSON representation
   163  // that matches the object
   164  func (g GHTTPWithGomega) VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
   165  	data, err := json.Marshal(object)
   166  	g.gomega.Expect(err).ShouldNot(HaveOccurred())
   167  	return CombineHandlers(
   168  		g.VerifyMimeType("application/json"),
   169  		g.VerifyJSON(string(data)),
   170  	)
   171  }
   172  
   173  // VerifyForm returns a handler that verifies a request contains the specified form values.
   174  //
   175  // The request must contain *all* of the specified values, but it is allowed to have additional
   176  // form values beyond the passed in set.
   177  func (g GHTTPWithGomega) VerifyForm(values url.Values) http.HandlerFunc {
   178  	return func(w http.ResponseWriter, r *http.Request) {
   179  		err := r.ParseForm()
   180  		g.gomega.Expect(err).ShouldNot(HaveOccurred())
   181  		for key, vals := range values {
   182  			g.gomega.Expect(r.Form[key]).Should(Equal(vals), "Form mismatch for key: %s", key)
   183  		}
   184  	}
   185  }
   186  
   187  // VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
   188  //
   189  // It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
   190  func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc {
   191  	return g.VerifyForm(url.Values{key: values})
   192  }
   193  
   194  // VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
   195  // representation of the passed message.
   196  //
   197  // VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
   198  func (g GHTTPWithGomega) VerifyProtoRepresenting(expected protoiface.MessageV1) http.HandlerFunc {
   199  	return CombineHandlers(
   200  		g.VerifyContentType("application/x-protobuf"),
   201  		func(w http.ResponseWriter, req *http.Request) {
   202  			body, err := gutil.ReadAll(req.Body)
   203  			g.gomega.Expect(err).ShouldNot(HaveOccurred())
   204  			req.Body.Close()
   205  
   206  			expectedType := reflect.TypeOf(expected)
   207  			actualValuePtr := reflect.New(expectedType.Elem())
   208  
   209  			actual, ok := actualValuePtr.Interface().(protoiface.MessageV1)
   210  			g.gomega.Expect(ok).Should(BeTrueBecause("Message value should be a protoiface.MessageV1"))
   211  
   212  			err = proto.Unmarshal(body, protoadapt.MessageV2Of(actual))
   213  			g.gomega.Expect(err).ShouldNot(HaveOccurred(), "Failed to unmarshal protobuf")
   214  
   215  			g.gomega.Expect(proto.Equal(protoadapt.MessageV2Of(expected), protoadapt.MessageV2Of(actual))).
   216  				Should(BeTrue(), "ProtoBuf Mismatch")
   217  		},
   218  	)
   219  }
   220  
   221  func copyHeader(src http.Header, dst http.Header) {
   222  	for key, value := range src {
   223  		dst[key] = value
   224  	}
   225  }
   226  
   227  /*
   228  RespondWith returns a handler that responds to a request with the specified status code and body
   229  
   230  Body may be a string or []byte
   231  
   232  Also, RespondWith can be given an optional http.Header.  The headers defined therein will be added to the response headers.
   233  */
   234  func (g GHTTPWithGomega) RespondWith(statusCode int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   235  	return func(w http.ResponseWriter, req *http.Request) {
   236  		if len(optionalHeader) == 1 {
   237  			copyHeader(optionalHeader[0], w.Header())
   238  		}
   239  		w.WriteHeader(statusCode)
   240  		switch x := body.(type) {
   241  		case string:
   242  			w.Write([]byte(x))
   243  		case []byte:
   244  			w.Write(x)
   245  		default:
   246  			g.gomega.Expect(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
   247  		}
   248  	}
   249  }
   250  
   251  /*
   252  RespondWithPtr returns a handler that responds to a request with the specified status code and body
   253  
   254  Unlike RespondWith, you pass RepondWithPtr a pointer to the status code and body allowing different tests
   255  to share the same setup but specify different status codes and bodies.
   256  
   257  Also, RespondWithPtr can be given an optional http.Header.  The headers defined therein will be added to the response headers.
   258  Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
   259  */
   260  func (g GHTTPWithGomega) RespondWithPtr(statusCode *int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   261  	return func(w http.ResponseWriter, req *http.Request) {
   262  		if len(optionalHeader) == 1 {
   263  			copyHeader(optionalHeader[0], w.Header())
   264  		}
   265  		w.WriteHeader(*statusCode)
   266  		if body != nil {
   267  			switch x := (body).(type) {
   268  			case *string:
   269  				w.Write([]byte(*x))
   270  			case *[]byte:
   271  				w.Write(*x)
   272  			default:
   273  				g.gomega.Expect(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
   274  			}
   275  		}
   276  	}
   277  }
   278  
   279  /*
   280  RespondWithJSONEncoded returns a handler that responds to a request with the specified status code and a body
   281  containing the JSON-encoding of the passed in object
   282  
   283  Also, RespondWithJSONEncoded can be given an optional http.Header.  The headers defined therein will be added to the response headers.
   284  */
   285  func (g GHTTPWithGomega) RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   286  	data, err := json.Marshal(object)
   287  	g.gomega.Expect(err).ShouldNot(HaveOccurred())
   288  
   289  	var headers http.Header
   290  	if len(optionalHeader) == 1 {
   291  		headers = optionalHeader[0]
   292  	} else {
   293  		headers = make(http.Header)
   294  	}
   295  	if _, found := headers["Content-Type"]; !found {
   296  		headers["Content-Type"] = []string{"application/json"}
   297  	}
   298  	return RespondWith(statusCode, string(data), headers)
   299  }
   300  
   301  /*
   302  RespondWithJSONEncodedPtr behaves like RespondWithJSONEncoded but takes a pointer
   303  to a status code and object.
   304  
   305  This allows different tests to share the same setup but specify different status codes and JSON-encoded
   306  objects.
   307  
   308  Also, RespondWithJSONEncodedPtr can be given an optional http.Header.  The headers defined therein will be added to the response headers.
   309  Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
   310  */
   311  func (g GHTTPWithGomega) RespondWithJSONEncodedPtr(statusCode *int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   312  	return func(w http.ResponseWriter, req *http.Request) {
   313  		data, err := json.Marshal(object)
   314  		g.gomega.Expect(err).ShouldNot(HaveOccurred())
   315  		var headers http.Header
   316  		if len(optionalHeader) == 1 {
   317  			headers = optionalHeader[0]
   318  		} else {
   319  			headers = make(http.Header)
   320  		}
   321  		if _, found := headers["Content-Type"]; !found {
   322  			headers["Content-Type"] = []string{"application/json"}
   323  		}
   324  		copyHeader(headers, w.Header())
   325  		w.WriteHeader(*statusCode)
   326  		w.Write(data)
   327  	}
   328  }
   329  
   330  // RespondWithProto returns a handler that responds to a request with the specified status code and a body
   331  // containing the protobuf serialization of the provided message.
   332  //
   333  // Also, RespondWithProto can be given an optional http.Header.  The headers defined therein will be added to the response headers.
   334  func (g GHTTPWithGomega) RespondWithProto(statusCode int, message protoadapt.MessageV1, optionalHeader ...http.Header) http.HandlerFunc {
   335  	return func(w http.ResponseWriter, req *http.Request) {
   336  		data, err := proto.Marshal(protoadapt.MessageV2Of(message))
   337  		g.gomega.Expect(err).ShouldNot(HaveOccurred())
   338  
   339  		var headers http.Header
   340  		if len(optionalHeader) == 1 {
   341  			headers = optionalHeader[0]
   342  		} else {
   343  			headers = make(http.Header)
   344  		}
   345  		if _, found := headers["Content-Type"]; !found {
   346  			headers["Content-Type"] = []string{"application/x-protobuf"}
   347  		}
   348  		copyHeader(headers, w.Header())
   349  
   350  		w.WriteHeader(statusCode)
   351  		w.Write(data)
   352  	}
   353  }
   354  
   355  func VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
   356  	return NewGHTTPWithGomega(gomega.Default).VerifyRequest(method, path, rawQuery...)
   357  }
   358  
   359  func VerifyContentType(contentType string) http.HandlerFunc {
   360  	return NewGHTTPWithGomega(gomega.Default).VerifyContentType(contentType)
   361  }
   362  
   363  func VerifyMimeType(mimeType string) http.HandlerFunc {
   364  	return NewGHTTPWithGomega(gomega.Default).VerifyMimeType(mimeType)
   365  }
   366  
   367  func VerifyBasicAuth(username string, password string) http.HandlerFunc {
   368  	return NewGHTTPWithGomega(gomega.Default).VerifyBasicAuth(username, password)
   369  }
   370  
   371  func VerifyHeader(header http.Header) http.HandlerFunc {
   372  	return NewGHTTPWithGomega(gomega.Default).VerifyHeader(header)
   373  }
   374  
   375  func VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
   376  	return NewGHTTPWithGomega(gomega.Default).VerifyHeaderKV(key, values...)
   377  }
   378  
   379  func VerifyHost(host interface{}) http.HandlerFunc {
   380  	return NewGHTTPWithGomega(gomega.Default).VerifyHost(host)
   381  }
   382  
   383  func VerifyBody(expectedBody []byte) http.HandlerFunc {
   384  	return NewGHTTPWithGomega(gomega.Default).VerifyBody(expectedBody)
   385  }
   386  
   387  func VerifyJSON(expectedJSON string) http.HandlerFunc {
   388  	return NewGHTTPWithGomega(gomega.Default).VerifyJSON(expectedJSON)
   389  }
   390  
   391  func VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
   392  	return NewGHTTPWithGomega(gomega.Default).VerifyJSONRepresenting(object)
   393  }
   394  
   395  func VerifyForm(values url.Values) http.HandlerFunc {
   396  	return NewGHTTPWithGomega(gomega.Default).VerifyForm(values)
   397  }
   398  
   399  func VerifyFormKV(key string, values ...string) http.HandlerFunc {
   400  	return NewGHTTPWithGomega(gomega.Default).VerifyFormKV(key, values...)
   401  }
   402  
   403  func VerifyProtoRepresenting(expected protoiface.MessageV1) http.HandlerFunc {
   404  	return NewGHTTPWithGomega(gomega.Default).VerifyProtoRepresenting(expected)
   405  }
   406  
   407  func RespondWith(statusCode int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   408  	return NewGHTTPWithGomega(gomega.Default).RespondWith(statusCode, body, optionalHeader...)
   409  }
   410  
   411  func RespondWithPtr(statusCode *int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   412  	return NewGHTTPWithGomega(gomega.Default).RespondWithPtr(statusCode, body, optionalHeader...)
   413  }
   414  
   415  func RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   416  	return NewGHTTPWithGomega(gomega.Default).RespondWithJSONEncoded(statusCode, object, optionalHeader...)
   417  }
   418  
   419  func RespondWithJSONEncodedPtr(statusCode *int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
   420  	return NewGHTTPWithGomega(gomega.Default).RespondWithJSONEncodedPtr(statusCode, object, optionalHeader...)
   421  }
   422  
   423  func RespondWithProto(statusCode int, message protoadapt.MessageV1, optionalHeader ...http.Header) http.HandlerFunc {
   424  	return NewGHTTPWithGomega(gomega.Default).RespondWithProto(statusCode, message, optionalHeader...)
   425  }
   426  

View as plain text