github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/service_test.go (about)

     1  package goa_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/goadesign/goa"
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  var _ = Describe("Service", func() {
    18  	const appName = "foo"
    19  	var s *goa.Service
    20  
    21  	BeforeEach(func() {
    22  		s = goa.New(appName)
    23  		s.Decoder.Register(goa.NewJSONDecoder, "*/*")
    24  		s.Encoder.Register(goa.NewJSONEncoder, "*/*")
    25  	})
    26  
    27  	Describe("New", func() {
    28  		It("creates a service", func() {
    29  			Ω(s).ShouldNot(BeNil())
    30  		})
    31  
    32  		It("initializes the service fields", func() {
    33  			Ω(s.Name).Should(Equal(appName))
    34  			Ω(s.Mux).ShouldNot(BeNil())
    35  		})
    36  	})
    37  
    38  	Describe("NotFound", func() {
    39  		var rw *TestResponseWriter
    40  		var req *http.Request
    41  
    42  		BeforeEach(func() {
    43  			req, _ = http.NewRequest("GET", "/foo", nil)
    44  			rw = &TestResponseWriter{ParentHeader: make(http.Header)}
    45  		})
    46  
    47  		JustBeforeEach(func() {
    48  			s.Mux.ServeHTTP(rw, req)
    49  		})
    50  
    51  		It("handles requests with no registered handlers", func() {
    52  			Ω(string(rw.Body)).Should(MatchRegexp(`{"id":".*","code":"not_found","status":404,"detail":"/foo"}` + "\n"))
    53  		})
    54  
    55  		Context("with middleware", func() {
    56  			middlewareCalled := false
    57  
    58  			BeforeEach(func() {
    59  				s.Use(TMiddleware(&middlewareCalled))
    60  				// trigger finalize
    61  				ctrl := s.NewController("test")
    62  				ctrl.MuxHandler("", nil, nil)
    63  			})
    64  
    65  			It("calls the middleware", func() {
    66  				Ω(middlewareCalled).Should(BeTrue())
    67  			})
    68  		})
    69  
    70  		Context("middleware and multiple controllers", func() {
    71  			middlewareCalled := 0
    72  
    73  			BeforeEach(func() {
    74  				s.Use(CMiddleware(&middlewareCalled))
    75  				ctrl := s.NewController("test")
    76  				ctrl.MuxHandler("/foo", nil, nil)
    77  				ctrl.MuxHandler("/bar", nil, nil)
    78  			})
    79  
    80  			It("calls the middleware once", func() {
    81  				Ω(middlewareCalled).Should(Equal(1))
    82  			})
    83  		})
    84  	})
    85  
    86  	Describe("MaxRequestBodyLength", func() {
    87  		var rw *TestResponseWriter
    88  		var req *http.Request
    89  		var muxHandler goa.MuxHandler
    90  
    91  		BeforeEach(func() {
    92  			body := bytes.NewBuffer([]byte{'"', '2', '3', '4', '"'})
    93  			req, _ = http.NewRequest("GET", "/foo", body)
    94  			rw = &TestResponseWriter{ParentHeader: make(http.Header)}
    95  			ctrl := s.NewController("test")
    96  			ctrl.MaxRequestBodyLength = 4
    97  			unmarshaler := func(ctx context.Context, service *goa.Service, req *http.Request) error {
    98  				_, err := ioutil.ReadAll(req.Body)
    99  				return err
   100  			}
   101  			handler := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   102  				rw.WriteHeader(400)
   103  				rw.Write([]byte(goa.ContextError(ctx).Error()))
   104  				return nil
   105  			}
   106  			muxHandler = ctrl.MuxHandler("testMax", handler, unmarshaler)
   107  		})
   108  
   109  		JustBeforeEach(func() {
   110  			muxHandler(rw, req, nil)
   111  		})
   112  
   113  		It("prevents reading more bytes", func() {
   114  			Ω(string(rw.Body)).Should(MatchRegexp(`\[.*\] 413 request_too_large: request body length exceeds 4 bytes`))
   115  		})
   116  	})
   117  
   118  	Describe("MuxHandler", func() {
   119  		var handler goa.Handler
   120  		var unmarshaler goa.Unmarshaler
   121  		const respStatus = 200
   122  		var respContent = []byte("response")
   123  
   124  		var muxHandler goa.MuxHandler
   125  		var ctx context.Context
   126  
   127  		JustBeforeEach(func() {
   128  			ctrl := s.NewController("test")
   129  			muxHandler = ctrl.MuxHandler("testAct", handler, unmarshaler)
   130  		})
   131  
   132  		BeforeEach(func() {
   133  			handler = func(c context.Context, rw http.ResponseWriter, req *http.Request) error {
   134  				if err := goa.ContextError(c); err != nil {
   135  					rw.WriteHeader(400)
   136  					rw.Write([]byte(err.Error()))
   137  					return nil
   138  				}
   139  				goa.ContextRequest(c).Request = req
   140  				ctx = c
   141  				rw.WriteHeader(respStatus)
   142  				rw.Write(respContent)
   143  				return nil
   144  			}
   145  			unmarshaler = func(c context.Context, service *goa.Service, req *http.Request) error {
   146  				ctx = c
   147  				if req != nil {
   148  					var payload interface{}
   149  					err := service.DecodeRequest(req, &payload)
   150  					if err != nil {
   151  						return err
   152  					}
   153  					goa.ContextRequest(ctx).Payload = payload
   154  				}
   155  				return nil
   156  			}
   157  		})
   158  
   159  		It("creates a handle", func() {
   160  			Ω(muxHandler).ShouldNot(BeNil())
   161  		})
   162  
   163  		Context("with a request", func() {
   164  			var rw http.ResponseWriter
   165  			var r *http.Request
   166  			var p url.Values
   167  
   168  			BeforeEach(func() {
   169  				var err error
   170  				r, err = http.NewRequest("GET", "/foo", nil)
   171  				Ω(err).ShouldNot(HaveOccurred())
   172  				rw = &TestResponseWriter{ParentHeader: make(http.Header)}
   173  				p = url.Values{"id": []string{"42"}, "sort": []string{"asc"}}
   174  			})
   175  
   176  			JustBeforeEach(func() {
   177  				muxHandler(rw, r, p)
   178  			})
   179  
   180  			It("creates a handle that handles the request", func() {
   181  				i := goa.ContextRequest(ctx).Params.Get("id")
   182  				Ω(i).Should(Equal("42"))
   183  				s := goa.ContextRequest(ctx).Params.Get("sort")
   184  				Ω(s).Should(Equal("asc"))
   185  				tw := rw.(*TestResponseWriter)
   186  				Ω(tw.Status).Should(Equal(respStatus))
   187  				Ω(tw.Body).Should(Equal(respContent))
   188  			})
   189  
   190  			Context("with an invalid payload", func() {
   191  				BeforeEach(func() {
   192  					r.Body = ioutil.NopCloser(bytes.NewBuffer([]byte("not json")))
   193  					r.ContentLength = 8
   194  				})
   195  
   196  				It("triggers the error handler", func() {
   197  					Ω(rw.(*TestResponseWriter).Status).Should(Equal(400))
   198  					Ω(string(rw.(*TestResponseWriter).Body)).Should(ContainSubstring("failed to decode"))
   199  				})
   200  
   201  				Context("then a valid payload", func() {
   202  					It("then succeeds", func() {
   203  						var err error
   204  						r, err = http.NewRequest("GET", "/foo2", nil)
   205  						Ω(err).ShouldNot(HaveOccurred())
   206  						rw = &TestResponseWriter{ParentHeader: make(http.Header)}
   207  						muxHandler(rw, r, p)
   208  						Ω(rw.(*TestResponseWriter).Status).Should(Equal(200))
   209  					})
   210  				})
   211  			})
   212  
   213  			Context("and middleware", func() {
   214  				middlewareCalled := false
   215  
   216  				BeforeEach(func() {
   217  					s.Use(TMiddleware(&middlewareCalled))
   218  				})
   219  
   220  				It("calls the middleware", func() {
   221  					Ω(middlewareCalled).Should(BeTrue())
   222  				})
   223  			})
   224  
   225  			Context("and a middleware chain", func() {
   226  				middlewareCalled := false
   227  				secondCalled := false
   228  
   229  				BeforeEach(func() {
   230  					s.Use(TMiddleware(&middlewareCalled))
   231  					s.Use(SecondMiddleware(&middlewareCalled, &secondCalled))
   232  				})
   233  
   234  				It("calls the middleware in the right order", func() {
   235  					Ω(middlewareCalled).Should(BeTrue())
   236  					Ω(secondCalled).Should(BeTrue())
   237  				})
   238  			})
   239  
   240  			Context("with a handler that fails", func() {
   241  				errorHandlerCalled := false
   242  
   243  				BeforeEach(func() {
   244  					s.Use(TErrorHandler(&errorHandlerCalled))
   245  				})
   246  
   247  				Context("by returning an error", func() {
   248  					BeforeEach(func() {
   249  						handler = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   250  							return fmt.Errorf("boom")
   251  						}
   252  						s.WithLogger(nil)
   253  					})
   254  
   255  					It("triggers the error handler", func() {
   256  						Ω(errorHandlerCalled).Should(BeTrue())
   257  					})
   258  				})
   259  			})
   260  
   261  			Context("with different payload types", func() {
   262  				content := []byte(`{"hello": "world"}`)
   263  				decodedContent := map[string]interface{}{"hello": "world"}
   264  
   265  				BeforeEach(func() {
   266  					r.Header.Set("Content-Type", "application/json")
   267  					r.Body = ioutil.NopCloser(bytes.NewReader(content))
   268  					r.ContentLength = int64(len(content))
   269  				})
   270  
   271  				It("should work with application/json and load properly", func() {
   272  					Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent))
   273  				})
   274  
   275  				Context("with an empty Content-Type", func() {
   276  					BeforeEach(func() {
   277  						delete(r.Header, "Content-Type")
   278  					})
   279  
   280  					It("defaults to application/json and loads properly for JSON bodies", func() {
   281  						Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent))
   282  					})
   283  				})
   284  
   285  				Context("with a Content-Type of 'application/octet-stream' or any other", func() {
   286  					BeforeEach(func() {
   287  						s.Decoder.Register(goa.NewJSONDecoder, "*/*")
   288  						r.Header.Set("Content-Type", "application/octet-stream")
   289  					})
   290  
   291  					It("should use the default decoder", func() {
   292  						Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent))
   293  					})
   294  				})
   295  
   296  				Context("with a Content-Type of 'application/octet-stream' or any other and no default decoder", func() {
   297  					BeforeEach(func() {
   298  						s = goa.New("test")
   299  						s.Decoder.Register(goa.NewJSONDecoder, "application/json")
   300  						r.Header.Set("Content-Type", "application/octet-stream")
   301  					})
   302  
   303  					It("should bypass decoding", func() {
   304  						Ω(goa.ContextRequest(ctx).Payload).Should(BeNil())
   305  					})
   306  				})
   307  			})
   308  		})
   309  	})
   310  })
   311  
   312  func TErrorHandler(witness *bool) goa.Middleware {
   313  	return func(h goa.Handler) goa.Handler {
   314  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   315  			err := h(ctx, rw, req)
   316  			if err != nil {
   317  				*witness = true
   318  			}
   319  			return nil
   320  		}
   321  	}
   322  }
   323  
   324  func TMiddleware(witness *bool) goa.Middleware {
   325  	return func(h goa.Handler) goa.Handler {
   326  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   327  			*witness = true
   328  			return h(ctx, rw, req)
   329  		}
   330  	}
   331  }
   332  
   333  func CMiddleware(witness *int) goa.Middleware {
   334  	return func(h goa.Handler) goa.Handler {
   335  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   336  			*witness++
   337  			return h(ctx, rw, req)
   338  		}
   339  	}
   340  }
   341  
   342  func SecondMiddleware(witness1, witness2 *bool) goa.Middleware {
   343  	return func(h goa.Handler) goa.Handler {
   344  		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
   345  			if !*witness1 {
   346  				panic("middleware called in wrong order")
   347  			}
   348  			*witness2 = true
   349  			return h(ctx, rw, req)
   350  		}
   351  	}
   352  }
   353  
   354  type TestResponseWriter struct {
   355  	ParentHeader http.Header
   356  	Body         []byte
   357  	Status       int
   358  }
   359  
   360  func (t *TestResponseWriter) Header() http.Header {
   361  	return t.ParentHeader
   362  }
   363  
   364  func (t *TestResponseWriter) Write(b []byte) (int, error) {
   365  	t.Body = append(t.Body, b...)
   366  	return len(b), nil
   367  }
   368  
   369  func (t *TestResponseWriter) WriteHeader(s int) {
   370  	t.Status = s
   371  }