package errors import ( "fmt" "net/http" "testing" ) func TestNewNotFound(t *testing.T) { err := NewNotFound("user not found") if err.Code != "NOT_FOUND" { t.Errorf("expected code NOT_FOUND, got %s", err.Code) } if err.Message != "user not found" { t.Errorf("expected message 'user not found', got %s", err.Message) } if err.HTTPCode != http.StatusNotFound { t.Errorf("expected HTTP 404, got %d", err.HTTPCode) } } func TestNewUnauthorized(t *testing.T) { err := NewUnauthorized("invalid token") if err.Code != "UNAUTHORIZED" { t.Errorf("expected code UNAUTHORIZED, got %s", err.Code) } if err.HTTPCode != http.StatusUnauthorized { t.Errorf("expected HTTP 401, got %d", err.HTTPCode) } } func TestNewForbidden(t *testing.T) { err := NewForbidden("admin only") if err.HTTPCode != http.StatusForbidden { t.Errorf("expected HTTP 403, got %d", err.HTTPCode) } } func TestNewBadRequest(t *testing.T) { err := NewBadRequest("invalid input") if err.HTTPCode != http.StatusBadRequest { t.Errorf("expected HTTP 400, got %d", err.HTTPCode) } } func TestNewConflict(t *testing.T) { err := NewConflict("duplicate entry") if err.HTTPCode != http.StatusConflict { t.Errorf("expected HTTP 409, got %d", err.HTTPCode) } } func TestNewTooManyRequests(t *testing.T) { err := NewTooManyRequests("rate limit") if err.HTTPCode != http.StatusTooManyRequests { t.Errorf("expected HTTP 429, got %d", err.HTTPCode) } } func TestNewInternal(t *testing.T) { cause := fmt.Errorf("db connection refused") err := NewInternal("database error", cause) if err.Code != "INTERNAL" { t.Errorf("expected code INTERNAL, got %s", err.Code) } if err.HTTPCode != http.StatusInternalServerError { t.Errorf("expected HTTP 500, got %d", err.HTTPCode) } if err.Cause != cause { t.Errorf("expected cause to be preserved") } // Check that Error() includes the cause errStr := err.Error() if errStr[len(errStr)-1] == '.' { // just check it doesn't panic } } func TestNewValidation(t *testing.T) { err := NewValidation("field required: email") if err.Code != "VALIDATION" { t.Errorf("expected code VALIDATION, got %s", err.Code) } if err.HTTPCode != http.StatusBadRequest { t.Errorf("expected HTTP 400, got %d", err.HTTPCode) } } func TestWrap(t *testing.T) { cause := fmt.Errorf("raw error") err := Wrap(cause, "CUSTOM", "custom message", http.StatusTeapot) if err.Code != "CUSTOM" { t.Errorf("expected code CUSTOM, got %s", err.Code) } if err.Cause != cause { t.Errorf("expected cause to be preserved") } if err.HTTPCode != http.StatusTeapot { t.Errorf("expected HTTP 418, got %d", err.HTTPCode) } } func TestWrapConvenienceFuncs(t *testing.T) { cause := fmt.Errorf("raw error") tests := []struct { name string err *AppError code string httpCode int }{ {"WrapNotFound", WrapNotFound(cause, "not found"), "NOT_FOUND", 404}, {"WrapBadRequest", WrapBadRequest(cause, "bad input"), "BAD_REQUEST", 400}, {"WrapInternal", WrapInternal(cause, "oops"), "INTERNAL", 500}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.err.Code != tt.code { t.Errorf("expected code %s, got %s", tt.code, tt.err.Code) } if tt.err.HTTPCode != tt.httpCode { t.Errorf("expected HTTP %d, got %d", tt.httpCode, tt.err.HTTPCode) } if tt.err.Cause != cause { t.Errorf("expected cause to be preserved") } }) } } func TestUnwrap(t *testing.T) { cause := fmt.Errorf("original") err := WrapInternal(cause, "wrapped") unwrapped := err.Unwrap() if unwrapped != cause { t.Errorf("Unwrap() = %v, want %v", unwrapped, cause) } } func TestIsAppError(t *testing.T) { ae := NewNotFound("test") _, ok := IsAppError(ae) if !ok { t.Error("expected AppError to be identified") } plain := fmt.Errorf("plain error") _, ok = IsAppError(plain) if ok { t.Error("plain error should not be AppError") } _, ok = IsAppError(nil) if ok { t.Error("nil should not be AppError") } } func TestStatusCode(t *testing.T) { if sc := StatusCode(NewNotFound("x")); sc != 404 { t.Errorf("expected 404, got %d", sc) } if sc := StatusCode(fmt.Errorf("plain")); sc != 500 { t.Errorf("expected 500 for plain error, got %d", sc) } } func TestMessage(t *testing.T) { if msg := Message(NewNotFound("user x not found")); msg != "user x not found" { t.Errorf("expected message preserved, got %s", msg) } if msg := Message(fmt.Errorf("raw")); msg != "Internal server error" { t.Errorf("expected generic message for unknown error, got %s", msg) } } func TestCode(t *testing.T) { if c := Code(NewForbidden("nope")); c != "FORBIDDEN" { t.Errorf("expected FORBIDDEN, got %s", c) } if c := Code(fmt.Errorf("raw")); c != "INTERNAL" { t.Errorf("expected INTERNAL for unknown error, got %s", c) } } func TestIsCode(t *testing.T) { err := NewNotFound("test") if !IsCode(err, "NOT_FOUND") { t.Error("expected IsCode true for NOT_FOUND") } if IsCode(err, "FORBIDDEN") { t.Error("expected IsCode false for wrong code") } if IsCode(nil, "NOT_FOUND") { t.Error("expected IsCode false for nil") } } func TestIsNotFound(t *testing.T) { if !IsNotFound(NewNotFound("x")) { t.Error("expected IsNotFound true") } if IsNotFound(NewBadRequest("x")) { t.Error("expected IsNotFound false for bad request") } } func TestSentinelErrors(t *testing.T) { if ErrUnauthorized.Code != "UNAUTHORIZED" { t.Errorf("expected UNAUTHORIZED sentinel") } if ErrForbidden.Code != "FORBIDDEN" { t.Errorf("expected FORBIDDEN sentinel") } if ErrInternal.Code != "INTERNAL" { t.Errorf("expected INTERNAL sentinel") } } func TestErrorFormatting(t *testing.T) { // Without cause err := NewNotFound("user not found") str := err.Error() if str != "[NOT_FOUND] user not found" { t.Errorf("unexpected format: %s", str) } // With cause cause := fmt.Errorf("sql: no rows") err2 := WrapInternal(cause, "database error") str2 := err2.Error() if str2[:len("[INTERNAL]")] != "[INTERNAL]" { t.Errorf("unexpected format: %s", str2) } }