478 lines
15 KiB
Go
478 lines
15 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"veza-backend-api/internal/common"
|
|
"veza-backend-api/internal/models"
|
|
"veza-backend-api/internal/services"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
// MockImageService implements ImageService interface for testing
|
|
type MockImageService struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockImageService) ProcessAvatar(fileHeader *multipart.FileHeader) ([]byte, error) {
|
|
args := m.Called(fileHeader)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).([]byte), args.Error(1)
|
|
}
|
|
|
|
func (m *MockImageService) GenerateS3Key(userID uuid.UUID) string {
|
|
args := m.Called(userID)
|
|
return args.String(0)
|
|
}
|
|
|
|
func (m *MockImageService) UploadToS3(data []byte, key string) (string, error) {
|
|
args := m.Called(data, key)
|
|
return args.String(0), args.Error(1)
|
|
}
|
|
|
|
func (m *MockImageService) DeleteFromS3(avatarURL string) error {
|
|
args := m.Called(avatarURL)
|
|
return args.Error(0)
|
|
}
|
|
|
|
// MockUserService implements UserService interface for testing
|
|
type MockUserServiceForAvatar struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockUserServiceForAvatar) GetByID(userID uuid.UUID) (*models.User, error) {
|
|
args := m.Called(userID)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.User), args.Error(1)
|
|
}
|
|
|
|
func (m *MockUserServiceForAvatar) UpdateAvatarURL(userID uuid.UUID, avatarURL string) error {
|
|
args := m.Called(userID, avatarURL)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func setupTestAvatarRouter(mockImageService *MockImageService, mockUserService *MockUserServiceForAvatar) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
|
|
handler := NewAvatarHandlerWithInterface(mockImageService, mockUserService)
|
|
|
|
api := router.Group("/api/v1")
|
|
api.Use(func(c *gin.Context) {
|
|
// Mock auth middleware - set user_id from header if present
|
|
userIDStr := c.GetHeader("X-User-ID")
|
|
if userIDStr != "" {
|
|
uid, err := uuid.Parse(userIDStr)
|
|
if err == nil {
|
|
common.SetUserIDInContext(c, uid)
|
|
}
|
|
}
|
|
c.Next()
|
|
})
|
|
{
|
|
users := api.Group("/users")
|
|
{
|
|
users.POST("/:userId/avatar", handler.UploadAvatar)
|
|
users.DELETE("/:userId/avatar", handler.DeleteAvatar)
|
|
}
|
|
}
|
|
|
|
return router
|
|
}
|
|
|
|
func createMultipartFormData(t *testing.T, fieldName, filename string, content []byte) (*bytes.Buffer, string) {
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
part, err := writer.CreateFormFile(fieldName, filename)
|
|
assert.NoError(t, err)
|
|
|
|
_, err = part.Write(content)
|
|
assert.NoError(t, err)
|
|
|
|
err = writer.Close()
|
|
assert.NoError(t, err)
|
|
|
|
return body, writer.FormDataContentType()
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_Success(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
s3Key := "avatars/" + userID.String() + ".jpg"
|
|
avatarURL := "https://s3.example.com/" + s3Key
|
|
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Mock expectations
|
|
mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(imageData, nil)
|
|
mockImageService.On("GenerateS3Key", userID).Return(s3Key)
|
|
mockImageService.On("UploadToS3", imageData, s3Key).Return(avatarURL, nil)
|
|
mockUserService.On("UpdateAvatarURL", userID, avatarURL).Return(nil)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockImageService.AssertExpectations(t)
|
|
mockUserService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_Unauthorized(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
|
|
mockImageService.AssertNotCalled(t, "ProcessAvatar")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_InvalidUserID(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/invalid/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
mockImageService.AssertNotCalled(t, "ProcessAvatar")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_Forbidden(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID1 := uuid.New()
|
|
userID2 := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Execute - userID1 trying to upload avatar for userID2
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID2.String()+"/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-User-ID", userID1.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
mockImageService.AssertNotCalled(t, "ProcessAvatar")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_NoFile(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
|
|
// Execute - no file in request
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", bytes.NewBuffer([]byte{}))
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
mockImageService.AssertNotCalled(t, "ProcessAvatar")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_ProcessAvatarError(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Mock expectations - ProcessAvatar returns error
|
|
mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(nil, assert.AnError)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
mockImageService.AssertExpectations(t)
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_UploadAvatar_UploadToS3Error(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
imageData := []byte("fake image data")
|
|
s3Key := "avatars/" + userID.String() + ".jpg"
|
|
body, contentType := createMultipartFormData(t, "avatar", "avatar.jpg", imageData)
|
|
|
|
// Mock expectations - UploadToS3 returns error
|
|
mockImageService.On("ProcessAvatar", mock.AnythingOfType("*multipart.FileHeader")).Return(imageData, nil)
|
|
mockImageService.On("GenerateS3Key", userID).Return(s3Key)
|
|
mockImageService.On("UploadToS3", imageData, s3Key).Return("", assert.AnError)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/avatar", body)
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
mockImageService.AssertExpectations(t)
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_Success(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
avatarURL := "https://s3.example.com/avatars/" + userID.String() + ".jpg"
|
|
|
|
user := &models.User{
|
|
ID: userID,
|
|
Avatar: avatarURL,
|
|
}
|
|
|
|
// Mock expectations
|
|
mockUserService.On("GetByID", userID).Return(user, nil)
|
|
mockImageService.On("DeleteFromS3", avatarURL).Return(nil)
|
|
mockUserService.On("UpdateAvatarURL", userID, "").Return(nil)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockImageService.AssertExpectations(t)
|
|
mockUserService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_Unauthorized(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/avatar", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
|
|
mockImageService.AssertNotCalled(t, "DeleteFromS3")
|
|
mockUserService.AssertNotCalled(t, "GetByID")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_InvalidUserID(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/invalid/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
mockImageService.AssertNotCalled(t, "DeleteFromS3")
|
|
mockUserService.AssertNotCalled(t, "GetByID")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_Forbidden(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID1 := uuid.New()
|
|
userID2 := uuid.New()
|
|
|
|
// Execute - userID1 trying to delete avatar for userID2
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID2.String()+"/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID1.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
mockImageService.AssertNotCalled(t, "DeleteFromS3")
|
|
mockUserService.AssertNotCalled(t, "GetByID")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_UserNotFound(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
|
|
// Mock expectations - GetByID returns error
|
|
mockUserService.On("GetByID", userID).Return(nil, assert.AnError)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
mockUserService.AssertExpectations(t)
|
|
mockImageService.AssertNotCalled(t, "DeleteFromS3")
|
|
mockUserService.AssertNotCalled(t, "UpdateAvatarURL")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_NoAvatar(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
|
|
user := &models.User{
|
|
ID: userID,
|
|
Avatar: "", // No avatar
|
|
}
|
|
|
|
// Mock expectations
|
|
mockUserService.On("GetByID", userID).Return(user, nil)
|
|
mockUserService.On("UpdateAvatarURL", userID, "").Return(nil)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockUserService.AssertExpectations(t)
|
|
// DeleteFromS3 should not be called if avatar is empty
|
|
mockImageService.AssertNotCalled(t, "DeleteFromS3")
|
|
}
|
|
|
|
func TestAvatarHandler_DeleteAvatar_UpdateAvatarURLError(t *testing.T) {
|
|
// Setup
|
|
mockImageService := new(MockImageService)
|
|
mockUserService := new(MockUserServiceForAvatar)
|
|
router := setupTestAvatarRouter(mockImageService, mockUserService)
|
|
|
|
userID := uuid.New()
|
|
avatarURL := "https://s3.example.com/avatars/" + userID.String() + ".jpg"
|
|
|
|
user := &models.User{
|
|
ID: userID,
|
|
Avatar: avatarURL,
|
|
}
|
|
|
|
// Mock expectations - UpdateAvatarURL returns error
|
|
mockUserService.On("GetByID", userID).Return(user, nil)
|
|
mockImageService.On("DeleteFromS3", avatarURL).Return(nil)
|
|
mockUserService.On("UpdateAvatarURL", userID, "").Return(assert.AnError)
|
|
|
|
// Execute
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/avatar", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Assert
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
mockImageService.AssertExpectations(t)
|
|
mockUserService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestNewAvatarHandler(t *testing.T) {
|
|
// Setup
|
|
mockImageService := &services.ImageService{}
|
|
mockUserService := &services.UserService{}
|
|
|
|
// Execute
|
|
handler := NewAvatarHandler(mockImageService, mockUserService)
|
|
|
|
// Assert
|
|
assert.NotNil(t, handler)
|
|
assert.NotNil(t, handler.imageService)
|
|
assert.NotNil(t, handler.userService)
|
|
}
|