837 lines
24 KiB
Go
837 lines
24 KiB
Go
package exerr
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
// ============================================================================
|
|
// Builder / Constructor tests
|
|
// ============================================================================
|
|
|
|
func TestNewBuildsExErr(t *testing.T) {
|
|
err := New(TypeInternal, "boom").Build()
|
|
tst.AssertTrue(t, err != nil)
|
|
|
|
ee, ok := err.(*ExErr)
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, ee.Message, "boom")
|
|
tst.AssertEqual(t, ee.Type, TypeInternal)
|
|
tst.AssertEqual(t, ee.Category, CatSystem)
|
|
tst.AssertEqual(t, ee.Severity, SevErr)
|
|
tst.AssertTrue(t, ee.UniqueID != "")
|
|
}
|
|
|
|
func TestWrapNilProducesInternalError(t *testing.T) {
|
|
err := Wrap(nil, "msg").Build()
|
|
tst.AssertTrue(t, err != nil)
|
|
|
|
ee, _ := err.(*ExErr)
|
|
tst.AssertEqual(t, ee.Message, "msg")
|
|
tst.AssertEqual(t, ee.Type, TypeInternal)
|
|
}
|
|
|
|
func TestWrapForeignError(t *testing.T) {
|
|
plain := errors.New("plain go error")
|
|
err := Wrap(plain, "wrapped").Build()
|
|
ee, _ := err.(*ExErr)
|
|
|
|
// outer is a wrap (TypeWrap), inner is the foreign error
|
|
tst.AssertEqual(t, ee.Type, TypeWrap)
|
|
tst.AssertEqual(t, ee.Category, CatWrap)
|
|
tst.AssertEqual(t, ee.Message, "wrapped")
|
|
tst.AssertTrue(t, ee.OriginalError != nil)
|
|
tst.AssertEqual(t, ee.OriginalError.Category, CatForeign)
|
|
tst.AssertEqual(t, ee.OriginalError.Message, "plain go error")
|
|
}
|
|
|
|
func TestWrapExErrChainsDepth(t *testing.T) {
|
|
e1 := New(TypeInternal, "level-1").Build()
|
|
e2 := Wrap(e1, "level-2").Build()
|
|
e3 := Wrap(e2, "level-3").Build()
|
|
ee3 := e3.(*ExErr)
|
|
|
|
tst.AssertEqual(t, ee3.Depth(), 3)
|
|
}
|
|
|
|
func TestGetReturnsExErr(t *testing.T) {
|
|
plain := errors.New("foreign")
|
|
b := Get(plain)
|
|
tst.AssertTrue(t, b != nil)
|
|
tst.AssertEqual(t, b.errorData.Category, CatForeign)
|
|
tst.AssertEqual(t, b.errorData.Message, "foreign")
|
|
}
|
|
|
|
func TestBuilderWithModifiers(t *testing.T) {
|
|
err := New(TypeInternal, "msg").
|
|
WithType(TypeAssert).
|
|
WithStatuscode(418).
|
|
WithMessage("teapot").
|
|
WithSeverity(SevWarn).
|
|
WithCategory(CatUser).
|
|
Build()
|
|
ee := err.(*ExErr)
|
|
|
|
tst.AssertEqual(t, ee.Type, TypeAssert)
|
|
tst.AssertDeRefEqual(t, ee.StatusCode, 418)
|
|
tst.AssertEqual(t, ee.Message, "teapot")
|
|
tst.AssertEqual(t, ee.Severity, SevWarn)
|
|
tst.AssertEqual(t, ee.Category, CatUser)
|
|
}
|
|
|
|
func TestBuilderSeverityShortcuts(t *testing.T) {
|
|
tst.AssertEqual(t, New(TypeInternal, "x").Err().Build().(*ExErr).Severity, SevErr)
|
|
tst.AssertEqual(t, New(TypeInternal, "x").Warn().Build().(*ExErr).Severity, SevWarn)
|
|
tst.AssertEqual(t, New(TypeInternal, "x").Info().Build().(*ExErr).Severity, SevInfo)
|
|
}
|
|
|
|
func TestBuilderCategoryShortcuts(t *testing.T) {
|
|
tst.AssertEqual(t, New(TypeInternal, "x").User().Build().(*ExErr).Category, CatUser)
|
|
tst.AssertEqual(t, New(TypeInternal, "x").System().Build().(*ExErr).Category, CatSystem)
|
|
}
|
|
|
|
func TestBuilderNoLog(t *testing.T) {
|
|
b := New(TypeInternal, "x").NoLog()
|
|
tst.AssertTrue(t, b.noLog)
|
|
}
|
|
|
|
func TestBuilderExtra(t *testing.T) {
|
|
err := New(TypeInternal, "x").Extra("k", 42).Build()
|
|
ee := err.(*ExErr)
|
|
v, ok := ee.GetExtra("k")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, v.(int), 42)
|
|
}
|
|
|
|
func TestBuilderMetaTypes(t *testing.T) {
|
|
now := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
err := New(TypeInternal, "msg").
|
|
Str("s", "value").
|
|
Int("i", 7).
|
|
Int8("i8", 8).
|
|
Int16("i16", 16).
|
|
Int32("i32", 32).
|
|
Int64("i64", 64).
|
|
Float32("f32", 1.5).
|
|
Float64("f64", 2.5).
|
|
Bool("b", true).
|
|
Bytes("by", []byte{0xAA, 0xBB}).
|
|
Time("t", now).
|
|
Dur("d", 5*time.Second).
|
|
Strs("strs", []string{"a", "b"}).
|
|
Ints("ints", []int{1, 2, 3}).
|
|
Ints32("ints32", []int32{4, 5}).
|
|
Type("typ", "hello").
|
|
Build()
|
|
|
|
ee := err.(*ExErr)
|
|
|
|
gotS, _ := ee.GetMetaString("s")
|
|
tst.AssertEqual(t, gotS, "value")
|
|
|
|
gotI, _ := ee.GetMetaInt("i")
|
|
tst.AssertEqual(t, gotI, 7)
|
|
|
|
gotB, _ := ee.GetMetaBool("b")
|
|
tst.AssertEqual(t, gotB, true)
|
|
|
|
gotF32, _ := ee.GetMetaFloat32("f32")
|
|
tst.AssertEqual(t, gotF32, float32(1.5))
|
|
|
|
gotF64, _ := ee.GetMetaFloat64("f64")
|
|
tst.AssertEqual(t, gotF64, 2.5)
|
|
|
|
gotT, _ := ee.GetMetaTime("t")
|
|
tst.AssertEqual(t, gotT.Equal(now), true)
|
|
}
|
|
|
|
func TestBuilderInterfaceAndAny(t *testing.T) {
|
|
type payload struct {
|
|
A int `json:"a"`
|
|
B string `json:"b"`
|
|
}
|
|
err := New(TypeInternal, "msg").Interface("p", payload{A: 1, B: "x"}).Any("p2", payload{A: 2, B: "y"}).Build()
|
|
ee := err.(*ExErr)
|
|
v1, ok := ee.GetMeta("p")
|
|
tst.AssertTrue(t, ok)
|
|
mv := v1.(AnyWrap)
|
|
tst.AssertTrue(t, strings.Contains(mv.Json, "\"a\":1"))
|
|
_, ok = ee.GetMeta("p2")
|
|
tst.AssertTrue(t, ok)
|
|
}
|
|
|
|
func TestBuilderStack(t *testing.T) {
|
|
err := New(TypeInternal, "msg").Stack().Build()
|
|
ee := err.(*ExErr)
|
|
v, ok := ee.GetMetaString("@Stack")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertTrue(t, len(v) > 0)
|
|
}
|
|
|
|
func TestBuilderErrs(t *testing.T) {
|
|
in := []error{errors.New("first"), errors.New("second")}
|
|
err := New(TypeInternal, "msg").Errs("errs", in).Build()
|
|
ee := err.(*ExErr)
|
|
|
|
v0, ok := ee.GetMetaString("errs[0]")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertTrue(t, strings.Contains(v0, "first"))
|
|
v1, ok := ee.GetMetaString("errs[1]")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertTrue(t, strings.Contains(v1, "second"))
|
|
}
|
|
|
|
type stringerImpl struct{ s string }
|
|
|
|
func (s stringerImpl) String() string { return s.s }
|
|
|
|
func TestBuilderStringerAndId(t *testing.T) {
|
|
err := New(TypeInternal, "msg").
|
|
Stringer("s", stringerImpl{s: "hello"}).
|
|
Id("id", stringerImpl{s: "abc-123"}).
|
|
Build()
|
|
ee := err.(*ExErr)
|
|
|
|
v, _ := ee.GetMetaString("s")
|
|
tst.AssertEqual(t, v, "hello")
|
|
|
|
idv, ok := ee.GetMeta("id")
|
|
tst.AssertTrue(t, ok)
|
|
w := idv.(IDWrap)
|
|
tst.AssertEqual(t, w.Value, "abc-123")
|
|
}
|
|
|
|
func TestBuilderObjectID(t *testing.T) {
|
|
oid := bson.NewObjectID()
|
|
err := New(TypeInternal, "msg").ObjectID("oid", oid).Build()
|
|
ee := err.(*ExErr)
|
|
mv, ok := ee.Meta["oid"]
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, mv.DataType, MDTObjectID)
|
|
tst.AssertEqual(t, mv.Value.(bson.ObjectID), oid)
|
|
}
|
|
|
|
func TestBuilderStrPtr(t *testing.T) {
|
|
s := "hello"
|
|
err := New(TypeInternal, "msg").StrPtr("p", &s).StrPtr("n", nil).Build()
|
|
ee := err.(*ExErr)
|
|
_, ok := ee.Meta["p"]
|
|
tst.AssertTrue(t, ok)
|
|
_, ok = ee.Meta["n"]
|
|
tst.AssertTrue(t, ok)
|
|
}
|
|
|
|
func TestBuilderMetaCollision(t *testing.T) {
|
|
err := New(TypeInternal, "msg").Str("k", "v1").Str("k", "v2").Str("k", "v3").Build()
|
|
ee := err.(*ExErr)
|
|
|
|
v1, _ := ee.GetMetaString("k")
|
|
tst.AssertEqual(t, v1, "v1")
|
|
|
|
v2, ok := ee.GetMetaString("k-2")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, v2, "v2")
|
|
|
|
v3, ok := ee.GetMetaString("k-3")
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, v3, "v3")
|
|
}
|
|
|
|
// ============================================================================
|
|
// FromError tests
|
|
// ============================================================================
|
|
|
|
func TestFromErrorNil(t *testing.T) {
|
|
ee := FromError(nil)
|
|
tst.AssertTrue(t, ee != nil)
|
|
tst.AssertEqual(t, ee.Category, CatForeign)
|
|
tst.AssertEqual(t, ee.WrappedErrType, "nil")
|
|
}
|
|
|
|
func TestFromErrorPassThrough(t *testing.T) {
|
|
orig := New(TypeInternal, "msg").Build().(*ExErr)
|
|
got := FromError(orig)
|
|
tst.AssertTrue(t, orig == got)
|
|
}
|
|
|
|
func TestFromErrorForeign(t *testing.T) {
|
|
in := errors.New("standard")
|
|
ee := FromError(in)
|
|
tst.AssertEqual(t, ee.Category, CatForeign)
|
|
tst.AssertEqual(t, ee.Message, "standard")
|
|
}
|
|
|
|
// ============================================================================
|
|
// ExErr method tests
|
|
// ============================================================================
|
|
|
|
func TestErrorReturnsRecursiveMessage(t *testing.T) {
|
|
in := errors.New("orig")
|
|
err := Wrap(in, "outer").Build()
|
|
tst.AssertTrue(t, strings.Contains(err.Error(), "outer") || strings.Contains(err.Error(), "orig"))
|
|
}
|
|
|
|
func TestUnwrapReturnsOriginalError(t *testing.T) {
|
|
e1 := New(TypeInternal, "inner").Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
tst.AssertEqual(t, e2.Unwrap().(*ExErr) == e1, true)
|
|
}
|
|
|
|
func TestUnwrapForeign(t *testing.T) {
|
|
in := errors.New("std")
|
|
ee := FromError(in)
|
|
u := ee.Unwrap()
|
|
tst.AssertEqual(t, u.Error(), "std")
|
|
}
|
|
|
|
func TestUnwrapNilWhenNoneAvailable(t *testing.T) {
|
|
ee := New(TypeInternal, "msg").Build().(*ExErr)
|
|
tst.AssertTrue(t, ee.Unwrap() == nil)
|
|
}
|
|
|
|
func TestRecursiveMessageSkipsForeign(t *testing.T) {
|
|
in := errors.New("foreign-msg")
|
|
err := Wrap(in, "wrapped-msg").Build().(*ExErr)
|
|
tst.AssertEqual(t, err.RecursiveMessage(), "wrapped-msg")
|
|
}
|
|
|
|
func TestRecursiveMessageFallback(t *testing.T) {
|
|
ee := &ExErr{Message: "self"}
|
|
tst.AssertEqual(t, ee.RecursiveMessage(), "self")
|
|
}
|
|
|
|
func TestRecursiveType(t *testing.T) {
|
|
e1 := New(TypeAssert, "inner").Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
tst.AssertEqual(t, e2.RecursiveType(), TypeAssert)
|
|
}
|
|
|
|
func TestRecursiveStatuscode(t *testing.T) {
|
|
e1 := New(TypeInternal, "inner").WithStatuscode(404).Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
|
|
got := e2.RecursiveStatuscode()
|
|
tst.AssertDeRefEqual(t, got, 404)
|
|
}
|
|
|
|
func TestRecursiveStatuscodeNil(t *testing.T) {
|
|
e1 := New(TypeWrap, "x").Build().(*ExErr)
|
|
tst.AssertTrue(t, e1.RecursiveStatuscode() == nil)
|
|
}
|
|
|
|
func TestRecursiveCategory(t *testing.T) {
|
|
e1 := New(TypeInternal, "inner").User().Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
tst.AssertEqual(t, e2.RecursiveCategory(), CatUser)
|
|
}
|
|
|
|
func TestRecursiveMeta(t *testing.T) {
|
|
e1 := New(TypeInternal, "inner").Str("xkey", "xval").Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
|
|
mv := e2.RecursiveMeta("xkey")
|
|
tst.AssertTrue(t, mv != nil)
|
|
tst.AssertEqual(t, mv.Value.(string), "xval")
|
|
tst.AssertTrue(t, e2.RecursiveMeta("nope") == nil)
|
|
}
|
|
|
|
func TestDepth(t *testing.T) {
|
|
e1 := New(TypeInternal, "1").Build().(*ExErr)
|
|
tst.AssertEqual(t, e1.Depth(), 1)
|
|
e2 := Wrap(e1, "2").Build().(*ExErr)
|
|
tst.AssertEqual(t, e2.Depth(), 2)
|
|
e3 := Wrap(e2, "3").Build().(*ExErr)
|
|
tst.AssertEqual(t, e3.Depth(), 3)
|
|
}
|
|
|
|
func TestGetMetaStringTypeMismatch(t *testing.T) {
|
|
err := New(TypeInternal, "msg").Int("k", 1).Build().(*ExErr)
|
|
_, ok := err.GetMetaString("k")
|
|
tst.AssertFalse(t, ok)
|
|
}
|
|
|
|
func TestGetMetaMissing(t *testing.T) {
|
|
err := New(TypeInternal, "msg").Build().(*ExErr)
|
|
_, ok := err.GetMeta("missing")
|
|
tst.AssertFalse(t, ok)
|
|
}
|
|
|
|
func TestGetExtraMissing(t *testing.T) {
|
|
err := New(TypeInternal, "msg").Build().(*ExErr)
|
|
_, ok := err.GetExtra("missing")
|
|
tst.AssertFalse(t, ok)
|
|
}
|
|
|
|
func TestUniqueIDsCollects(t *testing.T) {
|
|
e1 := New(TypeInternal, "1").Build().(*ExErr)
|
|
e2 := Wrap(e1, "2").Build().(*ExErr)
|
|
e3 := Wrap(e2, "3").Build().(*ExErr)
|
|
ids := e3.UniqueIDs()
|
|
tst.AssertEqual(t, len(ids), 3)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Format / Log
|
|
// ============================================================================
|
|
|
|
func TestFormatLogShort(t *testing.T) {
|
|
err := New(TypeAssert, "boom").Build().(*ExErr)
|
|
out := err.FormatLog(LogPrintShort)
|
|
tst.AssertTrue(t, strings.Contains(out, "boom"))
|
|
tst.AssertTrue(t, strings.Contains(out, TypeAssert.Key))
|
|
}
|
|
|
|
func TestFormatLogOverview(t *testing.T) {
|
|
e1 := New(TypeAssert, "inner").Build().(*ExErr)
|
|
e2 := Wrap(e1, "outer").Build().(*ExErr)
|
|
out := e2.FormatLog(LogPrintOverview)
|
|
tst.AssertTrue(t, strings.Contains(out, "outer"))
|
|
tst.AssertTrue(t, strings.Contains(out, "inner"))
|
|
}
|
|
|
|
func TestFormatLogFull(t *testing.T) {
|
|
err := New(TypeInternal, "boom").Str("k", "v").Build().(*ExErr)
|
|
out := err.FormatLog(LogPrintFull)
|
|
tst.AssertTrue(t, strings.Contains(out, "boom"))
|
|
tst.AssertTrue(t, strings.Contains(out, "k"))
|
|
}
|
|
|
|
func TestFormatLogUnknownLevel(t *testing.T) {
|
|
err := New(TypeInternal, "boom").Build().(*ExErr)
|
|
out := err.FormatLog(LogPrintLevel("__nope__"))
|
|
tst.AssertTrue(t, strings.HasPrefix(out, "[?["))
|
|
}
|
|
|
|
func TestBuilderFormat(t *testing.T) {
|
|
b := New(TypeInternal, "boom")
|
|
out := b.Format(LogPrintShort)
|
|
tst.AssertTrue(t, strings.Contains(out, "boom"))
|
|
}
|
|
|
|
// ============================================================================
|
|
// helper.go
|
|
// ============================================================================
|
|
|
|
func TestIsTypeMatching(t *testing.T) {
|
|
err := New(TypeAssert, "x").Build()
|
|
tst.AssertTrue(t, IsType(err, TypeAssert))
|
|
tst.AssertFalse(t, IsType(err, TypeNotImplemented))
|
|
tst.AssertFalse(t, IsType(nil, TypeAssert))
|
|
}
|
|
|
|
func TestIsTypeRecursive(t *testing.T) {
|
|
e1 := New(TypeAssert, "inner").Build()
|
|
e2 := Wrap(e1, "outer").Build()
|
|
tst.AssertTrue(t, IsType(e2, TypeAssert))
|
|
}
|
|
|
|
func TestIsFromIdentity(t *testing.T) {
|
|
e := errors.New("x")
|
|
tst.AssertTrue(t, IsFrom(e, e))
|
|
tst.AssertFalse(t, IsFrom(nil, e))
|
|
}
|
|
|
|
func TestIsFromForeign(t *testing.T) {
|
|
src := errors.New("origmsg")
|
|
wrap := Wrap(src, "outer").Build()
|
|
tst.AssertTrue(t, IsFrom(wrap, src))
|
|
|
|
other := errors.New("other")
|
|
tst.AssertFalse(t, IsFrom(wrap, other))
|
|
}
|
|
|
|
func TestHasSourceMessage(t *testing.T) {
|
|
src := errors.New("origmsg")
|
|
wrap := Wrap(src, "outer").Build()
|
|
tst.AssertTrue(t, HasSourceMessage(wrap, "origmsg"))
|
|
tst.AssertFalse(t, HasSourceMessage(wrap, "other"))
|
|
tst.AssertFalse(t, HasSourceMessage(nil, "x"))
|
|
}
|
|
|
|
func TestMessageMatch(t *testing.T) {
|
|
e := New(TypeInternal, "alpha-beta").Build()
|
|
tst.AssertTrue(t, MessageMatch(e, func(s string) bool { return strings.Contains(s, "alpha") }))
|
|
tst.AssertFalse(t, MessageMatch(e, func(s string) bool { return strings.Contains(s, "missing") }))
|
|
tst.AssertFalse(t, MessageMatch(nil, func(s string) bool { return true }))
|
|
}
|
|
|
|
func TestOriginalError(t *testing.T) {
|
|
src := errors.New("the-source")
|
|
wrap := Wrap(src, "outer").Build()
|
|
got := OriginalError(wrap)
|
|
tst.AssertEqual(t, got.Error(), "the-source")
|
|
tst.AssertTrue(t, OriginalError(nil) == nil)
|
|
}
|
|
|
|
func TestUniqueIDHelper(t *testing.T) {
|
|
err := New(TypeInternal, "x").Build()
|
|
id := UniqueID(err)
|
|
tst.AssertTrue(t, id != nil)
|
|
tst.AssertTrue(t, *id != "")
|
|
|
|
tst.AssertTrue(t, UniqueID(nil) == nil)
|
|
|
|
plain := errors.New("plain")
|
|
tst.AssertTrue(t, UniqueID(plain) == nil)
|
|
}
|
|
|
|
// ============================================================================
|
|
// errors.Is / errors.As
|
|
// ============================================================================
|
|
|
|
type customErr struct{ msg string }
|
|
|
|
func (c customErr) Error() string { return c.msg }
|
|
|
|
func TestErrorsAsForeign(t *testing.T) {
|
|
src := customErr{msg: "x"}
|
|
wrap := Wrap(src, "outer").Build()
|
|
|
|
var got customErr
|
|
ok := errors.As(wrap, &got)
|
|
tst.AssertTrue(t, ok)
|
|
tst.AssertEqual(t, got.msg, "x")
|
|
}
|
|
|
|
func TestErrorsIsForeign(t *testing.T) {
|
|
src := customErr{msg: "x"}
|
|
wrap := Wrap(src, "outer").Build()
|
|
|
|
tst.AssertTrue(t, errors.Is(wrap, customErr{msg: "x"}))
|
|
tst.AssertFalse(t, errors.Is(wrap, customErr{msg: "y"}))
|
|
}
|
|
|
|
// ============================================================================
|
|
// MetaValue serialize / deserialize roundtrip
|
|
// ============================================================================
|
|
|
|
func TestMetaValueRoundtripPrimitives(t *testing.T) {
|
|
cases := []MetaValue{
|
|
{DataType: MDTString, Value: "hello"},
|
|
{DataType: MDTInt, Value: 42},
|
|
{DataType: MDTInt8, Value: int8(8)},
|
|
{DataType: MDTInt16, Value: int16(16)},
|
|
{DataType: MDTInt32, Value: int32(32)},
|
|
{DataType: MDTInt64, Value: int64(64)},
|
|
{DataType: MDTFloat32, Value: float32(1.5)},
|
|
{DataType: MDTFloat64, Value: float64(2.5)},
|
|
{DataType: MDTBool, Value: true},
|
|
{DataType: MDTBool, Value: false},
|
|
{DataType: MDTBytes, Value: []byte{0x01, 0x02, 0xAB}},
|
|
{DataType: MDTStringArray, Value: []string{"a", "b"}},
|
|
{DataType: MDTIntArray, Value: []int{1, 2, 3}},
|
|
{DataType: MDTInt32Array, Value: []int32{4, 5}},
|
|
{DataType: MDTNil, Value: nil},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
s, err := c.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, dec.Deserialize(s, c.DataType))
|
|
tst.AssertStrRepEqual(t, dec.Value, c.Value)
|
|
}
|
|
}
|
|
|
|
func TestMetaValueRoundtripStringPtr(t *testing.T) {
|
|
v := "hello"
|
|
mv := MetaValue{DataType: MDTStringPtr, Value: &v}
|
|
s, err := mv.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, dec.Deserialize(s, MDTStringPtr))
|
|
tst.AssertEqual(t, *(dec.Value.(*string)), v)
|
|
|
|
mv2 := MetaValue{DataType: MDTStringPtr, Value: (*string)(nil)}
|
|
s, err = mv2.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
tst.AssertEqual(t, s, "#")
|
|
}
|
|
|
|
func TestMetaValueRoundtripTime(t *testing.T) {
|
|
tm := time.Date(2025, 4, 27, 12, 34, 56, 12345, time.UTC)
|
|
mv := MetaValue{DataType: MDTTime, Value: tm}
|
|
s, err := mv.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, dec.Deserialize(s, MDTTime))
|
|
got := dec.Value.(time.Time)
|
|
tst.AssertEqual(t, got.Unix(), tm.Unix())
|
|
tst.AssertEqual(t, got.Nanosecond(), tm.Nanosecond())
|
|
}
|
|
|
|
func TestMetaValueRoundtripDuration(t *testing.T) {
|
|
d := 3*time.Second + 250*time.Millisecond
|
|
mv := MetaValue{DataType: MDTDuration, Value: d}
|
|
s, err := mv.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, dec.Deserialize(s, MDTDuration))
|
|
tst.AssertEqual(t, dec.Value.(time.Duration), d)
|
|
}
|
|
|
|
func TestMetaValueRoundtripObjectID(t *testing.T) {
|
|
oid := bson.NewObjectID()
|
|
mv := MetaValue{DataType: MDTObjectID, Value: oid}
|
|
s, err := mv.SerializeValue()
|
|
tst.AssertNoErr(t, err)
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, dec.Deserialize(s, MDTObjectID))
|
|
tst.AssertEqual(t, dec.Value.(bson.ObjectID), oid)
|
|
}
|
|
|
|
func TestMetaValueDeserializeUnknownType(t *testing.T) {
|
|
var mv MetaValue
|
|
err := mv.Deserialize("x", metaDataType("unknown"))
|
|
tst.AssertTrue(t, err != nil)
|
|
}
|
|
|
|
func TestMetaValueDeserializeBadBool(t *testing.T) {
|
|
var mv MetaValue
|
|
err := mv.Deserialize("nope", MDTBool)
|
|
tst.AssertTrue(t, err != nil)
|
|
}
|
|
|
|
func TestMetaValueShortString(t *testing.T) {
|
|
cases := []struct {
|
|
mv MetaValue
|
|
out string
|
|
}{
|
|
{MetaValue{DataType: MDTString, Value: "hello"}, "hello"},
|
|
{MetaValue{DataType: MDTInt, Value: 42}, "42"},
|
|
{MetaValue{DataType: MDTBool, Value: true}, "true"},
|
|
{MetaValue{DataType: MDTNil, Value: nil}, "<<null>>"},
|
|
}
|
|
for _, c := range cases {
|
|
tst.AssertEqual(t, c.mv.ShortString(100), c.out)
|
|
}
|
|
}
|
|
|
|
func TestMetaValueValueString(t *testing.T) {
|
|
mv := MetaValue{DataType: MDTInt, Value: 42}
|
|
tst.AssertEqual(t, mv.ValueString(), "42")
|
|
|
|
mv = MetaValue{DataType: MDTString, Value: "ok"}
|
|
tst.AssertEqual(t, mv.ValueString(), "ok")
|
|
}
|
|
|
|
func TestMetaValueJSONMarshal(t *testing.T) {
|
|
mv := MetaValue{DataType: MDTString, Value: "abc"}
|
|
bin, err := json.Marshal(mv)
|
|
tst.AssertNoErr(t, err)
|
|
|
|
var dec MetaValue
|
|
tst.AssertNoErr(t, json.Unmarshal(bin, &dec))
|
|
tst.AssertEqual(t, dec.DataType, MDTString)
|
|
tst.AssertEqual(t, dec.Value.(string), "abc")
|
|
}
|
|
|
|
func TestMetaValueJSONMarshalInvalidString(t *testing.T) {
|
|
var mv MetaValue
|
|
err := json.Unmarshal([]byte("\"badformat\""), &mv)
|
|
tst.AssertTrue(t, err != nil)
|
|
}
|
|
|
|
// ============================================================================
|
|
// MetaMap
|
|
// ============================================================================
|
|
|
|
func TestMetaMapAny(t *testing.T) {
|
|
mm := MetaMap{}
|
|
tst.AssertFalse(t, mm.Any())
|
|
mm.add("k", MDTString, "v")
|
|
tst.AssertTrue(t, mm.Any())
|
|
}
|
|
|
|
func TestMetaMapFormatOneLine(t *testing.T) {
|
|
mm := MetaMap{}
|
|
mm.add("k", MDTString, "v")
|
|
out := mm.FormatOneLine(100)
|
|
tst.AssertTrue(t, strings.Contains(out, "k"))
|
|
tst.AssertTrue(t, strings.Contains(out, "v"))
|
|
}
|
|
|
|
func TestMetaMapFormatMultiLine(t *testing.T) {
|
|
mm := MetaMap{}
|
|
mm.add("k1", MDTString, "v1")
|
|
mm.add("gin_body", MDTString, "should-be-skipped")
|
|
out := mm.FormatMultiLine("", " ", 100)
|
|
tst.AssertTrue(t, strings.Contains(out, "k1"))
|
|
tst.AssertFalse(t, strings.Contains(out, "should-be-skipped"))
|
|
}
|
|
|
|
// ============================================================================
|
|
// typeWrapper.go
|
|
// ============================================================================
|
|
|
|
func TestIDWrap(t *testing.T) {
|
|
w := newIDWrap(stringerImpl{s: "id-1"})
|
|
tst.AssertEqual(t, w.Value, "id-1")
|
|
tst.AssertFalse(t, w.IsNil)
|
|
|
|
s := w.Serialize()
|
|
got := deserializeIDWrap(s)
|
|
tst.AssertEqual(t, got.Value, "id-1")
|
|
tst.AssertEqual(t, got.Type, w.Type)
|
|
}
|
|
|
|
func TestIDWrapNil(t *testing.T) {
|
|
var nilStringer fmt.Stringer = (*stringerImpl)(nil)
|
|
w := newIDWrap(nilStringer)
|
|
tst.AssertTrue(t, w.IsNil)
|
|
|
|
s := w.Serialize()
|
|
got := deserializeIDWrap(s)
|
|
tst.AssertTrue(t, got.IsNil)
|
|
}
|
|
|
|
func TestAnyWrap(t *testing.T) {
|
|
type p struct {
|
|
X int `json:"x"`
|
|
}
|
|
w := newAnyWrap(p{X: 7})
|
|
tst.AssertFalse(t, w.IsError)
|
|
tst.AssertFalse(t, w.IsNil)
|
|
tst.AssertTrue(t, strings.Contains(w.Json, "\"x\":7"))
|
|
|
|
s := w.Serialize()
|
|
got := deserializeAnyWrap(s)
|
|
tst.AssertEqual(t, got.IsError, false)
|
|
tst.AssertTrue(t, strings.Contains(got.Json, "\"x\":7"))
|
|
}
|
|
|
|
func TestAnyWrapNil(t *testing.T) {
|
|
w := newAnyWrap(nil)
|
|
tst.AssertTrue(t, w.IsNil)
|
|
|
|
s := w.Serialize()
|
|
got := deserializeAnyWrap(s)
|
|
tst.AssertTrue(t, got.IsNil)
|
|
}
|
|
|
|
func TestAnyWrapDeserializeBad(t *testing.T) {
|
|
got := deserializeAnyWrap("xx")
|
|
tst.AssertTrue(t, got.IsError)
|
|
}
|
|
|
|
// ============================================================================
|
|
// dataType.go
|
|
// ============================================================================
|
|
|
|
func TestNewTypeRegisters(t *testing.T) {
|
|
custom := NewType("UNIT_TEST_CUSTOM_TYPE", new(503))
|
|
tst.AssertEqual(t, custom.Key, "UNIT_TEST_CUSTOM_TYPE")
|
|
tst.AssertDeRefEqual(t, custom.DefaultStatusCode, 503)
|
|
|
|
all := ListRegisteredTypes()
|
|
found := false
|
|
for _, et := range all {
|
|
if et.Key == "UNIT_TEST_CUSTOM_TYPE" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
tst.AssertTrue(t, found)
|
|
}
|
|
|
|
func TestErrorTypeJSONUnmarshalKnown(t *testing.T) {
|
|
var et ErrorType
|
|
tst.AssertNoErr(t, json.Unmarshal([]byte("\"NOT_IMPLEMENTED\""), &et))
|
|
tst.AssertEqual(t, et.Key, TypeNotImplemented.Key)
|
|
}
|
|
|
|
func TestErrorTypeJSONUnmarshalUnknown(t *testing.T) {
|
|
var et ErrorType
|
|
tst.AssertNoErr(t, json.Unmarshal([]byte("\"COMPLETELY_UNKNOWN_TYPE_QQQ\""), &et))
|
|
tst.AssertEqual(t, et.Key, "COMPLETELY_UNKNOWN_TYPE_QQQ")
|
|
tst.AssertTrue(t, et.DefaultStatusCode == nil)
|
|
}
|
|
|
|
func TestCategoryAndSeverityJSONMarshal(t *testing.T) {
|
|
bin, err := json.Marshal(CatUser)
|
|
tst.AssertNoErr(t, err)
|
|
tst.AssertEqual(t, string(bin), "\"User\"")
|
|
|
|
var c ErrorCategory
|
|
tst.AssertNoErr(t, json.Unmarshal(bin, &c))
|
|
tst.AssertEqual(t, c, CatUser)
|
|
|
|
bin, err = json.Marshal(SevWarn)
|
|
tst.AssertNoErr(t, err)
|
|
tst.AssertEqual(t, string(bin), "\"Warn\"")
|
|
}
|
|
|
|
// ============================================================================
|
|
// proxy.go
|
|
// ============================================================================
|
|
|
|
func TestProxy(t *testing.T) {
|
|
ee := New(TypeInternal, "x").Build().(*ExErr)
|
|
p := Proxy{v: *ee}
|
|
tst.AssertEqual(t, p.UniqueID(), ee.UniqueID)
|
|
tst.AssertEqual(t, p.Get().Message, ee.Message)
|
|
}
|
|
|
|
// ============================================================================
|
|
// listener.go
|
|
// ============================================================================
|
|
|
|
func TestRegisterListenerInvokedOnBuild(t *testing.T) {
|
|
var gotMethod Method
|
|
var gotErr *ExErr
|
|
RegisterListener(func(method Method, v *ExErr, opt ListenerOpt) {
|
|
if v != nil && strings.Contains(v.Message, "listener-marker-1") {
|
|
gotMethod = method
|
|
gotErr = v
|
|
}
|
|
})
|
|
|
|
_ = New(TypeInternal, "listener-marker-1").Build()
|
|
|
|
tst.AssertEqual(t, gotMethod, MethodBuild)
|
|
tst.AssertTrue(t, gotErr != nil)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Initialized
|
|
// ============================================================================
|
|
|
|
func TestInitialized(t *testing.T) {
|
|
tst.AssertTrue(t, Initialized())
|
|
}
|
|
|
|
// ============================================================================
|
|
// JSON output (toJson / ToAPIJson)
|
|
// ============================================================================
|
|
|
|
func TestToAPIJsonContainsCoreFields(t *testing.T) {
|
|
err := New(TypeInternal, "boom").Str("k", "v").Extra("ex", 1).Build().(*ExErr)
|
|
out := err.ToAPIJson(false, true, true)
|
|
|
|
tst.AssertEqual(t, out["errorid"].(string), err.UniqueID)
|
|
tst.AssertEqual(t, out["errorcode"].(string), TypeInternal.Key)
|
|
tst.AssertEqual(t, out["category"].(string), CatSystem.Category)
|
|
tst.AssertEqual(t, out["message"].(string), "boom")
|
|
|
|
_, hasData := out["__data"]
|
|
tst.AssertTrue(t, hasData)
|
|
|
|
tst.AssertEqual(t, out["ex"].(int), 1)
|
|
}
|
|
|
|
func TestToDefaultAPIJson(t *testing.T) {
|
|
err := New(TypeInternal, "boom").Build().(*ExErr)
|
|
out, jerr := err.ToDefaultAPIJson()
|
|
tst.AssertNoErr(t, jerr)
|
|
tst.AssertTrue(t, strings.Contains(out, "boom"))
|
|
tst.AssertTrue(t, strings.Contains(out, err.UniqueID))
|
|
}
|