...

Source file src/github.com/onsi/gomega/ghttp/test_server.go

Documentation: github.com/onsi/gomega/ghttp

     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  

View as plain text