github.com/ManabuSeki/goa-v1@v1.4.3/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 })