test: add AvatarHandler tests for upload validation
Add unit tests for avatar upload including: - Unauthorized access (no token) - Non-admin cannot update other user avatar - User not found or forbidden case
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
@@ -103,6 +104,7 @@ func setupHandlerTestServer(t *testing.T) (*httptest.Server, func()) {
|
||||
WithPasswordHistoryRepo(passwordHistoryRepo)
|
||||
themeRepo := repository.NewThemeConfigRepository(db)
|
||||
themeSvc := service.NewThemeService(themeRepo)
|
||||
avatarH := handler.NewAvatarHandler(userRepo)
|
||||
|
||||
rateLimitCfg := config.RateLimitConfig{}
|
||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware(rateLimitCfg)
|
||||
@@ -127,7 +129,7 @@ func setupHandlerTestServer(t *testing.T) (*httptest.Server, func()) {
|
||||
authHandler, userHandler, roleHandler, permHandler, deviceHandler,
|
||||
logHandler, authMiddleware, rateLimitMiddleware, opLogMiddleware,
|
||||
pwdResetHandler, captchaHandler, totpHandler, nil,
|
||||
nil, nil, nil, nil, nil, themeHandler, nil, nil, nil,
|
||||
nil, nil, nil, nil, nil, themeHandler, nil, nil, nil, avatarH,
|
||||
)
|
||||
engine := r.Setup()
|
||||
|
||||
@@ -1276,3 +1278,99 @@ func TestAuthHandler_RefreshToken_MissingToken(t *testing.T) {
|
||||
t.Errorf("expected status %d for missing refresh token, got %d", http.StatusBadRequest, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Avatar Handler Tests
|
||||
// =============================================================================
|
||||
|
||||
func doUploadFile(url, token string, fieldName string, fileName string, fileContent []byte) (*http.Response, error) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile(fieldName, fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := part.Write(fileContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
client := &http.Client{}
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func TestAvatarHandler_UploadAvatar_Unauthorized(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Create a fake PNG file
|
||||
fileContent := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
|
||||
|
||||
resp, err := doUploadFile(server.URL+"/api/v1/users/1/avatar", "", "avatar", "test.png", fileContent)
|
||||
if err != nil {
|
||||
t.Fatalf("upload request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized {
|
||||
t.Errorf("expected status %d for unauthorized request, got %d", http.StatusUnauthorized, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvatarHandler_UploadAvatar_NonAdminCannotUpdateOther(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Register two users
|
||||
registerUser(server.URL, "user1", "user1@test.com", "UserPass123!")
|
||||
token1 := getToken(server.URL, "user1", "UserPass123!")
|
||||
registerUser(server.URL, "user2", "user2@test.com", "UserPass123!")
|
||||
|
||||
// user1 tries to update user2's avatar (should be forbidden)
|
||||
fileContent := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
|
||||
resp, err := doUploadFile(server.URL+"/api/v1/users/2/avatar", token1, "avatar", "test.png", fileContent)
|
||||
if err != nil {
|
||||
t.Fatalf("upload request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected status %d for non-admin updating other's avatar, got %d", http.StatusForbidden, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAvatarHandler_UploadAvatar_UserNotFoundOrForbidden(t *testing.T) {
|
||||
server, cleanup := setupHandlerTestServer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Register and login as a user
|
||||
registerUser(server.URL, "avataruser", "avataruser@test.com", "UserPass123!")
|
||||
token := getToken(server.URL, "avataruser", "UserPass123!")
|
||||
|
||||
// Try to upload avatar for non-existent user (ID 9999)
|
||||
// Should return 403 because permission check happens before existence check
|
||||
// (security: don't reveal whether user exists)
|
||||
fileContent := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
|
||||
resp, err := doUploadFile(server.URL+"/api/v1/users/9999/avatar", token, "avatar", "test.png", fileContent)
|
||||
if err != nil {
|
||||
t.Fatalf("upload request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Handler returns 403 (permission denied) before checking if user exists
|
||||
// This is intentional security behavior - don't leak whether user ID exists
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("expected status %d for updating non-existent user's avatar, got %d", http.StatusForbidden, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user