12 "github.com/golang/protobuf/proto"
13 . "github.com/onsi/gomega"
14 "github.com/onsi/gomega/types"
17 //CombineHandler takes variadic list of handlers and produces one handler
18 //that calls each handler in order.
19 func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
20 return func(w http.ResponseWriter, req *http.Request) {
21 for _, handler := range handlers {
27 //VerifyRequest returns a handler that verifies that a request uses the specified method to connect to the specified path
28 //You may also pass in an optional rawQuery string which is tested against the request's `req.URL.RawQuery`
30 //For path, you may pass in a string, in which case strict equality will be applied
31 //Alternatively you can pass in a matcher (ContainSubstring("/foo") and MatchRegexp("/foo/[a-f0-9]+") for example)
32 func VerifyRequest(method string, path interface{}, rawQuery ...string) http.HandlerFunc {
33 return func(w http.ResponseWriter, req *http.Request) {
34 Ω(req.Method).Should(Equal(method), "Method mismatch")
35 switch p := path.(type) {
36 case types.GomegaMatcher:
37 Ω(req.URL.Path).Should(p, "Path mismatch")
39 Ω(req.URL.Path).Should(Equal(path), "Path mismatch")
41 if len(rawQuery) > 0 {
42 values, err := url.ParseQuery(rawQuery[0])
43 Ω(err).ShouldNot(HaveOccurred(), "Expected RawQuery is malformed")
45 Ω(req.URL.Query()).Should(Equal(values), "RawQuery mismatch")
50 //VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
52 func VerifyContentType(contentType string) http.HandlerFunc {
53 return func(w http.ResponseWriter, req *http.Request) {
54 Ω(req.Header.Get("Content-Type")).Should(Equal(contentType))
58 //VerifyBasicAuth returns a handler that verifies the request contains a BasicAuth Authorization header
59 //matching the passed in username and password
60 func VerifyBasicAuth(username string, password string) http.HandlerFunc {
61 return func(w http.ResponseWriter, req *http.Request) {
62 auth := req.Header.Get("Authorization")
63 Ω(auth).ShouldNot(Equal(""), "Authorization header must be specified")
65 decoded, err := base64.StdEncoding.DecodeString(auth[6:])
66 Ω(err).ShouldNot(HaveOccurred())
68 Ω(string(decoded)).Should(Equal(fmt.Sprintf("%s:%s", username, password)), "Authorization mismatch")
72 //VerifyHeader returns a handler that verifies the request contains the passed in headers.
73 //The passed in header keys are first canonicalized via http.CanonicalHeaderKey.
75 //The request must contain *all* the passed in headers, but it is allowed to have additional headers
76 //beyond the passed in set.
77 func VerifyHeader(header http.Header) http.HandlerFunc {
78 return func(w http.ResponseWriter, req *http.Request) {
79 for key, values := range header {
80 key = http.CanonicalHeaderKey(key)
81 Ω(req.Header[key]).Should(Equal(values), "Header mismatch for key: %s", key)
86 //VerifyHeaderKV returns a handler that verifies the request contains a header matching the passed in key and values
87 //(recall that a `http.Header` is a mapping from string (key) to []string (values))
88 //It is a convenience wrapper around `VerifyHeader` that allows you to avoid having to create an `http.Header` object.
89 func VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
90 return VerifyHeader(http.Header{key: values})
93 //VerifyBody returns a handler that verifies that the body of the request matches the passed in byte array.
94 //It does this using Equal().
95 func VerifyBody(expectedBody []byte) http.HandlerFunc {
96 return CombineHandlers(
97 func(w http.ResponseWriter, req *http.Request) {
98 body, err := ioutil.ReadAll(req.Body)
100 Ω(err).ShouldNot(HaveOccurred())
101 Ω(body).Should(Equal(expectedBody), "Body Mismatch")
106 //VerifyJSON returns a handler that verifies that the body of the request is a valid JSON representation
107 //matching the passed in JSON string. It does this using Gomega's MatchJSON method
109 //VerifyJSON also verifies that the request's content type is application/json
110 func VerifyJSON(expectedJSON string) http.HandlerFunc {
111 return CombineHandlers(
112 VerifyContentType("application/json"),
113 func(w http.ResponseWriter, req *http.Request) {
114 body, err := ioutil.ReadAll(req.Body)
116 Ω(err).ShouldNot(HaveOccurred())
117 Ω(body).Should(MatchJSON(expectedJSON), "JSON Mismatch")
122 //VerifyJSONRepresenting is similar to VerifyJSON. Instead of taking a JSON string, however, it
123 //takes an arbitrary JSON-encodable object and verifies that the requests's body is a JSON representation
124 //that matches the object
125 func VerifyJSONRepresenting(object interface{}) http.HandlerFunc {
126 data, err := json.Marshal(object)
127 Ω(err).ShouldNot(HaveOccurred())
128 return CombineHandlers(
129 VerifyContentType("application/json"),
130 VerifyJSON(string(data)),
134 //VerifyForm returns a handler that verifies a request contains the specified form values.
136 //The request must contain *all* of the specified values, but it is allowed to have additional
137 //form values beyond the passed in set.
138 func VerifyForm(values url.Values) http.HandlerFunc {
139 return func(w http.ResponseWriter, r *http.Request) {
141 Ω(err).ShouldNot(HaveOccurred())
142 for key, vals := range values {
143 Ω(r.Form[key]).Should(Equal(vals), "Form mismatch for key: %s", key)
148 //VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
150 //It is a convenience wrapper around `VerifyForm` that lets you avoid having to create a `url.Values` object.
151 func VerifyFormKV(key string, values ...string) http.HandlerFunc {
152 return VerifyForm(url.Values{key: values})
155 //VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
156 //representation of the passed message.
158 //VerifyProtoRepresenting also verifies that the request's content type is application/x-protobuf
159 func VerifyProtoRepresenting(expected proto.Message) http.HandlerFunc {
160 return CombineHandlers(
161 VerifyContentType("application/x-protobuf"),
162 func(w http.ResponseWriter, req *http.Request) {
163 body, err := ioutil.ReadAll(req.Body)
164 Ω(err).ShouldNot(HaveOccurred())
167 expectedType := reflect.TypeOf(expected)
168 actualValuePtr := reflect.New(expectedType.Elem())
170 actual, ok := actualValuePtr.Interface().(proto.Message)
171 Ω(ok).Should(BeTrue(), "Message value is not a proto.Message")
173 err = proto.Unmarshal(body, actual)
174 Ω(err).ShouldNot(HaveOccurred(), "Failed to unmarshal protobuf")
176 Ω(actual).Should(Equal(expected), "ProtoBuf Mismatch")
181 func copyHeader(src http.Header, dst http.Header) {
182 for key, value := range src {
188 RespondWith returns a handler that responds to a request with the specified status code and body
190 Body may be a string or []byte
192 Also, RespondWith can be given an optional http.Header. The headers defined therein will be added to the response headers.
194 func RespondWith(statusCode int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
195 return func(w http.ResponseWriter, req *http.Request) {
196 if len(optionalHeader) == 1 {
197 copyHeader(optionalHeader[0], w.Header())
199 w.WriteHeader(statusCode)
200 switch x := body.(type) {
206 Ω(body).Should(BeNil(), "Invalid type for body. Should be string or []byte.")
212 RespondWithPtr returns a handler that responds to a request with the specified status code and body
214 Unlike RespondWith, you pass RepondWithPtr a pointer to the status code and body allowing different tests
215 to share the same setup but specify different status codes and bodies.
217 Also, RespondWithPtr can be given an optional http.Header. The headers defined therein will be added to the response headers.
218 Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
220 func RespondWithPtr(statusCode *int, body interface{}, optionalHeader ...http.Header) http.HandlerFunc {
221 return func(w http.ResponseWriter, req *http.Request) {
222 if len(optionalHeader) == 1 {
223 copyHeader(optionalHeader[0], w.Header())
225 w.WriteHeader(*statusCode)
227 switch x := (body).(type) {
233 Ω(body).Should(BeNil(), "Invalid type for body. Should be string or []byte.")
240 RespondWithJSONEncoded returns a handler that responds to a request with the specified status code and a body
241 containing the JSON-encoding of the passed in object
243 Also, RespondWithJSONEncoded can be given an optional http.Header. The headers defined therein will be added to the response headers.
245 func RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
246 data, err := json.Marshal(object)
247 Ω(err).ShouldNot(HaveOccurred())
249 var headers http.Header
250 if len(optionalHeader) == 1 {
251 headers = optionalHeader[0]
253 headers = make(http.Header)
255 if _, found := headers["Content-Type"]; !found {
256 headers["Content-Type"] = []string{"application/json"}
258 return RespondWith(statusCode, string(data), headers)
262 RespondWithJSONEncodedPtr behaves like RespondWithJSONEncoded but takes a pointer
263 to a status code and object.
265 This allows different tests to share the same setup but specify different status codes and JSON-encoded
268 Also, RespondWithJSONEncodedPtr can be given an optional http.Header. The headers defined therein will be added to the response headers.
269 Since the http.Header can be mutated after the fact you don't need to pass in a pointer.
271 func RespondWithJSONEncodedPtr(statusCode *int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
272 return func(w http.ResponseWriter, req *http.Request) {
273 data, err := json.Marshal(object)
274 Ω(err).ShouldNot(HaveOccurred())
275 var headers http.Header
276 if len(optionalHeader) == 1 {
277 headers = optionalHeader[0]
279 headers = make(http.Header)
281 if _, found := headers["Content-Type"]; !found {
282 headers["Content-Type"] = []string{"application/json"}
284 copyHeader(headers, w.Header())
285 w.WriteHeader(*statusCode)
290 //RespondWithProto returns a handler that responds to a request with the specified status code and a body
291 //containing the protobuf serialization of the provided message.
293 //Also, RespondWithProto can be given an optional http.Header. The headers defined therein will be added to the response headers.
294 func RespondWithProto(statusCode int, message proto.Message, optionalHeader ...http.Header) http.HandlerFunc {
295 return func(w http.ResponseWriter, req *http.Request) {
296 data, err := proto.Marshal(message)
297 Ω(err).ShouldNot(HaveOccurred())
299 var headers http.Header
300 if len(optionalHeader) == 1 {
301 headers = optionalHeader[0]
303 headers = make(http.Header)
305 if _, found := headers["Content-Type"]; !found {
306 headers["Content-Type"] = []string{"application/x-protobuf"}
308 copyHeader(headers, w.Header())
310 w.WriteHeader(statusCode)