ark-member/internal/lib/error/easy_func_test.go

1011 lines
26 KiB
Go

package error
import (
"context"
"errors"
"fmt"
"member/internal/lib/error/code"
"reflect"
"strconv"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestFromGRPCError_GivenStatusWithCodeAndMessage_ShouldReturnErr(t *testing.T) {
// setup
s := status.Error(codes.Code(102399), "FAKE ERROR")
// act
e := FromGRPCError(s)
// assert
assert.Equal(t, uint32(10), e.Scope())
assert.Equal(t, uint32(2300), e.Category())
assert.Equal(t, uint32(2399), e.Code())
assert.Equal(t, "FAKE ERROR", e.Error())
}
func TestFromGRPCError_GivenNilError_ShouldReturnErr_Scope0_Cat0_Detail0(t *testing.T) {
// setup
var nilError error = nil
// act
e := FromGRPCError(nilError)
// assert
assert.Equal(t, uint32(0), e.Scope())
assert.Equal(t, uint32(0), e.Category())
assert.Equal(t, uint32(0), e.Code())
assert.Equal(t, "", e.Error())
}
func TestFromGRPCError_GivenGRPCNativeError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnavailable(t *testing.T) {
// setup
msg := "GRPC Unavailable ERROR"
s := status.Error(codes.Code(codes.Unavailable), msg)
// act
e := FromGRPCError(s)
// assert
assert.Equal(t, code.Unset, e.Scope())
assert.Equal(t, code.CatGRPC, e.Category())
assert.Equal(t, uint32(codes.Unavailable), e.Code())
assert.Equal(t, msg, e.Error())
}
func TestFromGRPCError_GivenGeneralError_ShouldReturnErr_Scope0_CatGRPC_DetailGRPCUnknown(t *testing.T) {
// setup
generalErr := errors.New("general error")
// act
e := FromGRPCError(generalErr)
// assert
assert.Equal(t, code.Unset, e.Scope())
assert.Equal(t, code.CatGRPC, e.Category())
assert.Equal(t, uint32(codes.Unknown), e.Code())
}
func TestToGRPCError_GivenErr_StatusShouldHave_Code112233(t *testing.T) {
// setup
e := Err{scope: 11, code: 2233, msg: "FAKE MSG"}
// act
err := ToGRPCError(&e)
s, _ := status.FromError(err)
// assert
assert.Equal(t, 112233, int(s.Code()))
assert.Equal(t, "FAKE MSG", s.Message())
}
func TestInvalidFormat_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidFormat("field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidFormat, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Equal(t, e.Error(), "invalid format: field A Error description")
}
func TestInvalidFormatL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
// act
e := InvalidFormatL(logx.WithContext(ctx), "field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidFormat, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidRange_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidRange("field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidRange, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Equal(t, e.Error(), "invalid range: field A Error description")
}
func TestInvalidRangeL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.Background()
// act
e := InvalidRangeL(logx.WithContext(ctx), "field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidRange, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestNotValidImplementation_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := NotValidImplementation("field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.NotValidImplementation, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Equal(t, e.Error(), "not valid implementation: field A Error description")
}
func TestNotValidImplementationL_WithStrings_ShouldHasCatInputAndDetailCode(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := NotValidImplementationL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.NotValidImplementation, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestDBError_WithStrings_ShouldHasCatDBAndDetailCodeDBError(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := DBError("field A", "Error description")
// assert
assert.Equal(t, code.CatDB, e.Category())
assert.Equal(t, code.DBError, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestDBDataConvert_WithStrings_ShouldHasCatDBAndDetailCodeDBDataConvert(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := DBDataConvert("field A", "Error description")
// assert
assert.Equal(t, code.CatDB, e.Category())
assert.Equal(t, code.DBDataConvert, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceNotFound_WithStrings_ShouldHasCatResource_DetailCodeResourceNotFound(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := ResourceNotFound("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceNotFound, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidResourceFormat_WithStrings_ShouldHasCatResource_DetailCodeInvalidResourceFormat(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidResourceFormat("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InvalidResourceFormat, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidResourceState_OK(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidResourceState("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InvalidResourceState, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.EqualError(t, e, "invalid resource state: field A Error description")
}
func TestInvalidResourceStateL_LogError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := InvalidResourceStateL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InvalidResourceState, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.EqualError(t, e, "invalid resource state: field A Error description")
}
func TestAuthExpired_OK(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := AuthExpired("field A", "Error description")
// assert
assert.Equal(t, code.CatAuth, e.Category())
assert.Equal(t, code.AuthExpired, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestUnauthorized_WithStrings_ShouldHasCatAuth_DetailCodeUnauthorized(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := Unauthorized("field A", "Error description")
// assert
assert.Equal(t, code.CatAuth, e.Category())
assert.Equal(t, code.Unauthorized, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidPosixTime_WithStrings_ShouldHasCatAuth_DetailCodeInvalidPosixTime(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidPosixTime("field A", "Error description")
// assert
assert.Equal(t, code.CatAuth, e.Category())
assert.Equal(t, code.InvalidPosixTime, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestSigAndPayloadNotMatched_WithStrings_ShouldHasCatAuth_DetailCodeSigAndPayloadNotMatched(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := SigAndPayloadNotMatched("field A", "Error description")
// assert
assert.Equal(t, code.CatAuth, e.Category())
assert.Equal(t, code.SigAndPayloadNotMatched, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestForbidden_WithStrings_ShouldHasCatAuth_DetailCodeForbidden(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := Forbidden("field A", "Error description")
// assert
assert.Equal(t, code.CatAuth, e.Category())
assert.Equal(t, code.Forbidden, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestXBCInternal_WithStrings_ShouldHasCatResource_DetailCodeXBCInternal(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := XBCInternal("field A", "Error description")
// assert
assert.Equal(t, code.CatArk, e.Category())
assert.Equal(t, code.ArkInternal, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestGeneralInternalError_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := SystemInternalError("field A", "Error description")
// assert
assert.Equal(t, code.CatSystem, e.Category())
assert.Equal(t, code.SystemInternalError, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestGeneralInternalErrorL_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := SystemInternalErrorL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatSystem, e.Category())
assert.Equal(t, code.SystemInternalError, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestSystemMaintainError_WithStrings_DetailSystemMaintainError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := SystemMaintainErrorL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatSystem, e.Category())
assert.Equal(t, code.SystemMaintainError, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceAlreadyExist_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := ResourceAlreadyExist("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceAlreadyExist, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceAlreadyExistL_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := ResourceAlreadyExistL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceAlreadyExist, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceInsufficient_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := ResourceInsufficient("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceInsufficient, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceInsufficientL_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := ResourceInsufficientL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceInsufficient, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInsufficientPermission_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InsufficientPermission("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InsufficientPermission, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInsufficientPermissionL_WithStrings_DetailInternalError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := InsufficientPermissionL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InsufficientPermission, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidMeasurementID_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InvalidMeasurementID("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InvalidMeasurementID, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInvalidMeasurementIDL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := InvalidMeasurementIDL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InvalidMeasurementID, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceExpired_OK(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := ResourceExpired("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceExpired, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceExpiredL_LogError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := ResourceExpiredL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceExpired, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceMigrated_OK(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := ResourceMigrated("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceMigrated, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestResourceMigratedL_LogError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := ResourceMigratedL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.ResourceMigrated, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInsufficientQuota_OK(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := InsufficientQuota("field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InsufficientQuota, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestInsufficientQuotaL_LogError(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := InsufficientQuotaL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatResource, e.Category())
assert.Equal(t, code.InsufficientQuota, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestPublish_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := Publish("field A", "Error description")
// assert
assert.Equal(t, code.CatPubSub, e.Category())
assert.Equal(t, code.Publish, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestPublishL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := PublishL(l, "field A", "Error description")
// assert
assert.Equal(t, code.CatPubSub, e.Category())
assert.Equal(t, code.Publish, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
}
func TestMsgSizeTooLarge_WithErrorStrings_ShouldReturnCorrectCodeAndErrorString(t *testing.T) {
// setup
Scope = 99
defer func() {
Scope = code.Unset
}()
// act
e := MsgSizeTooLarge("Error description")
// assert
assert.Equal(t, code.CatPubSub, e.Category())
assert.Equal(t, code.MsgSizeTooLarge, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "kafka error: Error description")
}
func TestMsgSizeTooLargeL_WithErrorStrings_ShouldReturnCorrectCodeAndErrorStringAndCallLogger(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
l := logx.WithContext(context.Background())
// act
e := MsgSizeTooLargeL(l, "Error description")
// assert
assert.Equal(t, code.CatPubSub, e.Category())
assert.Equal(t, code.MsgSizeTooLarge, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "kafka error: Error description")
}
func TestStructErr_WithInternalErr_ShouldIsFuncReportCorrectly(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
// arrange 2 layers err
layer1Err := fmt.Errorf("layer 1 error")
layer2Err := fmt.Errorf("layer 2: %w", layer1Err)
// act with error chain: InvalidFormat -> layer 2 err -> layer 1 err
e := InvalidFormat("field A", "Error description")
e.Wrap(layer2Err)
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidFormat, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
// errors.Is should report correctly
assert.True(t, errors.Is(e, layer1Err))
assert.True(t, errors.Is(e, layer2Err))
}
func TestStructErr_WithInternalErr_ShouldErrorOutputChainErrMessage(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
// arrange 2 layers err
layer1Err := fmt.Errorf("layer 1 error")
// act with error chain: InvalidFormat -> layer 1 err
e := InvalidFormat("field A", "Error description")
e.Wrap(layer1Err)
// assert
assert.Equal(t, "invalid format: field A Error description: layer 1 error", e.Error())
}
// arrange a specific err type just for UT
type testErr struct {
code int
}
func (e *testErr) Error() string {
return strconv.Itoa(e.code)
}
func TestStructErr_WithInternalErr_ShouldAsFuncReportCorrectly(t *testing.T) {
// setup
Scope = 99
defer func() { Scope = code.Unset }()
testE := &testErr{code: 123}
layer2Err := fmt.Errorf("layer 2: %w", testE)
// act with error chain: InvalidFormat -> layer 2 err -> testErr
e := InvalidFormat("field A", "Error description")
e.Wrap(layer2Err)
// assert
assert.Equal(t, code.CatInput, e.Category())
assert.Equal(t, code.InvalidFormat, e.Code())
assert.Equal(t, uint32(99), e.Scope())
assert.Contains(t, e.Error(), "field A")
assert.Contains(t, e.Error(), "Error description")
// errors.As should report correctly
var internalErr *testErr
assert.True(t, errors.As(e, &internalErr))
assert.Equal(t, testE, internalErr)
}
/*
benchmark run for 1 second:
Benchmark_ErrorsIs_OneLayerError-4 148281332 8.68 ns/op 0 B/op 0 allocs/op
Benchmark_ErrorsIs_TwoLayerError-4 35048202 32.4 ns/op 0 B/op 0 allocs/op
Benchmark_ErrorsIs_FourLayerError-4 15309349 81.7 ns/op 0 B/op 0 allocs/op
Benchmark_ErrorsAs_OneLayerError-4 16893205 70.4 ns/op 0 B/op 0 allocs/op
Benchmark_ErrorsAs_TwoLayerError-4 10568083 112 ns/op 0 B/op 0 allocs/op
Benchmark_ErrorsAs_FourLayerError-4 6307729 188 ns/op 0 B/op 0 allocs/op
*/
func Benchmark_ErrorsIs_OneLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
var err error = layer1Err
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
errors.Is(err, layer1Err)
}
}
func Benchmark_ErrorsIs_TwoLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
// act with error chain: InvalidFormat(layer 2) -> testErr(layer 1)
layer2Err := InvalidFormat("field A", "Error description")
layer2Err.Wrap(layer1Err)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
errors.Is(layer2Err, layer1Err)
}
}
func Benchmark_ErrorsIs_FourLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
layer2Err := fmt.Errorf("layer 2: %w", layer1Err)
layer3Err := fmt.Errorf("layer 3: %w", layer2Err)
// act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1)
layer4Err := InvalidFormat("field A", "Error description")
layer4Err.Wrap(layer3Err)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
errors.Is(layer4Err, layer1Err)
}
}
func Benchmark_ErrorsAs_OneLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
var err error = layer1Err
b.ReportAllocs()
b.ResetTimer()
var internalErr *testErr
for i := 0; i < b.N; i++ {
errors.As(err, &internalErr)
}
}
func Benchmark_ErrorsAs_TwoLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
// act with error chain: InvalidFormat(layer 2) -> testErr(layer 1)
layer2Err := InvalidFormat("field A", "Error description")
layer2Err.Wrap(layer1Err)
b.ReportAllocs()
b.ResetTimer()
var internalErr *testErr
for i := 0; i < b.N; i++ {
errors.As(layer2Err, &internalErr)
}
}
func Benchmark_ErrorsAs_FourLayerError(b *testing.B) {
layer1Err := &testErr{code: 123}
layer2Err := fmt.Errorf("layer 2: %w", layer1Err)
layer3Err := fmt.Errorf("layer 3: %w", layer2Err)
// act with error chain: InvalidFormat(layer 4) -> Error(layer 3) -> Error(layer 2) -> testErr(layer 1)
layer4Err := InvalidFormat("field A", "Error description")
layer4Err.Wrap(layer3Err)
b.ReportAllocs()
b.ResetTimer()
var internalErr *testErr
for i := 0; i < b.N; i++ {
errors.As(layer4Err, &internalErr)
}
}
func TestFromError(t *testing.T) {
tests := []struct {
name string
givenError error
want *Err
}{
{
"given nil error should return nil",
nil,
nil,
},
{
"given normal error should return nil",
errors.New("normal error"),
nil,
},
{
"given Err should return Err",
ResourceNotFound("fake error"),
ResourceNotFound("fake error"),
},
{
"given error wraps Err should return Err",
fmt.Errorf("outter error wraps %w", ResourceNotFound("fake error")),
ResourceNotFound("fake error"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FromError(tt.givenError); !reflect.DeepEqual(got, tt.want) {
t.Errorf("FromError() = %v, want %v", got, tt.want)
}
})
}
}