1
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
34
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
44
45
46
47
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
67
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
75
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
83
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
97
98
99
100
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
111
112
113 func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc {
114 return g.VerifyHeader(http.Header{key: values})
115 }
116
117
118
119
120
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
133
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
146
147
148
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
162
163
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
174
175
176
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
188
189
190 func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc {
191 return g.VerifyForm(url.Values{key: values})
192 }
193
194
195
196
197
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
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
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
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
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
331
332
333
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