initial commit
[govpp.git] / vendor / github.com / onsi / gomega / ghttp / handlers.go
1 package ghttp
2
3 import (
4         "encoding/base64"
5         "encoding/json"
6         "fmt"
7         "io/ioutil"
8         "net/http"
9         "net/url"
10         "reflect"
11
12         "github.com/golang/protobuf/proto"
13         . "github.com/onsi/gomega"
14         "github.com/onsi/gomega/types"
15 )
16
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 {
22                         handler(w, req)
23                 }
24         }
25 }
26
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`
29 //
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")
38                 default:
39                         Ω(req.URL.Path).Should(Equal(path), "Path mismatch")
40                 }
41                 if len(rawQuery) > 0 {
42                         values, err := url.ParseQuery(rawQuery[0])
43                         Ω(err).ShouldNot(HaveOccurred(), "Expected RawQuery is malformed")
44
45                         Ω(req.URL.Query()).Should(Equal(values), "RawQuery mismatch")
46                 }
47         }
48 }
49
50 //VerifyContentType returns a handler that verifies that a request has a Content-Type header set to the
51 //specified value
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))
55         }
56 }
57
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")
64
65                 decoded, err := base64.StdEncoding.DecodeString(auth[6:])
66                 Ω(err).ShouldNot(HaveOccurred())
67
68                 Ω(string(decoded)).Should(Equal(fmt.Sprintf("%s:%s", username, password)), "Authorization mismatch")
69         }
70 }
71
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.
74 //
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)
82                 }
83         }
84 }
85
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})
91 }
92
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)
99                         req.Body.Close()
100                         Ω(err).ShouldNot(HaveOccurred())
101                         Ω(body).Should(Equal(expectedBody), "Body Mismatch")
102                 },
103         )
104 }
105
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
108 //
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)
115                         req.Body.Close()
116                         Ω(err).ShouldNot(HaveOccurred())
117                         Ω(body).Should(MatchJSON(expectedJSON), "JSON Mismatch")
118                 },
119         )
120 }
121
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)),
131         )
132 }
133
134 //VerifyForm returns a handler that verifies a request contains the specified form values.
135 //
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) {
140                 err := r.ParseForm()
141                 Ω(err).ShouldNot(HaveOccurred())
142                 for key, vals := range values {
143                         Ω(r.Form[key]).Should(Equal(vals), "Form mismatch for key: %s", key)
144                 }
145         }
146 }
147
148 //VerifyFormKV returns a handler that verifies a request contains a form key with the specified values.
149 //
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})
153 }
154
155 //VerifyProtoRepresenting returns a handler that verifies that the body of the request is a valid protobuf
156 //representation of the passed message.
157 //
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())
165                         req.Body.Close()
166
167                         expectedType := reflect.TypeOf(expected)
168                         actualValuePtr := reflect.New(expectedType.Elem())
169
170                         actual, ok := actualValuePtr.Interface().(proto.Message)
171                         Ω(ok).Should(BeTrue(), "Message value is not a proto.Message")
172
173                         err = proto.Unmarshal(body, actual)
174                         Ω(err).ShouldNot(HaveOccurred(), "Failed to unmarshal protobuf")
175
176                         Ω(actual).Should(Equal(expected), "ProtoBuf Mismatch")
177                 },
178         )
179 }
180
181 func copyHeader(src http.Header, dst http.Header) {
182         for key, value := range src {
183                 dst[key] = value
184         }
185 }
186
187 /*
188 RespondWith returns a handler that responds to a request with the specified status code and body
189
190 Body may be a string or []byte
191
192 Also, RespondWith can be given an optional http.Header.  The headers defined therein will be added to the response headers.
193 */
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())
198                 }
199                 w.WriteHeader(statusCode)
200                 switch x := body.(type) {
201                 case string:
202                         w.Write([]byte(x))
203                 case []byte:
204                         w.Write(x)
205                 default:
206                         Ω(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
207                 }
208         }
209 }
210
211 /*
212 RespondWithPtr returns a handler that responds to a request with the specified status code and body
213
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.
216
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.
219 */
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())
224                 }
225                 w.WriteHeader(*statusCode)
226                 if body != nil {
227                         switch x := (body).(type) {
228                         case *string:
229                                 w.Write([]byte(*x))
230                         case *[]byte:
231                                 w.Write(*x)
232                         default:
233                                 Ω(body).Should(BeNil(), "Invalid type for body.  Should be string or []byte.")
234                         }
235                 }
236         }
237 }
238
239 /*
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
242
243 Also, RespondWithJSONEncoded can be given an optional http.Header.  The headers defined therein will be added to the response headers.
244 */
245 func RespondWithJSONEncoded(statusCode int, object interface{}, optionalHeader ...http.Header) http.HandlerFunc {
246         data, err := json.Marshal(object)
247         Ω(err).ShouldNot(HaveOccurred())
248
249         var headers http.Header
250         if len(optionalHeader) == 1 {
251                 headers = optionalHeader[0]
252         } else {
253                 headers = make(http.Header)
254         }
255         if _, found := headers["Content-Type"]; !found {
256                 headers["Content-Type"] = []string{"application/json"}
257         }
258         return RespondWith(statusCode, string(data), headers)
259 }
260
261 /*
262 RespondWithJSONEncodedPtr behaves like RespondWithJSONEncoded but takes a pointer
263 to a status code and object.
264
265 This allows different tests to share the same setup but specify different status codes and JSON-encoded
266 objects.
267
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.
270 */
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]
278                 } else {
279                         headers = make(http.Header)
280                 }
281                 if _, found := headers["Content-Type"]; !found {
282                         headers["Content-Type"] = []string{"application/json"}
283                 }
284                 copyHeader(headers, w.Header())
285                 w.WriteHeader(*statusCode)
286                 w.Write(data)
287         }
288 }
289
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.
292 //
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())
298
299                 var headers http.Header
300                 if len(optionalHeader) == 1 {
301                         headers = optionalHeader[0]
302                 } else {
303                         headers = make(http.Header)
304                 }
305                 if _, found := headers["Content-Type"]; !found {
306                         headers["Content-Type"] = []string{"application/x-protobuf"}
307                 }
308                 copyHeader(headers, w.Header())
309
310                 w.WriteHeader(statusCode)
311                 w.Write(data)
312         }
313 }