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:
2026-04-11 20:05:40 +08:00
parent fd1161b867
commit 27a8dd91a2

View File

@@ -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)
}
}