github.com/zak-blake/goa@v1.4.1/error_test.go (about)

     1  package goa
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  
     9  	. "github.com/onsi/ginkgo"
    10  	. "github.com/onsi/gomega"
    11  )
    12  
    13  var _ = Describe("Error", func() {
    14  	const (
    15  		id     = "foo"
    16  		code   = "invalid"
    17  		status = 400
    18  		detail = "error"
    19  	)
    20  	var meta = map[string]interface{}{"what": 42}
    21  
    22  	var gerr *ErrorResponse
    23  
    24  	BeforeEach(func() {
    25  		gerr = &ErrorResponse{ID: id, Code: code, Status: status, Detail: detail, Meta: meta}
    26  	})
    27  
    28  	It("serializes to JSON", func() {
    29  		b, err := json.Marshal(gerr)
    30  		Ω(err).ShouldNot(HaveOccurred())
    31  		Ω(string(b)).Should(Equal(`{"id":"foo","code":"invalid","status":400,"detail":"error","meta":{"what":42}}`))
    32  	})
    33  })
    34  
    35  var _ = Describe("InvalidParamTypeError", func() {
    36  	var valErr error
    37  	name := "param"
    38  	val := 42
    39  	expected := "43"
    40  
    41  	JustBeforeEach(func() {
    42  		valErr = InvalidParamTypeError(name, val, expected)
    43  	})
    44  
    45  	It("creates a http error", func() {
    46  		Ω(valErr).ShouldNot(BeNil())
    47  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
    48  		err := valErr.(*ErrorResponse)
    49  		Ω(err.Detail).Should(ContainSubstring(name))
    50  		Ω(err.Detail).Should(ContainSubstring("%d", val))
    51  		Ω(err.Detail).Should(ContainSubstring(expected))
    52  	})
    53  })
    54  
    55  var _ = Describe("MissingParaerror", func() {
    56  	var valErr error
    57  	name := "param"
    58  
    59  	JustBeforeEach(func() {
    60  		valErr = MissingParamError(name)
    61  	})
    62  
    63  	It("creates a http error", func() {
    64  		Ω(valErr).ShouldNot(BeNil())
    65  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
    66  		err := valErr.(*ErrorResponse)
    67  		Ω(err.Detail).Should(ContainSubstring(name))
    68  	})
    69  })
    70  
    71  var _ = Describe("InvalidAttributeTypeError", func() {
    72  	var valErr error
    73  	ctx := "ctx"
    74  	val := 42
    75  	expected := "43"
    76  
    77  	JustBeforeEach(func() {
    78  		valErr = InvalidAttributeTypeError(ctx, val, expected)
    79  	})
    80  
    81  	It("creates a http error", func() {
    82  		Ω(valErr).ShouldNot(BeNil())
    83  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
    84  		err := valErr.(*ErrorResponse)
    85  		Ω(err.Detail).Should(ContainSubstring(ctx))
    86  		Ω(err.Detail).Should(ContainSubstring("%d", val))
    87  		Ω(err.Detail).Should(ContainSubstring(expected))
    88  	})
    89  })
    90  
    91  var _ = Describe("MissingAttributeError", func() {
    92  	var valErr error
    93  	ctx := "ctx"
    94  	name := "param"
    95  
    96  	JustBeforeEach(func() {
    97  		valErr = MissingAttributeError(ctx, name)
    98  	})
    99  
   100  	It("creates a http error", func() {
   101  		Ω(valErr).ShouldNot(BeNil())
   102  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   103  		err := valErr.(*ErrorResponse)
   104  		Ω(err.Detail).Should(ContainSubstring(ctx))
   105  		Ω(err.Detail).Should(ContainSubstring(name))
   106  	})
   107  })
   108  
   109  var _ = Describe("MissingHeaderError", func() {
   110  	var valErr error
   111  	name := "param"
   112  
   113  	JustBeforeEach(func() {
   114  		valErr = MissingHeaderError(name)
   115  	})
   116  
   117  	It("creates a http error", func() {
   118  		Ω(valErr).ShouldNot(BeNil())
   119  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   120  		err := valErr.(*ErrorResponse)
   121  		Ω(err.Detail).Should(ContainSubstring(name))
   122  	})
   123  })
   124  
   125  var _ = Describe("MethodNotAllowedError", func() {
   126  	var valErr error
   127  	method := "POST"
   128  	var allowed []string
   129  
   130  	JustBeforeEach(func() {
   131  		valErr = MethodNotAllowedError(method, allowed)
   132  	})
   133  
   134  	BeforeEach(func() {
   135  		allowed = []string{"OPTIONS", "GET"}
   136  	})
   137  
   138  	It("creates a http error", func() {
   139  		Ω(valErr).ShouldNot(BeNil())
   140  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   141  		err := valErr.(*ErrorResponse)
   142  		Ω(err.Detail).Should(ContainSubstring(method))
   143  		Ω(err.Detail).Should(ContainSubstring(strings.Join(allowed, ", ")))
   144  	})
   145  
   146  	Context("multiple allowed methods", func() {
   147  		It("should use plural", func() {
   148  			err := valErr.(*ErrorResponse)
   149  			Ω(err.Detail).Should(ContainSubstring("one of"))
   150  		})
   151  	})
   152  
   153  	Context("single allowed method", func() {
   154  		BeforeEach(func() {
   155  			allowed = []string{"GET"}
   156  		})
   157  
   158  		It("should not use plural", func() {
   159  			err := valErr.(*ErrorResponse)
   160  			Ω(err.Detail).ShouldNot(ContainSubstring("one of"))
   161  		})
   162  	})
   163  })
   164  
   165  var _ = Describe("InvalidEnumValueError", func() {
   166  	var valErr error
   167  	ctx := "ctx"
   168  	val := 42
   169  	allowed := []interface{}{"43", "44"}
   170  
   171  	JustBeforeEach(func() {
   172  		valErr = InvalidEnumValueError(ctx, val, allowed)
   173  	})
   174  
   175  	It("creates a http error", func() {
   176  		Ω(valErr).ShouldNot(BeNil())
   177  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   178  		err := valErr.(*ErrorResponse)
   179  		Ω(err.Detail).Should(ContainSubstring(ctx))
   180  		Ω(err.Detail).Should(ContainSubstring("%d", val))
   181  		Ω(err.Detail).Should(ContainSubstring(`"43", "44"`))
   182  	})
   183  })
   184  
   185  var _ = Describe("InvalidFormaerror", func() {
   186  	var valErr error
   187  	ctx := "ctx"
   188  	target := "target"
   189  	format := FormatDateTime
   190  	formatError := errors.New("boo")
   191  
   192  	JustBeforeEach(func() {
   193  		valErr = InvalidFormatError(ctx, target, format, formatError)
   194  	})
   195  
   196  	It("creates a http error", func() {
   197  		Ω(valErr).ShouldNot(BeNil())
   198  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   199  		err := valErr.(*ErrorResponse)
   200  		Ω(err.Detail).Should(ContainSubstring(ctx))
   201  		Ω(err.Detail).Should(ContainSubstring(target))
   202  		Ω(err.Detail).Should(ContainSubstring("date-time"))
   203  		Ω(err.Detail).Should(ContainSubstring(formatError.Error()))
   204  	})
   205  })
   206  
   207  var _ = Describe("InvalidPatternError", func() {
   208  	var valErr error
   209  	ctx := "ctx"
   210  	target := "target"
   211  	pattern := "pattern"
   212  
   213  	JustBeforeEach(func() {
   214  		valErr = InvalidPatternError(ctx, target, pattern)
   215  	})
   216  
   217  	It("creates a http error", func() {
   218  		Ω(valErr).ShouldNot(BeNil())
   219  		Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   220  		err := valErr.(*ErrorResponse)
   221  		Ω(err.Detail).Should(ContainSubstring(ctx))
   222  		Ω(err.Detail).Should(ContainSubstring(target))
   223  		Ω(err.Detail).Should(ContainSubstring(pattern))
   224  	})
   225  })
   226  
   227  var _ = Describe("InvalidRangeError", func() {
   228  	var valErr error
   229  	var value interface{}
   230  
   231  	ctx := "ctx"
   232  	target := "target"
   233  	min := true
   234  
   235  	JustBeforeEach(func() {
   236  		valErr = InvalidRangeError(ctx, target, value, min)
   237  	})
   238  
   239  	Context("with an int value", func() {
   240  		BeforeEach(func() {
   241  			value = 42
   242  		})
   243  
   244  		It("creates a http error", func() {
   245  			Ω(valErr).ShouldNot(BeNil())
   246  			Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   247  			err := valErr.(*ErrorResponse)
   248  			Ω(err.Detail).Should(ContainSubstring(ctx))
   249  			Ω(err.Detail).Should(ContainSubstring("greater than or equal to"))
   250  			Ω(err.Detail).Should(ContainSubstring(fmt.Sprintf("%#v", value)))
   251  			Ω(err.Detail).Should(ContainSubstring(target))
   252  		})
   253  	})
   254  
   255  	Context("with a float64 value", func() {
   256  		BeforeEach(func() {
   257  			value = 42.42
   258  		})
   259  
   260  		It("creates a http error with no value truncation", func() {
   261  			Ω(valErr).ShouldNot(BeNil())
   262  			Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   263  			err := valErr.(*ErrorResponse)
   264  			Ω(err.Detail).Should(ContainSubstring(ctx))
   265  			Ω(err.Detail).Should(ContainSubstring("greater than or equal to"))
   266  			Ω(err.Detail).Should(ContainSubstring(fmt.Sprintf("%#v", value)))
   267  			Ω(err.Detail).Should(ContainSubstring(target))
   268  		})
   269  	})
   270  })
   271  
   272  var _ = Describe("InvalidLengthError", func() {
   273  	const ctx = "ctx"
   274  	const value = 42
   275  	const min = true
   276  
   277  	var target interface{}
   278  	var ln int
   279  
   280  	var valErr error
   281  
   282  	JustBeforeEach(func() {
   283  		valErr = InvalidLengthError(ctx, target, ln, value, min)
   284  	})
   285  
   286  	Context("on strings", func() {
   287  		BeforeEach(func() {
   288  			target = "target"
   289  			ln = len("target")
   290  		})
   291  
   292  		It("creates a http error", func() {
   293  			Ω(valErr).ShouldNot(BeNil())
   294  			Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   295  			err := valErr.(*ErrorResponse)
   296  			Ω(err.Detail).Should(ContainSubstring(ctx))
   297  			Ω(err.Detail).Should(ContainSubstring("greater than or equal to"))
   298  			Ω(err.Detail).Should(ContainSubstring(fmt.Sprintf("%#v", value)))
   299  			Ω(err.Detail).Should(ContainSubstring(target.(string)))
   300  		})
   301  	})
   302  
   303  	Context("on slices", func() {
   304  		BeforeEach(func() {
   305  			target = []string{"target1", "target2"}
   306  			ln = 2
   307  		})
   308  
   309  		It("creates a http error", func() {
   310  			Ω(valErr).ShouldNot(BeNil())
   311  			Ω(valErr).Should(BeAssignableToTypeOf(&ErrorResponse{}))
   312  			err := valErr.(*ErrorResponse)
   313  			Ω(err.Detail).Should(ContainSubstring(ctx))
   314  			Ω(err.Detail).Should(ContainSubstring("greater than or equal to"))
   315  			Ω(err.Detail).Should(ContainSubstring(fmt.Sprintf("%#v", value)))
   316  			Ω(err.Detail).Should(ContainSubstring(fmt.Sprintf("%#v", target)))
   317  		})
   318  	})
   319  })
   320  
   321  // MergeableErrorResponse contains the details of a error response.
   322  // It implements ServiceMergeableError.
   323  type MergeableErrorResponse struct {
   324  	*ErrorResponse
   325  	MergeCalled int
   326  }
   327  
   328  // Merge will set that merge was called and return the underlying ErrorResponse.
   329  func (e *MergeableErrorResponse) Merge(other error) error {
   330  	e.MergeCalled++
   331  	return e
   332  }
   333  
   334  var _ = Describe("Merge", func() {
   335  	var err, err2 error
   336  	var mErr error
   337  
   338  	BeforeEach(func() {
   339  		err = nil
   340  		err2 = nil
   341  		mErr = nil
   342  	})
   343  
   344  	JustBeforeEach(func() {
   345  		mErr = MergeErrors(err, err2)
   346  	})
   347  
   348  	Context("with two nil errors", func() {
   349  		It("returns a nil error", func() {
   350  			Ω(mErr).Should(BeNil())
   351  		})
   352  	})
   353  
   354  	Context("with a nil target", func() {
   355  		const detail = "foo"
   356  
   357  		Context("with the second argument a Error", func() {
   358  			BeforeEach(func() {
   359  				err2 = &ErrorResponse{Detail: detail}
   360  			})
   361  
   362  			It("returns it", func() {
   363  				Ω(mErr).Should(Equal(err2))
   364  			})
   365  		})
   366  
   367  		Context("with the second argument a MergeableError", func() {
   368  			BeforeEach(func() {
   369  				err2 = &MergeableErrorResponse{ErrorResponse: &ErrorResponse{Detail: detail}}
   370  			})
   371  
   372  			It("returns it", func() {
   373  				Ω(mErr).Should(Equal(err2))
   374  			})
   375  		})
   376  
   377  		Context("with the second argument not a Error", func() {
   378  			BeforeEach(func() {
   379  				err2 = errors.New(detail)
   380  			})
   381  
   382  			It("wraps it into a Error", func() {
   383  				Ω(mErr).ShouldNot(BeNil())
   384  				Ω(mErr.(*ErrorResponse).Detail).Should(Equal(detail))
   385  			})
   386  		})
   387  	})
   388  
   389  	Context("with a non-nil target", func() {
   390  		const detail = "foo"
   391  		var status = 42
   392  		var code = "common"
   393  
   394  		Context("with Error", func() {
   395  			BeforeEach(func() {
   396  				err = &ErrorResponse{Detail: detail, Status: status, Code: code}
   397  			})
   398  
   399  			Context("with a nil argument", func() {
   400  				It("returns the target", func() {
   401  					Ω(mErr).Should(Equal(err))
   402  				})
   403  			})
   404  
   405  			Context("with another Error", func() {
   406  				const detail2 = "foo2"
   407  				var status2 = status
   408  				var code2 = code
   409  				var mErr2 *ErrorResponse
   410  
   411  				BeforeEach(func() {
   412  					mErr2 = &ErrorResponse{Detail: detail2, Status: status2, Code: code2}
   413  					err2 = mErr2
   414  				})
   415  
   416  				It("concatenates both error details", func() {
   417  					Ω(mErr.(*ErrorResponse).Detail).Should(Equal(detail + "; " + mErr2.Detail))
   418  				})
   419  
   420  				It("uses the common status", func() {
   421  					Ω(mErr.(*ErrorResponse).Status).Should(Equal(status))
   422  				})
   423  
   424  				It("uses the common code", func() {
   425  					Ω(mErr.(*ErrorResponse).Code).Should(Equal(code))
   426  				})
   427  
   428  				Context("with different code", func() {
   429  					BeforeEach(func() {
   430  						mErr2.Code = code + code
   431  					})
   432  
   433  					It("produces a bad_request error", func() {
   434  						Ω(mErr.(*ErrorResponse).Code).Should(Equal("bad_request"))
   435  						Ω(mErr.(*ErrorResponse).Status).Should(Equal(400))
   436  						Ω(mErr.(*ErrorResponse).Detail).Should(Equal(detail + "; " + mErr2.Detail))
   437  					})
   438  				})
   439  
   440  				Context("with different status", func() {
   441  					BeforeEach(func() {
   442  						mErr2.Status = status + status
   443  					})
   444  
   445  					It("produces a bad_request error", func() {
   446  						Ω(mErr.(*ErrorResponse).Code).Should(Equal("bad_request"))
   447  						Ω(mErr.(*ErrorResponse).Status).Should(Equal(400))
   448  						Ω(mErr.(*ErrorResponse).Detail).Should(Equal(detail + "; " + mErr2.Detail))
   449  					})
   450  				})
   451  
   452  				Context("with nil target metadata", func() {
   453  					BeforeEach(func() {
   454  						err.(*ErrorResponse).Meta = nil
   455  					})
   456  
   457  					Context("with nil/empty other metadata", func() {
   458  						BeforeEach(func() {
   459  							mErr2.Meta = nil
   460  						})
   461  
   462  						It("keeps nil target metadata if no other metadata", func() {
   463  							Ω(mErr.(*ErrorResponse).Meta).Should(BeNil())
   464  						})
   465  					})
   466  
   467  					Context("with other metadata", func() {
   468  						metaValues2 := map[string]interface{}{"foo": 1, "bar": 2}
   469  
   470  						BeforeEach(func() {
   471  							err.(*ErrorResponse).Meta = nil
   472  							mErr2.Meta = metaValues2
   473  						})
   474  
   475  						It("merges the metadata", func() {
   476  							Ω(mErr.(*ErrorResponse).Meta).Should(HaveLen(len(metaValues2)))
   477  							for k, v := range metaValues2 {
   478  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   479  							}
   480  						})
   481  					})
   482  				})
   483  
   484  				Context("with target metadata", func() {
   485  					metaValues := map[string]interface{}{"baz": 3, "qux": 4}
   486  
   487  					BeforeEach(func() {
   488  						mv := make(map[string]interface{}, len(metaValues))
   489  						for k, v := range metaValues {
   490  							mv[k] = v
   491  						}
   492  						err.(*ErrorResponse).Meta = mv
   493  					})
   494  
   495  					Context("with nil/empty other metadata", func() {
   496  						BeforeEach(func() {
   497  							mErr2.Meta = nil
   498  						})
   499  
   500  						It("keeps target metadata if no other metadata", func() {
   501  							Ω(mErr.(*ErrorResponse).Meta).Should(HaveLen(len(metaValues)))
   502  							for k, v := range metaValues {
   503  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   504  							}
   505  						})
   506  					})
   507  
   508  					Context("with other metadata", func() {
   509  						metaValues2 := map[string]interface{}{"foo": 1, "bar": 2}
   510  
   511  						BeforeEach(func() {
   512  							mErr2.Meta = metaValues2
   513  						})
   514  
   515  						It("merges the metadata", func() {
   516  							Ω(mErr.(*ErrorResponse).Meta).Should(HaveLen(len(metaValues) + len(metaValues2)))
   517  							for k, v := range metaValues {
   518  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   519  							}
   520  							for k, v := range metaValues2 {
   521  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   522  							}
   523  						})
   524  					})
   525  
   526  					Context("with the second argument a MergeableError", func() {
   527  						BeforeEach(func() {
   528  							err2 = &MergeableErrorResponse{ErrorResponse: &ErrorResponse{Detail: detail}}
   529  						})
   530  
   531  						It("returns it", func() {
   532  							Ω(mErr).Should(BeAssignableToTypeOf(&MergeableErrorResponse{}))
   533  							Ω(err2.(*MergeableErrorResponse).MergeCalled).To(Equal(1))
   534  						})
   535  					})
   536  				})
   537  
   538  				Context("with metadata with a common key", func() {
   539  					const commonKey = "foo"
   540  
   541  					var metaValues = map[string]interface{}{commonKey: "bar", "foo2": 44}
   542  					var metaValues2 = map[string]interface{}{commonKey: 43, "baz": 42}
   543  
   544  					BeforeEach(func() {
   545  						mv := make(map[string]interface{}, len(metaValues))
   546  						for k, v := range metaValues {
   547  							mv[k] = v
   548  						}
   549  						err.(*ErrorResponse).Meta = mv
   550  						mErr2.Meta = metaValues2
   551  					})
   552  
   553  					It("merges the metadata", func() {
   554  						Ω(mErr.(*ErrorResponse).Meta).Should(HaveLen(len(metaValues) + len(metaValues2) - 1))
   555  						for k, v := range metaValues {
   556  							if k != commonKey {
   557  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   558  							}
   559  						}
   560  						for k, v := range metaValues2 {
   561  							if k != commonKey {
   562  								Ω(mErr.(*ErrorResponse).Meta[k]).Should(Equal(v))
   563  							}
   564  						}
   565  						Ω(mErr.(*ErrorResponse).Meta[commonKey]).Should(Equal(metaValues2[commonKey]))
   566  					})
   567  				})
   568  			})
   569  		})
   570  
   571  		Context("with MergeableError", func() {
   572  			BeforeEach(func() {
   573  				err = &MergeableErrorResponse{ErrorResponse: &ErrorResponse{Detail: detail, Status: status, Code: code}}
   574  			})
   575  
   576  			Context("with a nil argument", func() {
   577  				It("returns the target", func() {
   578  					Ω(mErr).Should(Equal(err))
   579  				})
   580  			})
   581  
   582  			Context("with Error", func() {
   583  				const detail2 = "foo2"
   584  				var status2 = status
   585  				var code2 = code
   586  				var mErr2 *ErrorResponse
   587  
   588  				BeforeEach(func() {
   589  					mErr2 = &ErrorResponse{Detail: detail2, Status: status2, Code: code2}
   590  					err2 = mErr2
   591  				})
   592  
   593  				It("calls user defined merge", func() {
   594  					Ω(err.(*MergeableErrorResponse).MergeCalled).Should(Equal(1))
   595  				})
   596  			})
   597  
   598  			Context("with MergableError", func() {
   599  				const detail2 = "foo2"
   600  				var status2 = status
   601  				var code2 = code
   602  
   603  				BeforeEach(func() {
   604  					err2 = &MergeableErrorResponse{ErrorResponse: &ErrorResponse{Detail: detail2, Status: status2, Code: code2}}
   605  				})
   606  
   607  				It("calls user defined merge", func() {
   608  					Ω(err.(*MergeableErrorResponse).MergeCalled).Should(Equal(1))
   609  				})
   610  			})
   611  		})
   612  	})
   613  
   614  })