1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-12-23 22:11:10 +02:00
Files
imgproxy/errwrap/err_wrap_test.go
2025-08-03 03:31:23 +02:00

253 lines
7.8 KiB
Go

package errwrap
import (
"context"
"errors"
"net/http"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type (
fooError struct{ error }
barError struct{ error }
)
// notModifiedError is a custom error type that contains HTTP headers
type notModifiedError struct {
headers http.Header
}
func (e notModifiedError) Error() string {
return "not modified"
}
func (e notModifiedError) Headers() http.Header {
return e.headers
}
// Constructor for notModifiedError
func newNotModifiedError(headers http.Header) notModifiedError {
return notModifiedError{headers: headers}
}
// Is performs comparison of two notModifiedError instances.
// Any error should be Comparable, http.Header is not comparable,
// hence, we need to compare headers manually.
func (nm notModifiedError) Is(target error) bool {
m, ok := target.(notModifiedError)
return ok && reflect.DeepEqual(nm.headers, m.headers)
}
func TestInnerErrorWrapperIs(t *testing.T) {
fooInnerErr := &fooError{errors.New("inner error")}
barInnerErr := &barError{errors.New("inner error")}
assert.Equal(t, "inner error", fooInnerErr.Error())
require.NotErrorIs(t, fooInnerErr, errors.New("inner error"))
require.NotErrorIs(t, fooInnerErr, barInnerErr)
}
func TestInnerErrorWrapperAs(t *testing.T) {
fooInnerErr := fooError{errors.New("foo error")}
barInnerErr := barError{errors.New("foo error")}
var ie fooError
require.ErrorAs(t, fooInnerErr, &ie)
require.NotErrorAs(t, barInnerErr, &ie)
assert.Equal(t, "foo error", ie.Error())
}
func TestNew(t *testing.T) {
err := Errorf(0, "test error %d", 123)
assert.Equal(t, "test error 123", err.Error())
assert.Equal(t, http.StatusInternalServerError, err.StatusCode())
assert.Equal(t, "Internal error", err.PublicMessage())
assert.True(t, err.ShouldReport())
require.Error(t, err.Unwrap())
assert.Empty(t, err.messages) // No additional messages
}
func TestWrap(t *testing.T) {
originalErr := errors.New("original error")
wrappedErr := Wrap(originalErr)
assert.Equal(t, "original error", wrappedErr.Error())
assert.Equal(t, originalErr, wrappedErr.Unwrap())
assert.Equal(t, http.StatusInternalServerError, wrappedErr.StatusCode())
assert.Equal(t, "Internal error", wrappedErr.PublicMessage())
assert.True(t, wrappedErr.ShouldReport())
}
func TestWrapNil(t *testing.T) {
wrappedErr := Wrap(nil)
assert.Nil(t, wrappedErr)
wrappedErr = Wrapf(nil, "some message")
assert.Nil(t, wrappedErr)
}
func TestWrapAlreadyWrapped(t *testing.T) {
originalErr := New("original error", 0)
wrappedErr := Wrap(originalErr)
assert.NotSame(t, originalErr, wrappedErr)
}
func TestWrapf(t *testing.T) {
originalErr := errors.New("database error")
wrappedErr := Wrapf(originalErr, "failed to save user %d", 123)
assert.Equal(t, "database error: failed to save user 123", wrappedErr.Error())
assert.Equal(t, originalErr, wrappedErr.Unwrap())
assert.Len(t, wrappedErr.messages, 1)
}
func TestWrapfExistingErrWrap(t *testing.T) {
originalErr := New("database error", 0)
// First wrap
firstWrap := Wrapf(originalErr, "failed to query")
// Second wrap - should create new instance, not modify original
secondWrap := Wrapf(firstWrap, "failed to get user")
// Verify that Clone() was called
assert.NotSame(t, firstWrap, secondWrap)
assert.NotSame(t, originalErr, firstWrap)
assert.NotSame(t, originalErr, secondWrap)
assert.Equal(t, "database error", originalErr.Error())
assert.Empty(t, originalErr.messages)
// Check first wrap
assert.Equal(t, "database error: failed to query", firstWrap.Error())
assert.Len(t, firstWrap.messages, 1)
assert.Equal(t, "failed to query", firstWrap.messages[0])
// Check second wrap
assert.Equal(t, "database error: failed to query: failed to get user", secondWrap.Error())
assert.Len(t, secondWrap.messages, 2)
assert.Equal(t, "failed to query", secondWrap.messages[0])
assert.Equal(t, "failed to get user", secondWrap.messages[1])
}
func TestWithStatusCode(t *testing.T) {
originalErr := New("test error", 0)
modifiedErr := originalErr.WithStatusCode(http.StatusNotFound)
assert.Equal(t, http.StatusInternalServerError, originalErr.StatusCode())
assert.Equal(t, http.StatusNotFound, modifiedErr.StatusCode())
}
func TestWithPublicMessage(t *testing.T) {
originalErr := New("internal database error", 0)
modifiedErr := originalErr.WithPublicMessage("Service temporarily unavailable")
assert.Equal(t, "Internal error", originalErr.PublicMessage())
assert.Equal(t, "Service temporarily unavailable", modifiedErr.PublicMessage())
}
func TestWithShouldReport(t *testing.T) {
originalErr := New("test error", 0)
modifiedErr := originalErr.WithShouldReport(false)
assert.True(t, originalErr.ShouldReport())
assert.False(t, modifiedErr.ShouldReport())
assert.NotSame(t, originalErr, modifiedErr)
}
func TestChaining(t *testing.T) {
baseErr := errors.New("database connection failed")
finalErr := Wrapf(baseErr, "failed to save user %d", 123).
WithStatusCode(http.StatusInternalServerError).
WithPublicMessage("Unable to save changes").
WithShouldReport(true)
assert.Equal(t, "database connection failed: failed to save user 123", finalErr.Error())
assert.Equal(t, http.StatusInternalServerError, finalErr.StatusCode())
assert.Equal(t, "Unable to save changes", finalErr.PublicMessage())
assert.True(t, finalErr.ShouldReport())
assert.Equal(t, baseErr, finalErr.Unwrap())
}
func TestErrorsIs(t *testing.T) {
baseErr := errors.New("base error")
otherErr := errors.New("other error")
wrappedErr := Wrapf(baseErr, "wrapped error")
require.ErrorIs(t, wrappedErr, baseErr)
assert.NotErrorIs(t, wrappedErr, otherErr)
}
func TestErrorsAs(t *testing.T) {
baseErr := New("base error", 0).WithStatusCode(http.StatusAccepted)
wrappedErr := Wrapf(baseErr, "wrapped error")
var extractedErr *ErrWrap
require.ErrorAs(t, wrappedErr, &extractedErr)
assert.NotNil(t, extractedErr)
assert.Equal(t, baseErr.StatusCode(), extractedErr.StatusCode())
assert.Equal(t, baseErr.PublicMessage(), extractedErr.PublicMessage())
assert.Equal(t, baseErr.ShouldReport(), extractedErr.ShouldReport())
}
func TestStackTracePreservation(t *testing.T) {
originalErr := New("original", 0)
wrappedErr := Wrapf(originalErr, "wrapped")
assert.NotNil(t, originalErr.stack)
assert.NotNil(t, wrappedErr.stack)
// When wrapping an existing ErrWrap, it should preserve the original stack
assert.Equal(t, originalErr.stack, wrappedErr.stack)
// When wrapping a regular error, it should capture new stack
regularErr := errors.New("regular")
wrappedRegular := Wrapf(regularErr, "wrapped regular")
assert.NotNil(t, wrappedRegular.stack)
}
func TestNotModifiedError(t *testing.T) {
headers := make(http.Header)
headers.Set("Cache-Control", "no-cache")
headers.Set("ETag", `"abc123"`)
headers.Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
originalErr := newNotModifiedError(headers)
wrappedErr := Wrapf(originalErr, "cache validation failed for resource %s", "/api/users/123")
assert.Equal(t, "not modified: cache validation failed for resource /api/users/123", wrappedErr.Error())
assert.Equal(t, originalErr, wrappedErr.Unwrap())
require.ErrorIs(t, wrappedErr, originalErr)
differentHeaders := make(http.Header)
differentHeaders.Set("Cache-Control", "public")
differentErr := newNotModifiedError(differentHeaders)
require.NotErrorIs(t, wrappedErr, differentErr)
var extractedNotModified notModifiedError
require.ErrorAs(t, wrappedErr, &extractedNotModified)
extractedHeaders := extractedNotModified.Headers()
assert.Equal(t, "no-cache", extractedHeaders.Get("Cache-Control"))
assert.Equal(t, `"abc123"`, extractedHeaders.Get("ETag"))
assert.Equal(t, "Wed, 21 Oct 2015 07:28:00 GMT", extractedHeaders.Get("Last-Modified"))
}
func TestWrapStdErr(t *testing.T) {
err := Wrap(context.DeadlineExceeded)
assert.ErrorIs(t, err, context.DeadlineExceeded)
}