1 /* 2 Package ghttp supports testing HTTP clients by providing a test server (simply a thin wrapper around httptest's server) that supports 3 registering multiple handlers. Incoming requests are not routed between the different handlers 4 - rather it is merely the order of the handlers that matters. The first request is handled by the first 5 registered handler, the second request by the second handler, etc. 6 7 The intent here is to have each handler *verify* that the incoming request is valid. To accomplish, ghttp 8 also provides a collection of bite-size handlers that each perform one aspect of request verification. These can 9 be composed together and registered with a ghttp server. The result is an expressive language for describing 10 the requests generated by the client under test. 11 12 Here's a simple example, note that the server handler is only defined in one BeforeEach and then modified, as required, by the nested BeforeEaches. 13 A more comprehensive example is available at https://onsi.github.io/gomega/#_testing_http_clients 14 15 var _ = Describe("A Sprockets Client", func() { 16 var server *ghttp.Server 17 var client *SprocketClient 18 BeforeEach(func() { 19 server = ghttp.NewServer() 20 client = NewSprocketClient(server.URL(), "skywalker", "tk427") 21 }) 22 23 AfterEach(func() { 24 server.Close() 25 }) 26 27 Describe("fetching sprockets", func() { 28 var statusCode int 29 var sprockets []Sprocket 30 BeforeEach(func() { 31 statusCode = http.StatusOK 32 sprockets = []Sprocket{} 33 server.AppendHandlers(ghttp.CombineHandlers( 34 ghttp.VerifyRequest("GET", "/sprockets"), 35 ghttp.VerifyBasicAuth("skywalker", "tk427"), 36 ghttp.RespondWithJSONEncodedPtr(&statusCode, &sprockets), 37 )) 38 }) 39 40 When("requesting all sprockets", func() { 41 When("the response is successful", func() { 42 BeforeEach(func() { 43 sprockets = []Sprocket{ 44 NewSprocket("Alfalfa"), 45 NewSprocket("Banana"), 46 } 47 }) 48 49 It("should return the returned sprockets", func() { 50 Expect(client.Sprockets()).Should(Equal(sprockets)) 51 }) 52 }) 53 54 When("the response is missing", func() { 55 BeforeEach(func() { 56 statusCode = http.StatusNotFound 57 }) 58 59 It("should return an empty list of sprockets", func() { 60 Expect(client.Sprockets()).Should(BeEmpty()) 61 }) 62 }) 63 64 When("the response fails to authenticate", func() { 65 BeforeEach(func() { 66 statusCode = http.StatusUnauthorized 67 }) 68 69 It("should return an AuthenticationError error", func() { 70 sprockets, err := client.Sprockets() 71 Expect(sprockets).Should(BeEmpty()) 72 Expect(err).Should(MatchError(AuthenticationError)) 73 }) 74 }) 75 76 When("the response is a server failure", func() { 77 BeforeEach(func() { 78 statusCode = http.StatusInternalServerError 79 }) 80 81 It("should return an InternalError error", func() { 82 sprockets, err := client.Sprockets() 83 Expect(sprockets).Should(BeEmpty()) 84 Expect(err).Should(MatchError(InternalError)) 85 }) 86 }) 87 }) 88 89 When("requesting some sprockets", func() { 90 BeforeEach(func() { 91 sprockets = []Sprocket{ 92 NewSprocket("Alfalfa"), 93 NewSprocket("Banana"), 94 } 95 96 server.WrapHandler(0, ghttp.VerifyRequest("GET", "/sprockets", "filter=FOOD")) 97 }) 98 99 It("should make the request with a filter", func() { 100 Expect(client.Sprockets("food")).Should(Equal(sprockets)) 101 }) 102 }) 103 }) 104 }) 105 */ 106 107 // untested sections: 5 108 109 package ghttp 110 111 import ( 112 "fmt" 113 "io" 114 "net/http" 115 "net/http/httptest" 116 "net/http/httputil" 117 "reflect" 118 "regexp" 119 "strings" 120 "sync" 121 122 . "github.com/onsi/gomega" 123 "github.com/onsi/gomega/internal/gutil" 124 ) 125 126 func new() *Server { 127 return &Server{ 128 AllowUnhandledRequests: false, 129 UnhandledRequestStatusCode: http.StatusInternalServerError, 130 rwMutex: &sync.RWMutex{}, 131 } 132 } 133 134 type routedHandler struct { 135 method string 136 pathRegexp *regexp.Regexp 137 path string 138 handler http.HandlerFunc 139 } 140 141 // NewServer returns a new `*ghttp.Server` that wraps an `httptest` server. The server is started automatically. 142 func NewServer() *Server { 143 s := new() 144 s.HTTPTestServer = httptest.NewServer(s) 145 return s 146 } 147 148 // NewUnstartedServer return a new, unstarted, `*ghttp.Server`. Useful for specifying a custom listener on `server.HTTPTestServer`. 149 func NewUnstartedServer() *Server { 150 s := new() 151 s.HTTPTestServer = httptest.NewUnstartedServer(s) 152 return s 153 } 154 155 // NewTLSServer returns a new `*ghttp.Server` that wraps an `httptest` TLS server. The server is started automatically. 156 func NewTLSServer() *Server { 157 s := new() 158 s.HTTPTestServer = httptest.NewTLSServer(s) 159 return s 160 } 161 162 type Server struct { 163 //The underlying httptest server 164 HTTPTestServer *httptest.Server 165 166 //Defaults to false. If set to true, the Server will allow more requests than there are registered handlers. 167 //Direct use of this property is deprecated and is likely to be removed, use GetAllowUnhandledRequests and SetAllowUnhandledRequests instead. 168 AllowUnhandledRequests bool 169 170 //The status code returned when receiving an unhandled request. 171 //Defaults to http.StatusInternalServerError. 172 //Only applies if AllowUnhandledRequests is true 173 //Direct use of this property is deprecated and is likely to be removed, use GetUnhandledRequestStatusCode and SetUnhandledRequestStatusCode instead. 174 UnhandledRequestStatusCode int 175 176 //If provided, ghttp will log about each request received to the provided io.Writer 177 //Defaults to nil 178 //If you're using Ginkgo, set this to GinkgoWriter to get improved output during failures 179 Writer io.Writer 180 181 receivedRequests []*http.Request 182 requestHandlers []http.HandlerFunc 183 routedHandlers []routedHandler 184 185 rwMutex *sync.RWMutex 186 calls int 187 } 188 189 //Start() starts an unstarted ghttp server. It is a catastrophic error to call Start more than once (thanks, httptest). 190 func (s *Server) Start() { 191 s.HTTPTestServer.Start() 192 } 193 194 //URL() returns a url that will hit the server 195 func (s *Server) URL() string { 196 s.rwMutex.RLock() 197 defer s.rwMutex.RUnlock() 198 return s.HTTPTestServer.URL 199 } 200 201 //Addr() returns the address on which the server is listening. 202 func (s *Server) Addr() string { 203 s.rwMutex.RLock() 204 defer s.rwMutex.RUnlock() 205 return s.HTTPTestServer.Listener.Addr().String() 206 } 207 208 //Close() should be called at the end of each test. It spins down and cleans up the test server. 209 func (s *Server) Close() { 210 s.rwMutex.Lock() 211 server := s.HTTPTestServer 212 s.HTTPTestServer = nil 213 s.rwMutex.Unlock() 214 215 if server != nil { 216 server.Close() 217 } 218 } 219 220 //ServeHTTP() makes Server an http.Handler 221 //When the server receives a request it handles the request in the following order: 222 // 223 //1. If the request matches a handler registered with RouteToHandler, that handler is called. 224 //2. Otherwise, if there are handlers registered via AppendHandlers, those handlers are called in order. 225 //3. If all registered handlers have been called then: 226 // a) If AllowUnhandledRequests is set to true, the request will be handled with response code of UnhandledRequestStatusCode 227 // b) If AllowUnhandledRequests is false, the request will not be handled and the current test will be marked as failed. 228 func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { 229 s.rwMutex.Lock() 230 defer func() { 231 e := recover() 232 if e != nil { 233 w.WriteHeader(http.StatusInternalServerError) 234 } 235 236 //If the handler panics GHTTP will silently succeed. This is badâ„¢. 237 //To catch this case we need to fail the test if the handler has panicked. 238 //However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an assertion failed) 239 //then we shouldn't double-report the error as this will confuse people. 240 241 //So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure 242 eAsString, ok := e.(string) 243 if ok && strings.Contains(eAsString, "defer GinkgoRecover()") { 244 return 245 } 246 247 //If we're here, we have to do step 2: assert that the error is nil. This assertion will 248 //allow us to fail the test suite (note: we can't call Fail since Gomega is not allowed to import Ginkgo). 249 //Since a failed assertion throws a panic, and we are likely in a goroutine, we need to defer within our defer! 250 defer func() { 251 recover() 252 }() 253 Expect(e).Should(BeNil(), "Handler Panicked") 254 }() 255 256 if s.Writer != nil { 257 s.Writer.Write([]byte(fmt.Sprintf("GHTTP Received Request: %s - %s\n", req.Method, req.URL))) 258 } 259 260 s.receivedRequests = append(s.receivedRequests, req) 261 if routedHandler, ok := s.handlerForRoute(req.Method, req.URL.Path); ok { 262 s.rwMutex.Unlock() 263 routedHandler(w, req) 264 } else if s.calls < len(s.requestHandlers) { 265 h := s.requestHandlers[s.calls] 266 s.calls++ 267 s.rwMutex.Unlock() 268 h(w, req) 269 } else { 270 s.rwMutex.Unlock() 271 if s.GetAllowUnhandledRequests() { 272 gutil.ReadAll(req.Body) 273 req.Body.Close() 274 w.WriteHeader(s.GetUnhandledRequestStatusCode()) 275 } else { 276 formatted, err := httputil.DumpRequest(req, true) 277 Expect(err).NotTo(HaveOccurred(), "Encountered error while dumping HTTP request") 278 Expect(string(formatted)).Should(BeNil(), "Received Unhandled Request") 279 } 280 } 281 } 282 283 //ReceivedRequests is an array containing all requests received by the server (both handled and unhandled requests) 284 func (s *Server) ReceivedRequests() []*http.Request { 285 s.rwMutex.RLock() 286 defer s.rwMutex.RUnlock() 287 288 return s.receivedRequests 289 } 290 291 //RouteToHandler can be used to register handlers that will always handle requests that match 292 //the passed in method and path. 293 // 294 //The path may be either a string object or a *regexp.Regexp. 295 func (s *Server) RouteToHandler(method string, path interface{}, handler http.HandlerFunc) { 296 s.rwMutex.Lock() 297 defer s.rwMutex.Unlock() 298 299 rh := routedHandler{ 300 method: method, 301 handler: handler, 302 } 303 304 switch p := path.(type) { 305 case *regexp.Regexp: 306 rh.pathRegexp = p 307 case string: 308 rh.path = p 309 default: 310 panic("path must be a string or a regular expression") 311 } 312 313 for i, existingRH := range s.routedHandlers { 314 if existingRH.method == method && 315 reflect.DeepEqual(existingRH.pathRegexp, rh.pathRegexp) && 316 existingRH.path == rh.path { 317 s.routedHandlers[i] = rh 318 return 319 } 320 } 321 s.routedHandlers = append(s.routedHandlers, rh) 322 } 323 324 func (s *Server) handlerForRoute(method string, path string) (http.HandlerFunc, bool) { 325 for _, rh := range s.routedHandlers { 326 if rh.method == method { 327 if rh.pathRegexp != nil { 328 if rh.pathRegexp.Match([]byte(path)) { 329 return rh.handler, true 330 } 331 } else if rh.path == path { 332 return rh.handler, true 333 } 334 } 335 } 336 337 return nil, false 338 } 339 340 //AppendHandlers will appends http.HandlerFuncs to the server's list of registered handlers. The first incoming request is handled by the first handler, the second by the second, etc... 341 func (s *Server) AppendHandlers(handlers ...http.HandlerFunc) { 342 s.rwMutex.Lock() 343 defer s.rwMutex.Unlock() 344 345 s.requestHandlers = append(s.requestHandlers, handlers...) 346 } 347 348 //SetHandler overrides the registered handler at the passed in index with the passed in handler 349 //This is useful, for example, when a server has been set up in a shared context, but must be tweaked 350 //for a particular test. 351 func (s *Server) SetHandler(index int, handler http.HandlerFunc) { 352 s.rwMutex.Lock() 353 defer s.rwMutex.Unlock() 354 355 s.requestHandlers[index] = handler 356 } 357 358 //GetHandler returns the handler registered at the passed in index. 359 func (s *Server) GetHandler(index int) http.HandlerFunc { 360 s.rwMutex.RLock() 361 defer s.rwMutex.RUnlock() 362 363 return s.requestHandlers[index] 364 } 365 366 func (s *Server) Reset() { 367 s.rwMutex.Lock() 368 defer s.rwMutex.Unlock() 369 370 s.HTTPTestServer.CloseClientConnections() 371 s.calls = 0 372 s.receivedRequests = nil 373 s.requestHandlers = nil 374 s.routedHandlers = nil 375 } 376 377 //WrapHandler combines the passed in handler with the handler registered at the passed in index. 378 //This is useful, for example, when a server has been set up in a shared context but must be tweaked 379 //for a particular test. 380 // 381 //If the currently registered handler is A, and the new passed in handler is B then 382 //WrapHandler will generate a new handler that first calls A, then calls B, and assign it to index 383 func (s *Server) WrapHandler(index int, handler http.HandlerFunc) { 384 existingHandler := s.GetHandler(index) 385 s.SetHandler(index, CombineHandlers(existingHandler, handler)) 386 } 387 388 func (s *Server) CloseClientConnections() { 389 s.rwMutex.Lock() 390 defer s.rwMutex.Unlock() 391 392 s.HTTPTestServer.CloseClientConnections() 393 } 394 395 //SetAllowUnhandledRequests enables the server to accept unhandled requests. 396 func (s *Server) SetAllowUnhandledRequests(allowUnhandledRequests bool) { 397 s.rwMutex.Lock() 398 defer s.rwMutex.Unlock() 399 400 s.AllowUnhandledRequests = allowUnhandledRequests 401 } 402 403 //GetAllowUnhandledRequests returns true if the server accepts unhandled requests. 404 func (s *Server) GetAllowUnhandledRequests() bool { 405 s.rwMutex.RLock() 406 defer s.rwMutex.RUnlock() 407 408 return s.AllowUnhandledRequests 409 } 410 411 //SetUnhandledRequestStatusCode status code to be returned when the server receives unhandled requests 412 func (s *Server) SetUnhandledRequestStatusCode(statusCode int) { 413 s.rwMutex.Lock() 414 defer s.rwMutex.Unlock() 415 416 s.UnhandledRequestStatusCode = statusCode 417 } 418 419 //GetUnhandledRequestStatusCode returns the current status code being returned for unhandled requests 420 func (s *Server) GetUnhandledRequestStatusCode() int { 421 s.rwMutex.RLock() 422 defer s.rwMutex.RUnlock() 423 424 return s.UnhandledRequestStatusCode 425 } 426