initial commit
[govpp.git] / vendor / github.com / onsi / gomega / ghttp / test_server.go
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                         Context("when requesting all sprockets", func() {
41                                 Context("when the response is succesful", 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                                                 Ω(client.Sprockets()).Should(Equal(sprockets))
51                                         })
52                                 })
53
54                                 Context("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                                                 Ω(client.Sprockets()).Should(BeEmpty())
61                                         })
62                                 })
63
64                                 Context("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                                                 Ω(sprockets).Should(BeEmpty())
72                                                 Ω(err).Should(MatchError(AuthenticationError))
73                                         })
74                                 })
75
76                                 Context("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                                                 Ω(sprockets).Should(BeEmpty())
84                                                 Ω(err).Should(MatchError(InternalError))
85                                         })
86                                 })
87                         })
88
89                         Context("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                                         Ω(client.Sprockets("food")).Should(Equal(sprockets))
101                                 })
102                         })
103                 })
104         })
105 */
106 package ghttp
107
108 import (
109         "fmt"
110         "io"
111         "io/ioutil"
112         "net/http"
113         "net/http/httptest"
114         "reflect"
115         "regexp"
116         "strings"
117         "sync"
118
119         . "github.com/onsi/gomega"
120 )
121
122 func new() *Server {
123         return &Server{
124                 AllowUnhandledRequests:     false,
125                 UnhandledRequestStatusCode: http.StatusInternalServerError,
126                 writeLock:                  &sync.Mutex{},
127         }
128 }
129
130 type routedHandler struct {
131         method     string
132         pathRegexp *regexp.Regexp
133         path       string
134         handler    http.HandlerFunc
135 }
136
137 // NewServer returns a new `*ghttp.Server` that wraps an `httptest` server.  The server is started automatically.
138 func NewServer() *Server {
139         s := new()
140         s.HTTPTestServer = httptest.NewServer(s)
141         return s
142 }
143
144 // NewUnstartedServer return a new, unstarted, `*ghttp.Server`.  Useful for specifying a custom listener on `server.HTTPTestServer`.
145 func NewUnstartedServer() *Server {
146         s := new()
147         s.HTTPTestServer = httptest.NewUnstartedServer(s)
148         return s
149 }
150
151 // NewTLSServer returns a new `*ghttp.Server` that wraps an `httptest` TLS server.  The server is started automatically.
152 func NewTLSServer() *Server {
153         s := new()
154         s.HTTPTestServer = httptest.NewTLSServer(s)
155         return s
156 }
157
158 type Server struct {
159         //The underlying httptest server
160         HTTPTestServer *httptest.Server
161
162         //Defaults to false.  If set to true, the Server will allow more requests than there are registered handlers.
163         AllowUnhandledRequests bool
164
165         //The status code returned when receiving an unhandled request.
166         //Defaults to http.StatusInternalServerError.
167         //Only applies if AllowUnhandledRequests is true
168         UnhandledRequestStatusCode int
169
170         //If provided, ghttp will log about each request received to the provided io.Writer
171         //Defaults to nil
172         //If you're using Ginkgo, set this to GinkgoWriter to get improved output during failures
173         Writer io.Writer
174
175         receivedRequests []*http.Request
176         requestHandlers  []http.HandlerFunc
177         routedHandlers   []routedHandler
178
179         writeLock *sync.Mutex
180         calls     int
181 }
182
183 //Start() starts an unstarted ghttp server.  It is a catastrophic error to call Start more than once (thanks, httptest).
184 func (s *Server) Start() {
185         s.HTTPTestServer.Start()
186 }
187
188 //URL() returns a url that will hit the server
189 func (s *Server) URL() string {
190         return s.HTTPTestServer.URL
191 }
192
193 //Addr() returns the address on which the server is listening.
194 func (s *Server) Addr() string {
195         return s.HTTPTestServer.Listener.Addr().String()
196 }
197
198 //Close() should be called at the end of each test.  It spins down and cleans up the test server.
199 func (s *Server) Close() {
200         s.writeLock.Lock()
201         server := s.HTTPTestServer
202         s.HTTPTestServer = nil
203         s.writeLock.Unlock()
204
205         if server != nil {
206                 server.Close()
207         }
208 }
209
210 //ServeHTTP() makes Server an http.Handler
211 //When the server receives a request it handles the request in the following order:
212 //
213 //1. If the request matches a handler registered with RouteToHandler, that handler is called.
214 //2. Otherwise, if there are handlers registered via AppendHandlers, those handlers are called in order.
215 //3. If all registered handlers have been called then:
216 //   a) If AllowUnhandledRequests is true, the request will be handled with response code of UnhandledRequestStatusCode
217 //   b) If AllowUnhandledRequests is false, the request will not be handled and the current test will be marked as failed.
218 func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
219         s.writeLock.Lock()
220         defer func() {
221                 e := recover()
222                 if e != nil {
223                         w.WriteHeader(http.StatusInternalServerError)
224                 }
225
226                 //If the handler panics GHTTP will silently succeed.  This is bad™.
227                 //To catch this case we need to fail the test if the handler has panicked.
228                 //However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an assertion failed)
229                 //then we shouldn't double-report the error as this will confuse people.
230
231                 //So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure
232                 eAsString, ok := e.(string)
233                 if ok && strings.Contains(eAsString, "defer GinkgoRecover()") {
234                         return
235                 }
236
237                 //If we're here, we have to do step 2: assert that the error is nil.  This assertion will
238                 //allow us to fail the test suite (note: we can't call Fail since Gomega is not allowed to import Ginkgo).
239                 //Since a failed assertion throws a panic, and we are likely in a goroutine, we need to defer within our defer!
240                 defer func() {
241                         recover()
242                 }()
243                 Ω(e).Should(BeNil(), "Handler Panicked")
244         }()
245
246         if s.Writer != nil {
247                 s.Writer.Write([]byte(fmt.Sprintf("GHTTP Received Request: %s - %s\n", req.Method, req.URL)))
248         }
249
250         s.receivedRequests = append(s.receivedRequests, req)
251         if routedHandler, ok := s.handlerForRoute(req.Method, req.URL.Path); ok {
252                 s.writeLock.Unlock()
253                 routedHandler(w, req)
254         } else if s.calls < len(s.requestHandlers) {
255                 h := s.requestHandlers[s.calls]
256                 s.calls++
257                 s.writeLock.Unlock()
258                 h(w, req)
259         } else {
260                 s.writeLock.Unlock()
261                 if s.AllowUnhandledRequests {
262                         ioutil.ReadAll(req.Body)
263                         req.Body.Close()
264                         w.WriteHeader(s.UnhandledRequestStatusCode)
265                 } else {
266                         Ω(req).Should(BeNil(), "Received Unhandled Request")
267                 }
268         }
269 }
270
271 //ReceivedRequests is an array containing all requests received by the server (both handled and unhandled requests)
272 func (s *Server) ReceivedRequests() []*http.Request {
273         s.writeLock.Lock()
274         defer s.writeLock.Unlock()
275
276         return s.receivedRequests
277 }
278
279 //RouteToHandler can be used to register handlers that will always handle requests that match
280 //the passed in method and path.
281 //
282 //The path may be either a string object or a *regexp.Regexp.
283 func (s *Server) RouteToHandler(method string, path interface{}, handler http.HandlerFunc) {
284         s.writeLock.Lock()
285         defer s.writeLock.Unlock()
286
287         rh := routedHandler{
288                 method:  method,
289                 handler: handler,
290         }
291
292         switch p := path.(type) {
293         case *regexp.Regexp:
294                 rh.pathRegexp = p
295         case string:
296                 rh.path = p
297         default:
298                 panic("path must be a string or a regular expression")
299         }
300
301         for i, existingRH := range s.routedHandlers {
302                 if existingRH.method == method &&
303                         reflect.DeepEqual(existingRH.pathRegexp, rh.pathRegexp) &&
304                         existingRH.path == rh.path {
305                         s.routedHandlers[i] = rh
306                         return
307                 }
308         }
309         s.routedHandlers = append(s.routedHandlers, rh)
310 }
311
312 func (s *Server) handlerForRoute(method string, path string) (http.HandlerFunc, bool) {
313         for _, rh := range s.routedHandlers {
314                 if rh.method == method {
315                         if rh.pathRegexp != nil {
316                                 if rh.pathRegexp.Match([]byte(path)) {
317                                         return rh.handler, true
318                                 }
319                         } else if rh.path == path {
320                                 return rh.handler, true
321                         }
322                 }
323         }
324
325         return nil, false
326 }
327
328 //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...
329 func (s *Server) AppendHandlers(handlers ...http.HandlerFunc) {
330         s.writeLock.Lock()
331         defer s.writeLock.Unlock()
332
333         s.requestHandlers = append(s.requestHandlers, handlers...)
334 }
335
336 //SetHandler overrides the registered handler at the passed in index with the passed in handler
337 //This is useful, for example, when a server has been set up in a shared context, but must be tweaked
338 //for a particular test.
339 func (s *Server) SetHandler(index int, handler http.HandlerFunc) {
340         s.writeLock.Lock()
341         defer s.writeLock.Unlock()
342
343         s.requestHandlers[index] = handler
344 }
345
346 //GetHandler returns the handler registered at the passed in index.
347 func (s *Server) GetHandler(index int) http.HandlerFunc {
348         s.writeLock.Lock()
349         defer s.writeLock.Unlock()
350
351         return s.requestHandlers[index]
352 }
353
354 func (s *Server) Reset() {
355         s.writeLock.Lock()
356         defer s.writeLock.Unlock()
357
358         s.HTTPTestServer.CloseClientConnections()
359         s.calls = 0
360         s.receivedRequests = nil
361         s.requestHandlers = nil
362         s.routedHandlers = nil
363 }
364
365 //WrapHandler combines the passed in handler with the handler registered at the passed in index.
366 //This is useful, for example, when a server has been set up in a shared context but must be tweaked
367 //for a particular test.
368 //
369 //If the currently registered handler is A, and the new passed in handler is B then
370 //WrapHandler will generate a new handler that first calls A, then calls B, and assign it to index
371 func (s *Server) WrapHandler(index int, handler http.HandlerFunc) {
372         existingHandler := s.GetHandler(index)
373         s.SetHandler(index, CombineHandlers(existingHandler, handler))
374 }
375
376 func (s *Server) CloseClientConnections() {
377         s.writeLock.Lock()
378         defer s.writeLock.Unlock()
379
380         s.HTTPTestServer.CloseClientConnections()
381 }