| /* | 
 | Package ghttp supports testing HTTP clients by providing a test server (simply a thin wrapper around httptest's server) that supports | 
 | registering multiple handlers.  Incoming requests are not routed between the different handlers | 
 | - rather it is merely the order of the handlers that matters.  The first request is handled by the first | 
 | registered handler, the second request by the second handler, etc. | 
 |  | 
 | The intent here is to have each handler *verify* that the incoming request is valid.  To accomplish, ghttp | 
 | also provides a collection of bite-size handlers that each perform one aspect of request verification.  These can | 
 | be composed together and registered with a ghttp server.  The result is an expressive language for describing | 
 | the requests generated by the client under test. | 
 |  | 
 | 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. | 
 | A more comprehensive example is available at https://onsi.github.io/gomega/#_testing_http_clients | 
 |  | 
 | 	var _ = Describe("A Sprockets Client", func() { | 
 | 		var server *ghttp.Server | 
 | 		var client *SprocketClient | 
 | 		BeforeEach(func() { | 
 | 			server = ghttp.NewServer() | 
 | 			client = NewSprocketClient(server.URL(), "skywalker", "tk427") | 
 | 		}) | 
 |  | 
 | 		AfterEach(func() { | 
 | 			server.Close() | 
 | 		}) | 
 |  | 
 | 		Describe("fetching sprockets", func() { | 
 | 			var statusCode int | 
 | 			var sprockets []Sprocket | 
 | 			BeforeEach(func() { | 
 | 				statusCode = http.StatusOK | 
 | 				sprockets = []Sprocket{} | 
 | 				server.AppendHandlers(ghttp.CombineHandlers( | 
 | 					ghttp.VerifyRequest("GET", "/sprockets"), | 
 | 					ghttp.VerifyBasicAuth("skywalker", "tk427"), | 
 | 					ghttp.RespondWithJSONEncodedPtr(&statusCode, &sprockets), | 
 | 				)) | 
 | 			}) | 
 |  | 
 | 			Context("when requesting all sprockets", func() { | 
 | 				Context("when the response is succesful", func() { | 
 | 					BeforeEach(func() { | 
 | 						sprockets = []Sprocket{ | 
 | 							NewSprocket("Alfalfa"), | 
 | 							NewSprocket("Banana"), | 
 | 						} | 
 | 					}) | 
 |  | 
 | 					It("should return the returned sprockets", func() { | 
 | 						Ω(client.Sprockets()).Should(Equal(sprockets)) | 
 | 					}) | 
 | 				}) | 
 |  | 
 | 				Context("when the response is missing", func() { | 
 | 					BeforeEach(func() { | 
 | 						statusCode = http.StatusNotFound | 
 | 					}) | 
 |  | 
 | 					It("should return an empty list of sprockets", func() { | 
 | 						Ω(client.Sprockets()).Should(BeEmpty()) | 
 | 					}) | 
 | 				}) | 
 |  | 
 | 				Context("when the response fails to authenticate", func() { | 
 | 					BeforeEach(func() { | 
 | 						statusCode = http.StatusUnauthorized | 
 | 					}) | 
 |  | 
 | 					It("should return an AuthenticationError error", func() { | 
 | 						sprockets, err := client.Sprockets() | 
 | 						Ω(sprockets).Should(BeEmpty()) | 
 | 						Ω(err).Should(MatchError(AuthenticationError)) | 
 | 					}) | 
 | 				}) | 
 |  | 
 | 				Context("when the response is a server failure", func() { | 
 | 					BeforeEach(func() { | 
 | 						statusCode = http.StatusInternalServerError | 
 | 					}) | 
 |  | 
 | 					It("should return an InternalError error", func() { | 
 | 						sprockets, err := client.Sprockets() | 
 | 						Ω(sprockets).Should(BeEmpty()) | 
 | 						Ω(err).Should(MatchError(InternalError)) | 
 | 					}) | 
 | 				}) | 
 | 			}) | 
 |  | 
 | 			Context("when requesting some sprockets", func() { | 
 | 				BeforeEach(func() { | 
 | 					sprockets = []Sprocket{ | 
 | 						NewSprocket("Alfalfa"), | 
 | 						NewSprocket("Banana"), | 
 | 					} | 
 |  | 
 | 					server.WrapHandler(0, ghttp.VerifyRequest("GET", "/sprockets", "filter=FOOD")) | 
 | 				}) | 
 |  | 
 | 				It("should make the request with a filter", func() { | 
 | 					Ω(client.Sprockets("food")).Should(Equal(sprockets)) | 
 | 				}) | 
 | 			}) | 
 | 		}) | 
 | 	}) | 
 | */ | 
 | package ghttp | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"io/ioutil" | 
 | 	"net/http" | 
 | 	"net/http/httptest" | 
 | 	"reflect" | 
 | 	"regexp" | 
 | 	"strings" | 
 | 	"sync" | 
 |  | 
 | 	. "github.com/onsi/gomega" | 
 | ) | 
 |  | 
 | func new() *Server { | 
 | 	return &Server{ | 
 | 		AllowUnhandledRequests:     false, | 
 | 		UnhandledRequestStatusCode: http.StatusInternalServerError, | 
 | 		writeLock:                  &sync.Mutex{}, | 
 | 	} | 
 | } | 
 |  | 
 | type routedHandler struct { | 
 | 	method     string | 
 | 	pathRegexp *regexp.Regexp | 
 | 	path       string | 
 | 	handler    http.HandlerFunc | 
 | } | 
 |  | 
 | // NewServer returns a new `*ghttp.Server` that wraps an `httptest` server.  The server is started automatically. | 
 | func NewServer() *Server { | 
 | 	s := new() | 
 | 	s.HTTPTestServer = httptest.NewServer(s) | 
 | 	return s | 
 | } | 
 |  | 
 | // NewUnstartedServer return a new, unstarted, `*ghttp.Server`.  Useful for specifying a custom listener on `server.HTTPTestServer`. | 
 | func NewUnstartedServer() *Server { | 
 | 	s := new() | 
 | 	s.HTTPTestServer = httptest.NewUnstartedServer(s) | 
 | 	return s | 
 | } | 
 |  | 
 | // NewTLSServer returns a new `*ghttp.Server` that wraps an `httptest` TLS server.  The server is started automatically. | 
 | func NewTLSServer() *Server { | 
 | 	s := new() | 
 | 	s.HTTPTestServer = httptest.NewTLSServer(s) | 
 | 	return s | 
 | } | 
 |  | 
 | type Server struct { | 
 | 	//The underlying httptest server | 
 | 	HTTPTestServer *httptest.Server | 
 |  | 
 | 	//Defaults to false.  If set to true, the Server will allow more requests than there are registered handlers. | 
 | 	AllowUnhandledRequests bool | 
 |  | 
 | 	//The status code returned when receiving an unhandled request. | 
 | 	//Defaults to http.StatusInternalServerError. | 
 | 	//Only applies if AllowUnhandledRequests is true | 
 | 	UnhandledRequestStatusCode int | 
 |  | 
 | 	//If provided, ghttp will log about each request received to the provided io.Writer | 
 | 	//Defaults to nil | 
 | 	//If you're using Ginkgo, set this to GinkgoWriter to get improved output during failures | 
 | 	Writer io.Writer | 
 |  | 
 | 	receivedRequests []*http.Request | 
 | 	requestHandlers  []http.HandlerFunc | 
 | 	routedHandlers   []routedHandler | 
 |  | 
 | 	writeLock *sync.Mutex | 
 | 	calls     int | 
 | } | 
 |  | 
 | //Start() starts an unstarted ghttp server.  It is a catastrophic error to call Start more than once (thanks, httptest). | 
 | func (s *Server) Start() { | 
 | 	s.HTTPTestServer.Start() | 
 | } | 
 |  | 
 | //URL() returns a url that will hit the server | 
 | func (s *Server) URL() string { | 
 | 	return s.HTTPTestServer.URL | 
 | } | 
 |  | 
 | //Addr() returns the address on which the server is listening. | 
 | func (s *Server) Addr() string { | 
 | 	return s.HTTPTestServer.Listener.Addr().String() | 
 | } | 
 |  | 
 | //Close() should be called at the end of each test.  It spins down and cleans up the test server. | 
 | func (s *Server) Close() { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	server := s.HTTPTestServer | 
 | 	s.HTTPTestServer = nil | 
 | 	server.Close() | 
 | } | 
 |  | 
 | //ServeHTTP() makes Server an http.Handler | 
 | //When the server receives a request it handles the request in the following order: | 
 | // | 
 | //1. If the request matches a handler registered with RouteToHandler, that handler is called. | 
 | //2. Otherwise, if there are handlers registered via AppendHandlers, those handlers are called in order. | 
 | //3. If all registered handlers have been called then: | 
 | //   a) If AllowUnhandledRequests is true, the request will be handled with response code of UnhandledRequestStatusCode | 
 | //   b) If AllowUnhandledRequests is false, the request will not be handled and the current test will be marked as failed. | 
 | func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { | 
 | 	s.writeLock.Lock() | 
 | 	defer func() { | 
 | 		e := recover() | 
 | 		if e != nil { | 
 | 			w.WriteHeader(http.StatusInternalServerError) | 
 | 		} | 
 |  | 
 | 		//If the handler panics GHTTP will silently succeed.  This is bad™. | 
 | 		//To catch this case we need to fail the test if the handler has panicked. | 
 | 		//However, if the handler is panicking because Ginkgo's causing it to panic (i.e. an assertion failed) | 
 | 		//then we shouldn't double-report the error as this will confuse people. | 
 |  | 
 | 		//So: step 1, if this is a Ginkgo panic - do nothing, Ginkgo's aware of the failure | 
 | 		eAsString, ok := e.(string) | 
 | 		if ok && strings.Contains(eAsString, "defer GinkgoRecover()") { | 
 | 			return | 
 | 		} | 
 |  | 
 | 		//If we're here, we have to do step 2: assert that the error is nil.  This assertion will | 
 | 		//allow us to fail the test suite (note: we can't call Fail since Gomega is not allowed to import Ginkgo). | 
 | 		//Since a failed assertion throws a panic, and we are likely in a goroutine, we need to defer within our defer! | 
 | 		defer func() { | 
 | 			recover() | 
 | 		}() | 
 | 		Ω(e).Should(BeNil(), "Handler Panicked") | 
 | 	}() | 
 |  | 
 | 	if s.Writer != nil { | 
 | 		s.Writer.Write([]byte(fmt.Sprintf("GHTTP Received Request: %s - %s\n", req.Method, req.URL))) | 
 | 	} | 
 |  | 
 | 	s.receivedRequests = append(s.receivedRequests, req) | 
 | 	if routedHandler, ok := s.handlerForRoute(req.Method, req.URL.Path); ok { | 
 | 		s.writeLock.Unlock() | 
 | 		routedHandler(w, req) | 
 | 	} else if s.calls < len(s.requestHandlers) { | 
 | 		h := s.requestHandlers[s.calls] | 
 | 		s.calls++ | 
 | 		s.writeLock.Unlock() | 
 | 		h(w, req) | 
 | 	} else { | 
 | 		s.writeLock.Unlock() | 
 | 		if s.AllowUnhandledRequests { | 
 | 			ioutil.ReadAll(req.Body) | 
 | 			req.Body.Close() | 
 | 			w.WriteHeader(s.UnhandledRequestStatusCode) | 
 | 		} else { | 
 | 			Ω(req).Should(BeNil(), "Received Unhandled Request") | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | //ReceivedRequests is an array containing all requests received by the server (both handled and unhandled requests) | 
 | func (s *Server) ReceivedRequests() []*http.Request { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	return s.receivedRequests | 
 | } | 
 |  | 
 | //RouteToHandler can be used to register handlers that will always handle requests that match | 
 | //the passed in method and path. | 
 | // | 
 | //The path may be either a string object or a *regexp.Regexp. | 
 | func (s *Server) RouteToHandler(method string, path interface{}, handler http.HandlerFunc) { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	rh := routedHandler{ | 
 | 		method:  method, | 
 | 		handler: handler, | 
 | 	} | 
 |  | 
 | 	switch p := path.(type) { | 
 | 	case *regexp.Regexp: | 
 | 		rh.pathRegexp = p | 
 | 	case string: | 
 | 		rh.path = p | 
 | 	default: | 
 | 		panic("path must be a string or a regular expression") | 
 | 	} | 
 |  | 
 | 	for i, existingRH := range s.routedHandlers { | 
 | 		if existingRH.method == method && | 
 | 			reflect.DeepEqual(existingRH.pathRegexp, rh.pathRegexp) && | 
 | 			existingRH.path == rh.path { | 
 | 			s.routedHandlers[i] = rh | 
 | 			return | 
 | 		} | 
 | 	} | 
 | 	s.routedHandlers = append(s.routedHandlers, rh) | 
 | } | 
 |  | 
 | func (s *Server) handlerForRoute(method string, path string) (http.HandlerFunc, bool) { | 
 | 	for _, rh := range s.routedHandlers { | 
 | 		if rh.method == method { | 
 | 			if rh.pathRegexp != nil { | 
 | 				if rh.pathRegexp.Match([]byte(path)) { | 
 | 					return rh.handler, true | 
 | 				} | 
 | 			} else if rh.path == path { | 
 | 				return rh.handler, true | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return nil, false | 
 | } | 
 |  | 
 | //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... | 
 | func (s *Server) AppendHandlers(handlers ...http.HandlerFunc) { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	s.requestHandlers = append(s.requestHandlers, handlers...) | 
 | } | 
 |  | 
 | //SetHandler overrides the registered handler at the passed in index with the passed in handler | 
 | //This is useful, for example, when a server has been set up in a shared context, but must be tweaked | 
 | //for a particular test. | 
 | func (s *Server) SetHandler(index int, handler http.HandlerFunc) { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	s.requestHandlers[index] = handler | 
 | } | 
 |  | 
 | //GetHandler returns the handler registered at the passed in index. | 
 | func (s *Server) GetHandler(index int) http.HandlerFunc { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	return s.requestHandlers[index] | 
 | } | 
 |  | 
 | func (s *Server) Reset() { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	s.HTTPTestServer.CloseClientConnections() | 
 | 	s.calls = 0 | 
 | 	s.receivedRequests = nil | 
 | 	s.requestHandlers = nil | 
 | 	s.routedHandlers = nil | 
 | } | 
 |  | 
 | //WrapHandler combines the passed in handler with the handler registered at the passed in index. | 
 | //This is useful, for example, when a server has been set up in a shared context but must be tweaked | 
 | //for a particular test. | 
 | // | 
 | //If the currently registered handler is A, and the new passed in handler is B then | 
 | //WrapHandler will generate a new handler that first calls A, then calls B, and assign it to index | 
 | func (s *Server) WrapHandler(index int, handler http.HandlerFunc) { | 
 | 	existingHandler := s.GetHandler(index) | 
 | 	s.SetHandler(index, CombineHandlers(existingHandler, handler)) | 
 | } | 
 |  | 
 | func (s *Server) CloseClientConnections() { | 
 | 	s.writeLock.Lock() | 
 | 	defer s.writeLock.Unlock() | 
 |  | 
 | 	s.HTTPTestServer.CloseClientConnections() | 
 | } |