package apierror import ( "errors" "fmt" "testing" ) func TestError_ErrorString_NoCause(t *testing.T) { e := &Error{Message: "x"} if got := e.Error(); got != "x" { t.Fatalf("Error() = %q, want %q", got, "x") } } func TestError_ErrorString_WithCause(t *testing.T) { e := &Error{Message: "x", Cause: errors.New("y")} if got := e.Error(); got != "x: y" { t.Fatalf("Error() = %q, want %q", got, "x: y") } } func TestError_Unwrap(t *testing.T) { cause := errors.New("boom") e := &Error{Message: "x", Cause: cause} if got := e.Unwrap(); got != cause { t.Fatalf("Unwrap() = %v, want %v", got, cause) } e2 := &Error{Message: "x"} if got := e2.Unwrap(); got != nil { t.Fatalf("Unwrap() with nil Cause = %v, want nil", got) } } func TestFrom_NilReturnsNil(t *testing.T) { if got := From(nil); got != nil { t.Fatalf("From(nil) = %v, want nil", got) } } func TestFrom_PreservesAPIError(t *testing.T) { orig := NotFound("track") got := From(orig) if got != orig { t.Fatalf("From(orig) returned different pointer: got %p, want %p", got, orig) } } func TestFrom_PreservesWrappedAPIError(t *testing.T) { inner := NotFound("track") wrapped := fmt.Errorf("ctx: %w", inner) got := From(wrapped) if got == nil { t.Fatal("From(wrapped) = nil, want *Error") } if got.Code != "track_not_found" { t.Fatalf("got.Code = %q, want %q", got.Code, "track_not_found") } } func TestFrom_WrapsPlainError(t *testing.T) { plain := errors.New("boom") got := From(plain) if got == nil { t.Fatal("From(plain) = nil, want *Error") } if got.Status != 500 { t.Errorf("Status = %d, want 500", got.Status) } if got.Code != "server_error" { t.Errorf("Code = %q, want %q", got.Code, "server_error") } if got.Cause != plain { t.Errorf("Cause = %v, want %v", got.Cause, plain) } } func TestConstructors_FieldsCorrect(t *testing.T) { tests := []struct { name string err *Error wantStatus int wantCode string wantMessage string }{ { name: "NotFound", err: NotFound("track"), wantStatus: 404, wantCode: "track_not_found", wantMessage: "track not found", }, { name: "BadRequest", err: BadRequest("invalid_input", "invalid input"), wantStatus: 400, wantCode: "invalid_input", wantMessage: "invalid input", }, { name: "Conflict", err: Conflict("already_exists", "already exists"), wantStatus: 409, wantCode: "already_exists", wantMessage: "already exists", }, { name: "Forbidden", err: Forbidden("forbidden", "forbidden"), wantStatus: 403, wantCode: "forbidden", wantMessage: "forbidden", }, { name: "Unauthorized", err: Unauthorized("unauthorized", "authentication required"), wantStatus: 401, wantCode: "unauthorized", wantMessage: "authentication required", }, { name: "Internal", err: Internal(errors.New("boom")), wantStatus: 500, wantCode: "server_error", wantMessage: "internal server error", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.err.Status != tt.wantStatus { t.Errorf("Status = %d, want %d", tt.err.Status, tt.wantStatus) } if tt.err.Code != tt.wantCode { t.Errorf("Code = %q, want %q", tt.err.Code, tt.wantCode) } if tt.err.Message != tt.wantMessage { t.Errorf("Message = %q, want %q", tt.err.Message, tt.wantMessage) } }) } } func TestInternalMsg(t *testing.T) { cause := errors.New("db down") e := InternalMsg("lookup failed", cause) if e.Status != 500 { t.Errorf("Status = %d, want 500", e.Status) } if e.Code != "server_error" { t.Errorf("Code = %q, want server_error", e.Code) } if e.Message != "lookup failed" { t.Errorf("Message = %q, want lookup failed", e.Message) } if !errors.Is(e, cause) { t.Errorf("expected Is(cause) to be true") } } func TestSentinels_Identity(t *testing.T) { wrapped := fmt.Errorf("ctx: %w", ErrNotFound) var apiErr *Error if !errors.As(wrapped, &apiErr) { t.Fatal("errors.As(wrapped, &apiErr) = false, want true") } if apiErr != ErrNotFound { t.Fatalf("recovered error pointer = %p, want %p", apiErr, ErrNotFound) } }