github.com/furusax0621/goa-v1@v1.4.3/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 "os" 10 "path/filepath" 11 12 "context" 13 14 "sync" 15 16 "github.com/goadesign/goa" 17 . "github.com/onsi/ginkgo" 18 . "github.com/onsi/gomega" 19 ) 20 21 var _ = Describe("Service", func() { 22 const appName = "foo" 23 var s *goa.Service 24 25 BeforeEach(func() { 26 s = goa.New(appName) 27 s.Decoder.Register(goa.NewJSONDecoder, "*/*") 28 s.Encoder.Register(goa.NewJSONEncoder, "*/*") 29 }) 30 31 Describe("New", func() { 32 It("creates a service", func() { 33 Ω(s).ShouldNot(BeNil()) 34 }) 35 36 It("initializes the service fields", func() { 37 Ω(s.Name).Should(Equal(appName)) 38 Ω(s.Mux).ShouldNot(BeNil()) 39 Ω(s.Server).ShouldNot(BeNil()) 40 }) 41 }) 42 43 Describe("NotFound", func() { 44 var rw *TestResponseWriter 45 var req *http.Request 46 47 BeforeEach(func() { 48 req, _ = http.NewRequest("GET", "/foo", nil) 49 rw = &TestResponseWriter{ParentHeader: make(http.Header)} 50 }) 51 52 JustBeforeEach(func() { 53 s.Mux.ServeHTTP(rw, req) 54 }) 55 56 It("handles requests with no registered handlers", func() { 57 Ω(string(rw.Body)).Should(MatchRegexp(`{"id":".*","code":"not_found","status":404,"detail":"/foo"}` + "\n")) 58 }) 59 60 Context("with middleware", func() { 61 middlewareCalled := false 62 63 BeforeEach(func() { 64 s.Use(TMiddleware(&middlewareCalled)) 65 // trigger finalize 66 ctrl := s.NewController("test") 67 ctrl.MuxHandler("", nil, nil) 68 }) 69 70 It("calls the middleware", func() { 71 Ω(middlewareCalled).Should(BeTrue()) 72 }) 73 }) 74 75 Context("middleware and multiple controllers", func() { 76 middlewareCalled := 0 77 78 BeforeEach(func() { 79 s.Use(CMiddleware(&middlewareCalled)) 80 ctrl := s.NewController("test") 81 ctrl.MuxHandler("/foo", nil, nil) 82 ctrl.MuxHandler("/bar", nil, nil) 83 }) 84 85 It("calls the middleware once", func() { 86 Ω(middlewareCalled).Should(Equal(1)) 87 }) 88 }) 89 }) 90 91 Describe("MethodNotAllowed", func() { 92 var rw *TestResponseWriter 93 var req *http.Request 94 95 JustBeforeEach(func() { 96 rw = &TestResponseWriter{ParentHeader: http.Header{}} 97 s.Mux.ServeHTTP(rw, req) 98 }) 99 100 BeforeEach(func() { 101 req, _ = http.NewRequest("GET", "/foo", nil) 102 s.Mux.Handle("POST", "/foo", func(rw http.ResponseWriter, req *http.Request, vals url.Values) {}) 103 s.Mux.Handle("PUT", "/foo", func(rw http.ResponseWriter, req *http.Request, vals url.Values) {}) 104 }) 105 106 It("handles requests with wrong method but existing endpoint", func() { 107 Ω(rw.Status).Should(Equal(405)) 108 Ω(rw.Header().Get("Allow")).Should(Or(Equal("POST, PUT"), Equal("PUT, POST"))) 109 Ω(string(rw.Body)).Should(MatchRegexp(`{"id":".*","code":"method_not_allowed","status":405,"detail":".*","meta":{.*}}` + "\n")) 110 }) 111 }) 112 113 Describe("MaxRequestBodyLength", func() { 114 var rw *TestResponseWriter 115 var req *http.Request 116 var muxHandler goa.MuxHandler 117 118 BeforeEach(func() { 119 body := bytes.NewBuffer([]byte{'"', '2', '3', '4', '"'}) 120 req, _ = http.NewRequest("GET", "/foo", body) 121 rw = &TestResponseWriter{ParentHeader: make(http.Header)} 122 ctrl := s.NewController("test") 123 ctrl.MaxRequestBodyLength = 4 124 unmarshaler := func(ctx context.Context, service *goa.Service, req *http.Request) error { 125 _, err := ioutil.ReadAll(req.Body) 126 return err 127 } 128 handler := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 129 rw.WriteHeader(400) 130 rw.Write([]byte(goa.ContextError(ctx).Error())) 131 return nil 132 } 133 muxHandler = ctrl.MuxHandler("testMax", handler, unmarshaler) 134 }) 135 136 JustBeforeEach(func() { 137 muxHandler(rw, req, nil) 138 }) 139 140 It("prevents reading more bytes", func() { 141 Ω(string(rw.Body)).Should(MatchRegexp(`\[.*\] 413 request_too_large: request body length exceeds 4 bytes`)) 142 }) 143 }) 144 145 Describe("MuxHandler", func() { 146 var handler goa.Handler 147 var unmarshaler goa.Unmarshaler 148 const respStatus = 200 149 var respContent = []byte("response") 150 151 var muxHandler goa.MuxHandler 152 var ctx context.Context 153 154 JustBeforeEach(func() { 155 ctrl := s.NewController("test") 156 muxHandler = ctrl.MuxHandler("testAct", handler, unmarshaler) 157 }) 158 159 BeforeEach(func() { 160 handler = func(c context.Context, rw http.ResponseWriter, req *http.Request) error { 161 if err := goa.ContextError(c); err != nil { 162 rw.WriteHeader(400) 163 rw.Write([]byte(err.Error())) 164 return nil 165 } 166 goa.ContextRequest(c).Request = req 167 ctx = c 168 rw.WriteHeader(respStatus) 169 rw.Write(respContent) 170 return nil 171 } 172 unmarshaler = func(c context.Context, service *goa.Service, req *http.Request) error { 173 ctx = c 174 if req != nil { 175 var payload interface{} 176 err := service.DecodeRequest(req, &payload) 177 if err != nil { 178 return err 179 } 180 goa.ContextRequest(ctx).Payload = payload 181 } 182 return nil 183 } 184 }) 185 186 It("creates a handler", func() { 187 Ω(muxHandler).ShouldNot(BeNil()) 188 }) 189 190 Context("with multiple instances and middlewares", func() { 191 var ctrl *goa.Controller 192 var handlers []goa.MuxHandler 193 var rws []*TestResponseWriter 194 var reqs []*http.Request 195 var p url.Values 196 var wg sync.WaitGroup 197 198 BeforeEach(func() { 199 nopHandler := func(context.Context, http.ResponseWriter, *http.Request) error { 200 return nil 201 } 202 ctrl = s.NewController("test") 203 for i := 0; i < 5; i++ { 204 ctrl.Service.Use(func(goa.Handler) goa.Handler { 205 return nopHandler 206 }) 207 } 208 ctrl.Use(func(goa.Handler) goa.Handler { return nopHandler }) 209 for i := 0; i < 10; i++ { 210 tmp := ctrl.MuxHandler("test", nopHandler, nil) 211 handlers = append(handlers, tmp) 212 rws = append(rws, &TestResponseWriter{}) 213 req, _ := http.NewRequest("GET", "", nil) 214 reqs = append(reqs, req) 215 } 216 p = url.Values{} 217 wg = sync.WaitGroup{} 218 wg.Add(10) 219 }) 220 221 It("doesn't race with parallel handler calls", func() { 222 for i := range handlers { 223 go func(j int) { 224 handlers[j](rws[j], reqs[j], p) 225 wg.Done() 226 }(i) 227 } 228 wg.Wait() 229 }) 230 }) 231 232 Context("with a request", func() { 233 var rw http.ResponseWriter 234 var r *http.Request 235 var p url.Values 236 237 BeforeEach(func() { 238 var err error 239 r, err = http.NewRequest("GET", "/foo", nil) 240 Ω(err).ShouldNot(HaveOccurred()) 241 rw = &TestResponseWriter{ParentHeader: make(http.Header)} 242 p = url.Values{"id": []string{"42"}, "sort": []string{"asc"}} 243 }) 244 245 JustBeforeEach(func() { 246 muxHandler(rw, r, p) 247 }) 248 249 It("creates a handle that handles the request", func() { 250 i := goa.ContextRequest(ctx).Params.Get("id") 251 Ω(i).Should(Equal("42")) 252 s := goa.ContextRequest(ctx).Params.Get("sort") 253 Ω(s).Should(Equal("asc")) 254 tw := rw.(*TestResponseWriter) 255 Ω(tw.Status).Should(Equal(respStatus)) 256 Ω(tw.Body).Should(Equal(respContent)) 257 }) 258 259 Context("with an invalid payload", func() { 260 BeforeEach(func() { 261 r.Body = ioutil.NopCloser(bytes.NewBuffer([]byte("not json"))) 262 r.ContentLength = 8 263 }) 264 265 It("triggers the error handler", func() { 266 Ω(rw.(*TestResponseWriter).Status).Should(Equal(400)) 267 Ω(string(rw.(*TestResponseWriter).Body)).Should(ContainSubstring("failed to decode")) 268 }) 269 270 Context("then a valid payload", func() { 271 It("then succeeds", func() { 272 var err error 273 r, err = http.NewRequest("GET", "/foo2", nil) 274 Ω(err).ShouldNot(HaveOccurred()) 275 rw = &TestResponseWriter{ParentHeader: make(http.Header)} 276 muxHandler(rw, r, p) 277 Ω(rw.(*TestResponseWriter).Status).Should(Equal(200)) 278 }) 279 }) 280 }) 281 282 Context("and middleware", func() { 283 middlewareCalled := false 284 285 BeforeEach(func() { 286 s.Use(TMiddleware(&middlewareCalled)) 287 }) 288 289 It("calls the middleware", func() { 290 Ω(middlewareCalled).Should(BeTrue()) 291 }) 292 }) 293 294 Context("and a middleware chain", func() { 295 middlewareCalled := false 296 secondCalled := false 297 298 BeforeEach(func() { 299 s.Use(TMiddleware(&middlewareCalled)) 300 s.Use(SecondMiddleware(&middlewareCalled, &secondCalled)) 301 }) 302 303 It("calls the middleware in the right order", func() { 304 Ω(middlewareCalled).Should(BeTrue()) 305 Ω(secondCalled).Should(BeTrue()) 306 }) 307 }) 308 309 Context("with a handler that fails", func() { 310 errorHandlerCalled := false 311 312 BeforeEach(func() { 313 s.Use(TErrorHandler(&errorHandlerCalled)) 314 }) 315 316 Context("by returning an error", func() { 317 BeforeEach(func() { 318 handler = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 319 return fmt.Errorf("boom") 320 } 321 s.WithLogger(nil) 322 }) 323 324 It("triggers the error handler", func() { 325 Ω(errorHandlerCalled).Should(BeTrue()) 326 }) 327 }) 328 }) 329 330 Context("with different payload types", func() { 331 content := []byte(`{"hello": "world"}`) 332 decodedContent := map[string]interface{}{"hello": "world"} 333 334 BeforeEach(func() { 335 r.Header.Set("Content-Type", "application/json") 336 r.Body = ioutil.NopCloser(bytes.NewReader(content)) 337 r.ContentLength = int64(len(content)) 338 }) 339 340 It("should work with application/json and load properly", func() { 341 Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent)) 342 }) 343 344 Context("with an empty Content-Type", func() { 345 BeforeEach(func() { 346 delete(r.Header, "Content-Type") 347 }) 348 349 It("defaults to application/json and loads properly for JSON bodies", func() { 350 Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent)) 351 }) 352 }) 353 354 Context("with a Content-Type of 'application/octet-stream' or any other", func() { 355 BeforeEach(func() { 356 s.Decoder.Register(goa.NewJSONDecoder, "*/*") 357 r.Header.Set("Content-Type", "application/octet-stream") 358 }) 359 360 It("should use the default decoder", func() { 361 Ω(goa.ContextRequest(ctx).Payload).Should(Equal(decodedContent)) 362 }) 363 }) 364 365 Context("with a Content-Type of 'application/octet-stream' or any other and no default decoder", func() { 366 BeforeEach(func() { 367 s = goa.New("test") 368 s.Decoder.Register(goa.NewJSONDecoder, "application/json") 369 r.Header.Set("Content-Type", "application/octet-stream") 370 }) 371 372 It("should bypass decoding", func() { 373 Ω(goa.ContextRequest(ctx).Payload).Should(BeNil()) 374 }) 375 }) 376 }) 377 }) 378 }) 379 380 Describe("FileHandler", func() { 381 const publicPath = "github.com/goadesign/goa/public" 382 383 var outDir string 384 385 var handler goa.Handler 386 const respStatus = 200 387 var respContent = []byte(`{"foo":"bar"}`) 388 389 var muxHandler goa.MuxHandler 390 391 JustBeforeEach(func() { 392 gopath := filepath.SplitList(os.Getenv("GOPATH"))[0] 393 outDir = filepath.Join(gopath, "src", publicPath) 394 err := os.MkdirAll(filepath.Join(outDir, "swagger"), 0777) 395 Ω(err).ShouldNot(HaveOccurred()) 396 file, err := os.Create(filepath.Join(outDir, "swagger", "swagger.json")) 397 Ω(err).ShouldNot(HaveOccurred()) 398 _, err = file.Write(respContent) 399 Ω(err).ShouldNot(HaveOccurred()) 400 file.Close() 401 402 ctrl := s.NewController("test") 403 handler = ctrl.FileHandler("/swagger.json", "public/swagger/swagger.json") 404 muxHandler = ctrl.MuxHandler("testAct", handler, nil) 405 }) 406 407 AfterEach(func() { 408 os.RemoveAll(outDir) 409 }) 410 411 It("creates a handler", func() { 412 Ω(muxHandler).ShouldNot(BeNil()) 413 }) 414 415 Context("with a request", func() { 416 var rw http.ResponseWriter 417 var r *http.Request 418 var p url.Values 419 420 BeforeEach(func() { 421 var err error 422 r, err = http.NewRequest("GET", "/swagger.json", nil) 423 Ω(err).ShouldNot(HaveOccurred()) 424 rw = &TestResponseWriter{ParentHeader: make(http.Header)} 425 }) 426 427 JustBeforeEach(func() { 428 muxHandler(rw, r, p) 429 }) 430 431 It("creates a handle that handles the request", func() { 432 tw := rw.(*TestResponseWriter) 433 Ω(tw.Status).Should(Equal(respStatus)) 434 Ω(tw.Body).Should(Equal(respContent)) 435 }) 436 }) 437 }) 438 }) 439 440 func TErrorHandler(witness *bool) goa.Middleware { 441 return func(h goa.Handler) goa.Handler { 442 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 443 err := h(ctx, rw, req) 444 if err != nil { 445 *witness = true 446 } 447 return nil 448 } 449 } 450 } 451 452 func TMiddleware(witness *bool) goa.Middleware { 453 return func(h goa.Handler) goa.Handler { 454 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 455 *witness = true 456 return h(ctx, rw, req) 457 } 458 } 459 } 460 461 func CMiddleware(witness *int) goa.Middleware { 462 return func(h goa.Handler) goa.Handler { 463 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 464 *witness++ 465 return h(ctx, rw, req) 466 } 467 } 468 } 469 470 func SecondMiddleware(witness1, witness2 *bool) goa.Middleware { 471 return func(h goa.Handler) goa.Handler { 472 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 473 if !*witness1 { 474 panic("middleware called in wrong order") 475 } 476 *witness2 = true 477 return h(ctx, rw, req) 478 } 479 } 480 } 481 482 type TestResponseWriter struct { 483 ParentHeader http.Header 484 Body []byte 485 Status int 486 } 487 488 func (t *TestResponseWriter) Header() http.Header { 489 return t.ParentHeader 490 } 491 492 func (t *TestResponseWriter) Write(b []byte) (int, error) { 493 t.Body = append(t.Body, b...) 494 return len(b), nil 495 } 496 497 func (t *TestResponseWriter) WriteHeader(s int) { 498 t.Status = s 499 }