Extract logging to pkg/log for use outside CLI: - Logger with Debug/Info/Warn/Error levels - Key-value pairs for structured logging - Customisable styling and output - Optional Core framework integration via Service Enhance pkg/errors with: - Wrap() and WrapCode() helpers - Code() for error codes - Op(), ErrCode(), Message(), Root() extractors - Standard library wrappers (Is, As, New, Join) Update pkg/cli/log.go to use pkg/log with CLI styling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
182 lines
3.8 KiB
Go
182 lines
3.8 KiB
Go
package errors
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
)
|
|
|
|
func TestE(t *testing.T) {
|
|
err := E("user.Create", "validation failed", nil)
|
|
|
|
if err.Error() != "user.Create: validation failed" {
|
|
t.Errorf("unexpected error message: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestE_WithUnderlying(t *testing.T) {
|
|
underlying := New("database connection failed")
|
|
err := E("user.Create", "failed to save", underlying)
|
|
|
|
if err.Error() != "user.Create: failed to save: database connection failed" {
|
|
t.Errorf("unexpected error message: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestWrap(t *testing.T) {
|
|
// Wrap nil returns nil
|
|
if Wrap(nil, "op", "msg") != nil {
|
|
t.Error("expected Wrap(nil) to return nil")
|
|
}
|
|
|
|
// Wrap error
|
|
underlying := New("original")
|
|
err := Wrap(underlying, "user.Get", "failed")
|
|
|
|
if !Is(err, underlying) {
|
|
t.Error("expected wrapped error to match underlying")
|
|
}
|
|
}
|
|
|
|
func TestWrapCode(t *testing.T) {
|
|
underlying := New("not found")
|
|
err := WrapCode(underlying, "ERR_NOT_FOUND", "user.Get", "user not found")
|
|
|
|
var e *Error
|
|
if !As(err, &e) {
|
|
t.Fatal("expected error to be *Error")
|
|
}
|
|
|
|
if e.Code != "ERR_NOT_FOUND" {
|
|
t.Errorf("expected code ERR_NOT_FOUND, got %s", e.Code)
|
|
}
|
|
}
|
|
|
|
func TestCode(t *testing.T) {
|
|
err := Code("ERR_VALIDATION", "invalid email")
|
|
|
|
var e *Error
|
|
if !As(err, &e) {
|
|
t.Fatal("expected error to be *Error")
|
|
}
|
|
|
|
if e.Code != "ERR_VALIDATION" {
|
|
t.Errorf("expected code ERR_VALIDATION, got %s", e.Code)
|
|
}
|
|
if e.Msg != "invalid email" {
|
|
t.Errorf("expected msg 'invalid email', got %s", e.Msg)
|
|
}
|
|
}
|
|
|
|
func TestIs(t *testing.T) {
|
|
err := Wrap(io.EOF, "read", "failed")
|
|
|
|
if !Is(err, io.EOF) {
|
|
t.Error("expected Is to find io.EOF in chain")
|
|
}
|
|
|
|
if Is(err, io.ErrClosedPipe) {
|
|
t.Error("expected Is to not find io.ErrClosedPipe")
|
|
}
|
|
}
|
|
|
|
func TestAs(t *testing.T) {
|
|
err := E("test.Op", "test message", nil)
|
|
|
|
var e *Error
|
|
if !As(err, &e) {
|
|
t.Fatal("expected As to find *Error")
|
|
}
|
|
|
|
if e.Op != "test.Op" {
|
|
t.Errorf("expected Op 'test.Op', got %s", e.Op)
|
|
}
|
|
}
|
|
|
|
func TestOp(t *testing.T) {
|
|
err := E("user.Create", "failed", nil)
|
|
|
|
if Op(err) != "user.Create" {
|
|
t.Errorf("expected Op 'user.Create', got %s", Op(err))
|
|
}
|
|
|
|
// Non-Error returns empty string
|
|
if Op(New("plain error")) != "" {
|
|
t.Error("expected empty Op for non-Error")
|
|
}
|
|
}
|
|
|
|
func TestErrCode(t *testing.T) {
|
|
err := Code("ERR_TEST", "test")
|
|
|
|
if ErrCode(err) != "ERR_TEST" {
|
|
t.Errorf("expected code ERR_TEST, got %s", ErrCode(err))
|
|
}
|
|
|
|
// Non-Error returns empty string
|
|
if ErrCode(New("plain error")) != "" {
|
|
t.Error("expected empty code for non-Error")
|
|
}
|
|
}
|
|
|
|
func TestMessage(t *testing.T) {
|
|
err := E("op", "the message", nil)
|
|
|
|
if Message(err) != "the message" {
|
|
t.Errorf("expected 'the message', got %s", Message(err))
|
|
}
|
|
|
|
// Plain error returns full error string
|
|
plain := New("plain error")
|
|
if Message(plain) != "plain error" {
|
|
t.Errorf("expected 'plain error', got %s", Message(plain))
|
|
}
|
|
|
|
// Nil returns empty string
|
|
if Message(nil) != "" {
|
|
t.Error("expected empty string for nil")
|
|
}
|
|
}
|
|
|
|
func TestRoot(t *testing.T) {
|
|
root := New("root cause")
|
|
mid := Wrap(root, "mid", "middle")
|
|
top := Wrap(mid, "top", "top level")
|
|
|
|
if Root(top) != root {
|
|
t.Error("expected Root to return deepest error")
|
|
}
|
|
|
|
// Single error returns itself
|
|
single := New("single")
|
|
if Root(single) != single {
|
|
t.Error("expected Root of single error to return itself")
|
|
}
|
|
}
|
|
|
|
func TestError_Unwrap(t *testing.T) {
|
|
underlying := New("underlying")
|
|
err := E("op", "msg", underlying)
|
|
|
|
var e *Error
|
|
if !As(err, &e) {
|
|
t.Fatal("expected *Error")
|
|
}
|
|
|
|
if e.Unwrap() != underlying {
|
|
t.Error("expected Unwrap to return underlying error")
|
|
}
|
|
}
|
|
|
|
func TestJoin(t *testing.T) {
|
|
err1 := New("error 1")
|
|
err2 := New("error 2")
|
|
|
|
joined := Join(err1, err2)
|
|
|
|
if !Is(joined, err1) {
|
|
t.Error("expected joined error to contain err1")
|
|
}
|
|
if !Is(joined, err2) {
|
|
t.Error("expected joined error to contain err2")
|
|
}
|
|
}
|