Files
Cyrene_v2/backend/internal/errors/errors_test.go
T
Aska 7943e290ec feat(internal): add foundational packages — config, logger, errors
- config: env-based loading with startup validation, 13 tests
- logger: structured slog wrapper with redaction, 10 tests
- errors: unified AppError model with HTTP mapping, 18 tests
- 41 tests total, build + vet clean
2026-08-09 20:35:42 +08:00

240 lines
5.9 KiB
Go

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)
}
}