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 }