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  }