package usermanagement import ( "errors" "net/http" "testing" "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types" "github.com/aws/smithy-go" "github.com/stretchr/testify/assert" ) // mockAPIError implements smithy.APIError for testing. type mockAPIError struct { code string message string } func (e *mockAPIError) Error() string { return e.message } func (e *mockAPIError) ErrorCode() string { return e.code } func (e *mockAPIError) ErrorMessage() string { return e.message } func (e *mockAPIError) ErrorFault() smithy.ErrorFault { return smithy.FaultServer } func TestClassifyCognitoError_NilError(t *testing.T) { result := ClassifyCognitoError(nil, "test@example.com") assert.Equal(t, http.StatusOK, result.HTTPStatus) assert.Equal(t, CognitoErrorType(""), result.ErrorType) assert.Empty(t, result.Message) } func TestClassifyCognitoError_UserNotFoundException(t *testing.T) { email := "test@example.com" err := &types.UserNotFoundException{ Message: stringPtr("User does not exist"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusNotFound, result.HTTPStatus) assert.Equal(t, ErrTypeUserNotFound, result.ErrorType) assert.Contains(t, result.Message, email) assert.NotEmpty(t, result.Details) } func TestClassifyCognitoError_NotAuthorizedException(t *testing.T) { email := "test@example.com" err := &types.NotAuthorizedException{ Message: stringPtr("User is not authorized"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusUnauthorized, result.HTTPStatus) assert.Equal(t, ErrTypeNotAuthorized, result.ErrorType) assert.NotEmpty(t, result.Message) } func TestClassifyCognitoError_InvalidParameterException(t *testing.T) { email := "test@example.com" err := &types.InvalidParameterException{ Message: stringPtr("Invalid email format"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusBadRequest, result.HTTPStatus) assert.Equal(t, ErrTypeInvalidParameter, result.ErrorType) assert.NotEmpty(t, result.Message) } func TestClassifyCognitoError_TooManyRequestsException(t *testing.T) { email := "test@example.com" err := &types.TooManyRequestsException{ Message: stringPtr("Rate exceeded"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusTooManyRequests, result.HTTPStatus) assert.Equal(t, ErrTypeRateLimited, result.ErrorType) assert.NotEmpty(t, result.Message) } func TestClassifyCognitoError_InternalErrorException(t *testing.T) { email := "test@example.com" err := &types.InternalErrorException{ Message: stringPtr("Internal server error"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusServiceUnavailable, result.HTTPStatus) assert.Equal(t, ErrTypeServiceUnavailable, result.ErrorType) assert.NotEmpty(t, result.Message) } func TestClassifyCognitoError_UsernameExistsException(t *testing.T) { email := "test@example.com" err := &types.UsernameExistsException{ Message: stringPtr("User already exists"), } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusConflict, result.HTTPStatus) assert.Equal(t, ErrTypeUserExists, result.ErrorType) assert.Contains(t, result.Message, email) } func TestClassifyCognitoError_AccessDeniedException_ViaAPIError(t *testing.T) { email := "test@example.com" err := &mockAPIError{ code: "AccessDeniedException", message: "User: arn:aws:sts::123456789:assumed-role/role is not authorized", } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusForbidden, result.HTTPStatus) assert.Equal(t, ErrTypeAccessDenied, result.ErrorType) assert.Contains(t, result.Message, "Access denied") } func TestClassifyCognitoError_GenericError(t *testing.T) { email := "test@example.com" err := errors.New("some unknown error") result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusBadGateway, result.HTTPStatus) assert.Equal(t, ErrTypeServiceError, result.ErrorType) assert.Contains(t, result.Message, "Failed to communicate with Cognito") } func TestClassifyCognitoError_UnknownAPIError(t *testing.T) { email := "test@example.com" err := &mockAPIError{ code: "SomeUnknownException", message: "Unknown error occurred", } result := ClassifyCognitoError(err, email) assert.Equal(t, http.StatusBadGateway, result.HTTPStatus) assert.Equal(t, ErrTypeServiceError, result.ErrorType) } func TestSanitizeErrorMessage_RemovesARNs(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "removes single ARN", input: "User: arn:aws:sts::123456789012:assumed-role/TestRole/session is not authorized", expected: "User: [REDACTED] is not authorized", }, { name: "removes multiple ARNs", input: "arn:aws:iam::123:user/test and arn:aws:s3:::bucket", expected: "[REDACTED] and [REDACTED]", }, { name: "handles no ARN", input: "Simple error message", expected: "Simple error message", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := sanitizeErrorMessage(tt.input) assert.Equal(t, tt.expected, result) }) } } func TestSanitizeErrorMessage_RemovesRequestIDs(t *testing.T) { tests := []struct { name string input string expected string }{ { name: "removes RequestID", input: "Error occurred, RequestID: abc-123-def", expected: "Error occurred,", }, { name: "removes Request ID with space", input: "Error occurred, Request ID: abc-123-def something", expected: "Error occurred, something", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := sanitizeErrorMessage(tt.input) assert.NotContains(t, result, "abc-123-def") }) } } func TestClassifyAPIError_AllCases(t *testing.T) { email := "user@test.com" tests := []struct { name string code string expectedStatus int expectedType CognitoErrorType }{ {"AccessDenied", "AccessDeniedException", http.StatusForbidden, ErrTypeAccessDenied}, {"UserNotFound", "UserNotFoundException", http.StatusNotFound, ErrTypeUserNotFound}, {"NotAuthorized", "NotAuthorizedException", http.StatusUnauthorized, ErrTypeNotAuthorized}, {"InvalidParameter", "InvalidParameterException", http.StatusBadRequest, ErrTypeInvalidParameter}, {"TooManyRequests", "TooManyRequestsException", http.StatusTooManyRequests, ErrTypeRateLimited}, {"ServiceUnavailable", "ServiceUnavailable", http.StatusServiceUnavailable, ErrTypeServiceUnavailable}, {"InternalError", "InternalError", http.StatusServiceUnavailable, ErrTypeServiceUnavailable}, {"UsernameExists", "UsernameExistsException", http.StatusConflict, ErrTypeUserExists}, {"Unknown", "SomeRandomException", http.StatusBadGateway, ErrTypeServiceError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { apiErr := &mockAPIError{code: tt.code, message: "test message"} result := classifyAPIError(apiErr, email) assert.Equal(t, tt.expectedStatus, result.HTTPStatus) assert.Equal(t, tt.expectedType, result.ErrorType) }) } } // stringPtr is a helper to create a pointer to a string. func stringPtr(s string) *string { return &s }