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