github.com/zak-blake/goa@v1.4.1/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  }