github.com/blend/go-sdk@v1.20220411.3/ex/ex_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package ex 9 10 import ( 11 "encoding/json" 12 "errors" 13 "fmt" 14 "strings" 15 "testing" 16 17 "github.com/blend/go-sdk/assert" 18 ) 19 20 func TestNewOfString(t *testing.T) { 21 a := assert.New(t) 22 ex := As(New("this is a test")) 23 a.Equal("this is a test", fmt.Sprintf("%v", ex)) 24 a.NotNil(ex.StackTrace) 25 a.Nil(ex.Inner) 26 } 27 28 func TestNewOfError(t *testing.T) { 29 a := assert.New(t) 30 31 err := errors.New("This is an error") 32 wrappedErr := New(err) 33 a.NotNil(wrappedErr) 34 typedWrapped := As(wrappedErr) 35 a.NotNil(typedWrapped) 36 a.Equal("This is an error", fmt.Sprintf("%v", typedWrapped)) 37 } 38 39 func TestNewOfException(t *testing.T) { 40 a := assert.New(t) 41 ex := New(Class("This is an exception")) 42 wrappedEx := New(ex) 43 a.NotNil(wrappedEx) 44 typedWrappedEx := As(wrappedEx) 45 a.Equal("This is an exception", fmt.Sprintf("%v", typedWrappedEx)) 46 a.Equal(ex, typedWrappedEx) 47 } 48 49 func TestNewOfNil(t *testing.T) { 50 a := assert.New(t) 51 52 shouldBeNil := New(nil) 53 a.Nil(shouldBeNil) 54 a.Equal(nil, shouldBeNil) 55 a.True(nil == shouldBeNil) 56 } 57 58 func TestNewOfTypedNil(t *testing.T) { 59 a := assert.New(t) 60 61 var nilError error 62 a.Nil(nilError) 63 a.Equal(nil, nilError) 64 65 shouldBeNil := New(nilError) 66 a.Nil(shouldBeNil) 67 a.True(shouldBeNil == nil) 68 } 69 70 func TestNewOfReturnedNil(t *testing.T) { 71 a := assert.New(t) 72 73 returnsNil := func() error { 74 return nil 75 } 76 77 shouldBeNil := New(returnsNil()) 78 a.Nil(shouldBeNil) 79 a.True(shouldBeNil == nil) 80 81 returnsTypedNil := func() error { 82 return New(nil) 83 } 84 85 shouldAlsoBeNil := returnsTypedNil() 86 a.Nil(shouldAlsoBeNil) 87 a.True(shouldAlsoBeNil == nil) 88 } 89 90 func TestError(t *testing.T) { 91 a := assert.New(t) 92 93 ex := New(Class("this is a test")) 94 message := ex.Error() 95 a.NotEmpty(message) 96 } 97 98 func TestErrorOptions(t *testing.T) { 99 a := assert.New(t) 100 101 ex := New(Class("this is a test"), OptMessage("foo")) 102 message := ex.Error() 103 a.NotEmpty(message) 104 105 typed := As(ex) 106 a.NotNil(typed) 107 a.Equal("foo", typed.Message) 108 } 109 110 func TestCallers(t *testing.T) { 111 a := assert.New(t) 112 113 callStack := func() StackTrace { return Callers(DefaultStartDepth) }() 114 115 a.NotNil(callStack) 116 callstackStr := callStack.String() 117 a.True(strings.Contains(callstackStr, "TestCallers"), callstackStr) 118 } 119 120 func TestExceptionFormatters(t *testing.T) { 121 assert := assert.New(t) 122 123 // test the "%v" formatter with just the exception class. 124 class := &Ex{Class: Class("this is a test")} 125 assert.Equal("this is a test", fmt.Sprintf("%v", class)) 126 127 classAndMessage := &Ex{Class: Class("foo"), Message: "bar"} 128 assert.Equal("foo; bar", fmt.Sprintf("%v", classAndMessage)) 129 } 130 131 func TestMarshalJSON(t *testing.T) { 132 133 type ReadableStackTrace struct { 134 Class string `json:"Class"` 135 Message string `json:"Message"` 136 Inner error `json:"Inner"` 137 Stack []string `json:"StackTrace"` 138 } 139 140 a := assert.New(t) 141 message := "new test error" 142 ex := As(New(message)) 143 a.NotNil(ex) 144 stackTrace := ex.StackTrace 145 typed, isTyped := stackTrace.(StackPointers) 146 a.True(isTyped) 147 a.NotNil(typed) 148 stackDepth := len(typed) 149 150 jsonErr, err := json.Marshal(ex) 151 a.Nil(err) 152 a.NotNil(jsonErr) 153 154 ex2 := &ReadableStackTrace{} 155 err = json.Unmarshal(jsonErr, ex2) 156 a.Nil(err) 157 a.Len(ex2.Stack, stackDepth) 158 a.Equal(message, ex2.Class) 159 160 ex = As(New(fmt.Errorf(message))) 161 a.NotNil(ex) 162 stackTrace = ex.StackTrace 163 typed, isTyped = stackTrace.(StackPointers) 164 a.True(isTyped) 165 a.NotNil(typed) 166 stackDepth = len(typed) 167 168 jsonErr, err = json.Marshal(ex) 169 a.Nil(err) 170 a.NotNil(jsonErr) 171 172 ex2 = &ReadableStackTrace{} 173 err = json.Unmarshal(jsonErr, ex2) 174 a.Nil(err) 175 a.Len(ex2.Stack, stackDepth) 176 a.Equal(message, ex2.Class) 177 } 178 179 func TestJSON(t *testing.T) { 180 assert := assert.New(t) 181 182 ex := New("this is a test", 183 OptMessage("test message"), 184 OptInner(New("inner exception", OptMessagef("inner test message"))), 185 ) 186 187 contents, err := json.Marshal(ex) 188 assert.Nil(err) 189 190 var verify Ex 191 err = json.Unmarshal(contents, &verify) 192 assert.Nil(err) 193 194 assert.Equal(ErrClass(ex), ErrClass(verify)) 195 assert.Equal(ErrMessage(ex), ErrMessage(verify)) 196 assert.NotNil(verify.Inner) 197 assert.Equal(ErrClass(ErrInner(ex)), ErrClass(ErrInner(verify))) 198 assert.Equal(ErrMessage(ErrInner(ex)), ErrMessage(ErrInner(verify))) 199 } 200 201 func TestNest(t *testing.T) { 202 a := assert.New(t) 203 204 ex1 := As(New("this is an error")) 205 ex2 := As(New("this is another error")) 206 err := As(Nest(ex1, ex2)) 207 208 a.NotNil(err) 209 a.NotNil(err.Inner) 210 a.NotEmpty(err.Error()) 211 212 a.True(Is(ex1, Class("this is an error"))) 213 a.True(Is(ex1.Inner, Class("this is another error"))) 214 } 215 216 func TestNestNil(t *testing.T) { 217 a := assert.New(t) 218 219 var ex1 error 220 var ex2 error 221 var ex3 error 222 223 err := Nest(ex1, ex2, ex3) 224 a.Nil(err) 225 a.Equal(nil, err) 226 a.True(nil == err) 227 } 228 229 func TestExceptionFormat(t *testing.T) { 230 assert := assert.New(t) 231 232 e := &Ex{Class: fmt.Errorf("this is only a test")} 233 output := fmt.Sprintf("%v", e) 234 assert.Equal("this is only a test", output) 235 236 output = fmt.Sprintf("%+v", e) 237 assert.Equal("this is only a test", output) 238 239 e = &Ex{ 240 Class: fmt.Errorf("this is only a test"), 241 StackTrace: StackStrings([]string{ 242 "foo", 243 "bar", 244 }), 245 } 246 247 output = fmt.Sprintf("%+v", e) 248 assert.Equal("this is only a test\nfoo\nbar", output) 249 } 250 251 func TestExceptionPrintsInner(t *testing.T) { 252 assert := assert.New(t) 253 254 ex := New("outer", OptInner(New("middle", OptInner(New("terminal"))))) 255 256 output := fmt.Sprintf("%v", ex) 257 258 assert.Contains(output, "outer") 259 assert.Contains(output, "middle") 260 assert.Contains(output, "terminal") 261 262 output = fmt.Sprintf("%+v", ex) 263 264 assert.Contains(output, "outer") 265 assert.Contains(output, "middle") 266 assert.Contains(output, "terminal") 267 } 268 269 type structuredError struct { 270 value string 271 } 272 273 func (err structuredError) Error() string { 274 return err.value 275 } 276 277 func TestException_ErrorsIsCompatability(t *testing.T) { 278 assert := assert.New(t) 279 280 { // Single nesting, Ex is outermost 281 innerErr := errors.New("inner") 282 outerErr := New("outer", OptInnerClass(innerErr)) 283 284 assert.True(errors.Is(outerErr, innerErr)) 285 } 286 287 { // Single nesting, Ex is innermost 288 innerErr := New("inner") 289 outerErr := fmt.Errorf("outer: %w", innerErr) 290 291 assert.True(errors.Is(outerErr, Class("inner"))) 292 } 293 294 { // Triple nesting, including Ex and non-Ex 295 firstErr := errors.New("inner most") 296 secondErr := fmt.Errorf("standard err: %w", firstErr) 297 thirdErr := New("ex err", OptInner(secondErr)) 298 fourthErr := New("outer most", OptInner(thirdErr)) 299 300 assert.True(errors.Is(fourthErr, firstErr)) 301 assert.True(errors.Is(fourthErr, secondErr)) 302 assert.True(errors.Is(fourthErr, Class("ex err"))) 303 } 304 305 { // Target is nested in an Ex class and not in Inner chain 306 firstErr := errors.New("inner most") 307 secondErr := fmt.Errorf("standard err: %w", firstErr) 308 thirdErr := New(secondErr, OptInner(fmt.Errorf("another cause"))) 309 310 assert.True(errors.Is(thirdErr, firstErr)) 311 assert.True(errors.Is(thirdErr, secondErr)) 312 } 313 } 314 315 func TestException_ErrorsAsCompatability(t *testing.T) { 316 assert := assert.New(t) 317 318 { // Single nesting, targeting non-Ex 319 innerErr := structuredError{"inner most"} 320 outerErr := New("outer", OptInner(innerErr)) 321 322 var matchedErr structuredError 323 assert.True(errors.As(outerErr, &matchedErr)) 324 assert.Equal("inner most", matchedErr.value) 325 } 326 327 { // Single nesting, targeting Ex 328 innerErr := New("outer most") 329 outerErr := fmt.Errorf("outer err: %w", innerErr) 330 331 var matchedErr *Ex 332 assert.True(errors.As(outerErr, &matchedErr)) 333 assert.Equal("outer most", matchedErr.Class.Error()) 334 } 335 336 { // Single nesting, targeting inner Ex class 337 innerErr := New(structuredError{"inner most"}) 338 outerErr := New("outer most", OptInner(innerErr)) 339 340 var matchedErr structuredError 341 assert.True(errors.As(outerErr, &matchedErr)) 342 assert.Equal("inner most", matchedErr.value) 343 } 344 345 { // Triple Nesting, targeting non-Ex 346 firstErr := structuredError{"inner most"} 347 secondErr := fmt.Errorf("standard err: %w", firstErr) 348 thirdErr := New("ex err", OptInner(secondErr)) 349 fourthErr := New("outer most", OptInner(thirdErr)) 350 351 var matchedErr structuredError 352 assert.True(errors.As(fourthErr, &matchedErr)) 353 assert.Equal("inner most", matchedErr.value) 354 } 355 }