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  }