2025-10-26 20:32:02 +08:00
|
|
|
|
package errorx_test
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-09-21 00:43:43 +08:00
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
2025-10-26 20:32:02 +08:00
|
|
|
|
"testing"
|
2025-10-26 20:58:12 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/yuninks/errorx"
|
2025-10-26 20:32:02 +08:00
|
|
|
|
"github.com/yuninks/langx"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
// 测试用的错误常量:key + 业务码 + 默认文案(占位符用 #name#)
|
|
|
|
|
|
var (
|
|
|
|
|
|
ErrUserNotFound = errorx.New("test_user_not_found", 10001, "用户不存在")
|
|
|
|
|
|
ErrParamInvalid = errorx.New("test_param_invalid", 10002, "参数错误: #field#")
|
|
|
|
|
|
ErrDB = errorx.New("test_db_error", 50001, "数据库繁忙")
|
|
|
|
|
|
)
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
func TestMain(m *testing.M) {
|
2025-10-26 20:32:02 +08:00
|
|
|
|
langx.InitLangx(
|
|
|
|
|
|
langx.SetDefaultLanguage("zh"),
|
2026-09-21 00:43:43 +08:00
|
|
|
|
langx.SetDefaultCode(-1), // 故意设成 -1,用来验证未注册的 key 不会退化成默认码
|
2025-10-26 20:32:02 +08:00
|
|
|
|
)
|
2026-09-21 00:43:43 +08:00
|
|
|
|
langx.AppendTrans("en", map[string]string{
|
|
|
|
|
|
"test_user_not_found": "user not found",
|
|
|
|
|
|
"test_param_invalid": "invalid parameter: #field#",
|
|
|
|
|
|
"test_db_error": "database is busy",
|
2025-10-26 20:32:02 +08:00
|
|
|
|
})
|
2026-09-21 00:43:43 +08:00
|
|
|
|
os.Exit(m.Run())
|
|
|
|
|
|
}
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
func TestErrBasics(t *testing.T) {
|
|
|
|
|
|
e := ErrUserNotFound.Err()
|
|
|
|
|
|
|
|
|
|
|
|
if got, want := e.GetCode(), 10001; got != want {
|
|
|
|
|
|
t.Fatalf("GetCode() = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.GetKey(), "test_user_not_found"; got != want {
|
|
|
|
|
|
t.Fatalf("GetKey() = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.GetDefault(), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("GetDefault() = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.Error(), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("Error() = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 错误常量本身也实现了 error 接口
|
|
|
|
|
|
var err error = ErrUserNotFound
|
|
|
|
|
|
if got, want := err.Error(), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("ErrorLanguage.Error() = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if got, want := ErrUserNotFound.GetCode(), 10001; got != want {
|
|
|
|
|
|
t.Fatalf("ErrorLanguage.GetCode() = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := ErrUserNotFound.GetDefault(), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("ErrorLanguage.GetDefault() = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestPlaceholder(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
got string
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"ErrKV", ErrParamInvalid.ErrKV("field", "age").Error(), "参数错误: age"},
|
|
|
|
|
|
{"ErrWith", ErrParamInvalid.ErrWith(map[string]string{"field": "name"}).Error(), "参数错误: name"},
|
|
|
|
|
|
{"Err().WithFormatKV", ErrParamInvalid.Err().WithFormatKV("field", "id").Error(), "参数错误: id"},
|
|
|
|
|
|
{"MsgKV", ErrParamInvalid.MsgKV(context.Background(), "field", "phone"), "参数错误: phone"},
|
|
|
|
|
|
{"MsgWith", ErrParamInvalid.MsgWith(context.Background(), map[string]string{"field": "mail"}), "参数错误: mail"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
if c.got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %q, want %q", c.name, c.got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestMultiLanguage(t *testing.T) {
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
enCtx := langx.SetCtxLang(ctx, "en")
|
|
|
|
|
|
|
|
|
|
|
|
if got, want := ErrUserNotFound.Msg(ctx), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("中文 Msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := ErrUserNotFound.Msg(enCtx), "user not found"; got != want {
|
|
|
|
|
|
t.Fatalf("英文 Msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := ErrParamInvalid.MsgKV(enCtx, "field", "age"), "invalid parameter: age"; got != want {
|
|
|
|
|
|
t.Fatalf("英文占位符 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 固定语言:不再跟随 ctx
|
|
|
|
|
|
err := ErrUserNotFound.Err().WithLang("en")
|
|
|
|
|
|
if got, want := err.Msg(ctx), "user not found"; got != want {
|
|
|
|
|
|
t.Fatalf("WithLang 后 Msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := err.Error(), "user not found"; got != want {
|
|
|
|
|
|
t.Fatalf("WithLang 后 Error = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 未注册的语言回退到默认语言,而不是吐裸 key
|
|
|
|
|
|
if got, want := ErrUserNotFound.Msg(langx.SetCtxLang(ctx, "ja")), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("未注册语言 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestImmutability(t *testing.T) {
|
|
|
|
|
|
base := ErrParamInvalid.Err()
|
|
|
|
|
|
derived := base.WithFormatKV("field", "age")
|
|
|
|
|
|
|
|
|
|
|
|
if got, want := base.Error(), "参数错误: #field#"; got != want {
|
|
|
|
|
|
t.Fatalf("派生后原错误被修改:%q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := derived.Error(), "参数错误: age"; got != want {
|
|
|
|
|
|
t.Fatalf("派生错误 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := ErrParamInvalid.Error(), "参数错误: #field#"; got != want {
|
|
|
|
|
|
t.Fatalf("全局常量被污染:%q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetFormat 返回副本,外部修改不影响错误本身
|
|
|
|
|
|
f := derived.GetFormat()
|
|
|
|
|
|
f["field"] = "hacked"
|
|
|
|
|
|
if got, want := derived.Error(), "参数错误: age"; got != want {
|
|
|
|
|
|
t.Fatalf("GetFormat 泄漏了内部 map:%q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WithLang / WithFormat 不互相影响
|
|
|
|
|
|
a := base.WithLang("en")
|
|
|
|
|
|
b := base.WithFormatKV("field", "x")
|
|
|
|
|
|
if a.GetLang() != "en" || b.GetLang() != "" {
|
|
|
|
|
|
t.Fatalf("派生值互相污染:a.lang=%q b.lang=%q", a.GetLang(), b.GetLang())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestConcurrentUse(t *testing.T) {
|
|
|
|
|
|
// 同一个错误值被多个 goroutine 共享:旧版本要么 fatal: concurrent map writes,
|
|
|
|
|
|
// 要么不同请求之间互相改写文案。
|
|
|
|
|
|
base := ErrParamInvalid.Err()
|
|
|
|
|
|
const workers, rounds = 32, 50
|
|
|
|
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
bad := make(chan string, workers)
|
|
|
|
|
|
for w := 0; w < workers; w++ {
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
go func(w int) {
|
|
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
for r := 0; r < rounds; r++ {
|
|
|
|
|
|
v := fmt.Sprintf("%d-%d", w, r)
|
|
|
|
|
|
if got, want := base.WithFormatKV("field", v).Error(), "参数错误: "+v; got != want {
|
|
|
|
|
|
bad <- fmt.Sprintf("派生错误被串改: got %q, want %q", got, want)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := base.GetFormat()["field"], ""; got != want {
|
|
|
|
|
|
bad <- fmt.Sprintf("原错误被派生值污染: got %q, want %q", got, want)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := ErrUserNotFound.Error(), "用户不存在"; got != want {
|
|
|
|
|
|
bad <- fmt.Sprintf("全局常量被改写: got %q, want %q", got, want)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}(w)
|
|
|
|
|
|
}
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
close(bad)
|
|
|
|
|
|
for msg := range bad {
|
|
|
|
|
|
t.Fatal(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestUnknownKeyIsNotSuccess(t *testing.T) {
|
|
|
|
|
|
e := errorx.NewError(context.Background(), "totally_unknown_key")
|
|
|
|
|
|
|
|
|
|
|
|
if got, want := e.GetCode(), errorx.ErrUnregistered.GetCode(); got != want {
|
|
|
|
|
|
t.Fatalf("未注册 key 的 code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e.GetCode() == 200 {
|
|
|
|
|
|
t.Fatal("未注册的 key 被伪装成了成功(200)")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !strings.Contains(e.Error(), "totally_unknown_key") {
|
|
|
|
|
|
t.Fatalf("未注册 key 的文案应包含原始 key,实际 %q", e.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
// 空 key 同样按未注册处理
|
|
|
|
|
|
if got, want := errorx.NewError(context.Background(), "").GetCode(), errorx.ErrUnregistered.GetCode(); got != want {
|
|
|
|
|
|
t.Fatalf("空 key 的 code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestRegistryOverwriteKeepsBuiltin(t *testing.T) {
|
|
|
|
|
|
// 用户习惯用 Register*,它会整体替换 langx 的注册表
|
|
|
|
|
|
langx.RegisterCode(map[string]int{"biz_code": 500})
|
|
|
|
|
|
langx.RegisterTrans("zh", map[string]string{"biz_code": "业务错误"})
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
langx.AppendCode(map[string]int{"error": 400, "success": 200, "error_with_msg": 400})
|
|
|
|
|
|
langx.AppendTrans("zh", map[string]string{
|
|
|
|
|
|
"error": "操作失败", "success": "操作成功", "error_with_msg": "操作失败: #msg#",
|
|
|
|
|
|
})
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// code 来自错误常量自身,不会被清空
|
|
|
|
|
|
if got, want := errorx.Error.Err().GetCode(), 400; got != want {
|
|
|
|
|
|
t.Fatalf("内置错误 code = %d, want %d(旧版本会变成 200)", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 文案用错误常量自带的默认值兜底,不会退化成裸 key
|
|
|
|
|
|
if got, want := errorx.Error.Err().Error(), "操作失败"; got != want {
|
|
|
|
|
|
t.Fatalf("内置错误文案 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := errorx.ErrWithMsg.ErrKV("msg", "超时").Error(), "操作失败: 超时"; got != want {
|
|
|
|
|
|
t.Fatalf("内置错误占位符 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 用户仍然可以用资源里的 key
|
|
|
|
|
|
if got, want := errorx.NewError(context.Background(), "biz_code").GetCode(), 500; got != want {
|
|
|
|
|
|
t.Fatalf("资源 key 的 code = %d, want %d", got, want)
|
2025-10-26 20:32:02 +08:00
|
|
|
|
}
|
2026-09-21 00:43:43 +08:00
|
|
|
|
}
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
func TestNilAndDirtyCtx(t *testing.T) {
|
|
|
|
|
|
if got, want := ErrUserNotFound.Msg(nil), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("nil ctx = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// langx 会对非 string 的 ctx 值做强制断言,errorx 必须兜住
|
|
|
|
|
|
dirty := context.WithValue(context.Background(), "Accept-Language", 123)
|
|
|
|
|
|
if got, want := ErrUserNotFound.Msg(dirty), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("脏 ctx = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := errorx.NewError(nil, "test_user_not_found").Error(), "用户不存在"; got != want {
|
|
|
|
|
|
t.Fatalf("nil ctx 动态构造 = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
func TestErrorsIsAsUnwrap(t *testing.T) {
|
|
|
|
|
|
cause := errors.New("dial tcp 127.0.0.1:3306: connection refused")
|
|
|
|
|
|
err := ErrDB.Wrap(cause)
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
if !errors.Is(err, ErrDB) {
|
|
|
|
|
|
t.Fatal("errors.Is(err, ErrDB) = false, want true")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !errors.Is(err, cause) {
|
|
|
|
|
|
t.Fatal("errors.Is 应该能穿透到底层错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(err, ErrUserNotFound) {
|
|
|
|
|
|
t.Fatal("不同 key 的错误不应相等")
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := errors.Unwrap(err); got != cause {
|
|
|
|
|
|
t.Fatalf("Unwrap() = %v, want %v", got, cause)
|
|
|
|
|
|
}
|
|
|
|
|
|
var ei errorx.ErrorInterface
|
|
|
|
|
|
if !errors.As(err, &ei) {
|
|
|
|
|
|
t.Fatal("errors.As(err, &ErrorInterface) = false, want true")
|
|
|
|
|
|
}
|
|
|
|
|
|
if ei.GetCode() != 50001 {
|
|
|
|
|
|
t.Fatalf("errors.As 拿到的 code = %d", ei.GetCode())
|
|
|
|
|
|
}
|
|
|
|
|
|
// 文案里不应出现底层错误的细节
|
|
|
|
|
|
if strings.Contains(err.Error(), "connection refused") {
|
|
|
|
|
|
t.Fatalf("底层错误不应出现在对外文案里:%q", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
// 常量在左边:错误常量直接当 error 返回时走 ErrorLanguage.Is
|
|
|
|
|
|
if !errors.Is(ErrUserNotFound, ErrUserNotFound.Err()) {
|
|
|
|
|
|
t.Fatal("errors.Is(常量, 错误值) = false, want true")
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(ErrUserNotFound, ErrDB.Err()) {
|
|
|
|
|
|
t.Fatal("不同 key 的常量不应匹配")
|
|
|
|
|
|
}
|
|
|
|
|
|
if errors.Is(ErrUserNotFound, errors.New("plain")) {
|
|
|
|
|
|
t.Fatal("常量不应匹配非 errorx 的错误")
|
2025-10-26 20:32:02 +08:00
|
|
|
|
}
|
2026-09-21 00:43:43 +08:00
|
|
|
|
// 非 errorx 的普通错误不应匹配
|
|
|
|
|
|
if errors.Is(err, errors.New("boom")) {
|
|
|
|
|
|
t.Fatal("普通 error 不应匹配 errorx 错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
func TestStackTrace(t *testing.T) {
|
|
|
|
|
|
st := ErrUserNotFound.Err().StackTrace()
|
|
|
|
|
|
if len(st) == 0 {
|
|
|
|
|
|
t.Fatal("StackTrace() 为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
var found bool
|
|
|
|
|
|
for _, f := range st {
|
|
|
|
|
|
if strings.HasPrefix(f.Function, "github.com/yuninks/errorx.") {
|
|
|
|
|
|
t.Fatalf("栈里不应包含 errorx 自身帧:%s", f.Function)
|
|
|
|
|
|
}
|
|
|
|
|
|
if strings.Contains(f.Function, "TestStackTrace") {
|
|
|
|
|
|
found = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !found {
|
|
|
|
|
|
t.Fatal("栈里没有调用方帧")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !strings.Contains(st.String(), "error_test.go") {
|
|
|
|
|
|
t.Fatalf("StackTrace.String() 内容异常:%s", st.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
// 空栈渲染为空串,不 panic
|
|
|
|
|
|
var empty errorx.StackTrace
|
|
|
|
|
|
if got := empty.String(); got != "" {
|
|
|
|
|
|
t.Fatalf("空 StackTrace.String() = %q, want \"\"", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
var f errorx.Frame
|
|
|
|
|
|
if !strings.Contains(f.String(), ":0") {
|
|
|
|
|
|
t.Fatalf("Frame.String() = %q", f.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestFormatVerb(t *testing.T) {
|
|
|
|
|
|
err := ErrDB.Wrap(errors.New("boom"))
|
2025-10-26 20:32:02 +08:00
|
|
|
|
|
2026-09-21 00:43:43 +08:00
|
|
|
|
if got, want := fmt.Sprintf("%v", err), "数据库繁忙"; got != want {
|
|
|
|
|
|
t.Fatalf("%%v = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := fmt.Sprintf("%s", err), "数据库繁忙"; got != want {
|
|
|
|
|
|
t.Fatalf("%%s = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := fmt.Sprintf("%q", err), "\"数据库繁忙\""; got != want {
|
|
|
|
|
|
t.Fatalf("%%q = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
plus := fmt.Sprintf("%+v", err)
|
|
|
|
|
|
if !strings.Contains(plus, "cause: boom") || !strings.Contains(plus, "stack:") {
|
|
|
|
|
|
t.Fatalf("%%+v 应包含 cause 与 stack,实际:%s", plus)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestFromRegistry(t *testing.T) {
|
|
|
|
|
|
langx.AppendCode(map[string]int{"test_registry_key": 777})
|
|
|
|
|
|
langx.AppendTrans("zh", map[string]string{"test_registry_key": "注册表错误: #msg#"})
|
|
|
|
|
|
|
|
|
|
|
|
e := errorx.NewErrorWith(context.Background(), "test_registry_key", map[string]string{"msg": "磁盘满了"})
|
|
|
|
|
|
if got, want := e.GetCode(), 777; got != want {
|
|
|
|
|
|
t.Fatalf("code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.Error(), "注册表错误: 磁盘满了"; got != want {
|
|
|
|
|
|
t.Fatalf("msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 构造时带上 ctx 语言
|
|
|
|
|
|
enCtx := langx.SetCtxLang(context.Background(), "en")
|
|
|
|
|
|
langx.AppendTrans("en", map[string]string{"test_registry_key": "registry error: #msg#"})
|
|
|
|
|
|
if got, want := errorx.NewErrorWith(enCtx, "test_registry_key", map[string]string{"msg": "full"}).Error(), "registry error: full"; got != want {
|
|
|
|
|
|
t.Fatalf("en msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestResolveBranches(t *testing.T) {
|
|
|
|
|
|
// 资源里只有文案、没有 code:保留文案,code 用 CodeUnknown 兜底
|
|
|
|
|
|
langx.AppendTrans("zh", map[string]string{"test_msg_only": "只有文案"})
|
|
|
|
|
|
e := errorx.NewError(context.Background(), "test_msg_only")
|
|
|
|
|
|
if got, want := e.GetCode(), errorx.CodeUnknown; got != want {
|
|
|
|
|
|
t.Fatalf("只有文案时 code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.Error(), "只有文案"; got != want {
|
|
|
|
|
|
t.Fatalf("只有文案时 msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 资源里只有 code、没有文案:code 生效,文案退化为 key
|
|
|
|
|
|
langx.AppendCode(map[string]int{"test_code_only": 1234})
|
|
|
|
|
|
e2 := errorx.NewError(context.Background(), "test_code_only")
|
|
|
|
|
|
if got, want := e2.GetCode(), 1234; got != want {
|
|
|
|
|
|
t.Fatalf("只有 code 时 code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e2.Error(), "test_code_only"; got != want {
|
|
|
|
|
|
t.Fatalf("只有 code 时 msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestZeroValueAndEmptyKey(t *testing.T) {
|
|
|
|
|
|
var l errorx.ErrorLanguage
|
|
|
|
|
|
if l.GetCode() != 0 || l.GetKey() != "" {
|
|
|
|
|
|
t.Fatalf("零值访问器异常:code=%d key=%q", l.GetCode(), l.GetKey())
|
|
|
|
|
|
}
|
|
|
|
|
|
var err error = l
|
|
|
|
|
|
if err.Error() == "" {
|
|
|
|
|
|
t.Fatal("零值错误也应该能安全渲染")
|
|
|
|
|
|
}
|
|
|
|
|
|
if l.Err().Error() == "" {
|
|
|
|
|
|
t.Fatal("零值错误常量也应该能安全渲染")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestNewPanicsOnEmptyKey(t *testing.T) {
|
|
|
|
|
|
defer func() {
|
|
|
|
|
|
if recover() == nil {
|
|
|
|
|
|
t.Fatal("空 key 应该 panic,避免注册出无法定位的错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
_ = errorx.New("", 1, "x")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestNewErrorWith(t *testing.T) {
|
|
|
|
|
|
langx.AppendCode(map[string]int{"test_dynamic": 901})
|
|
|
|
|
|
langx.AppendTrans("zh", map[string]string{"test_dynamic": "动态错误: #msg#"})
|
|
|
|
|
|
|
|
|
|
|
|
e := errorx.NewErrorWith(context.Background(), "test_dynamic", map[string]string{"msg": "磁盘满了"})
|
|
|
|
|
|
if got, want := e.GetCode(), 901; got != want {
|
|
|
|
|
|
t.Fatalf("code = %d, want %d", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got, want := e.Error(), "动态错误: 磁盘满了"; got != want {
|
|
|
|
|
|
t.Fatalf("msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 带 ctx 语言
|
|
|
|
|
|
enCtx := langx.SetCtxLang(context.Background(), "en")
|
|
|
|
|
|
langx.AppendTrans("en", map[string]string{"test_dynamic": "dynamic error: #msg#"})
|
|
|
|
|
|
if got, want := errorx.NewErrorWith(enCtx, "test_dynamic", map[string]string{"msg": "full"}).Error(), "dynamic error: full"; got != want {
|
|
|
|
|
|
t.Fatalf("en msg = %q, want %q", got, want)
|
|
|
|
|
|
}
|
2025-10-26 20:32:02 +08:00
|
|
|
|
}
|