github.com/onsi/gomega@v1.32.0/ghttp/handlers.go (about) 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 }