mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-12-23 22:11:10 +02:00
253 lines
7.8 KiB
Go
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)
|
|
}
|