package handler_test import ( "bytes" "io" "mime/multipart" "net/http" "os" "testing" ) // minimalPNG is a valid 1x1 PNG image var minimalPNG = []byte{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x05, 0xFE, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, } func buildAvatarUploadRequest(t *testing.T, url, token string, fileBody []byte, filename string) *http.Request { t.Helper() var body bytes.Buffer writer := multipart.NewWriter(&body) part, err := writer.CreateFormFile("avatar", filename) if err != nil { t.Fatalf("create form file failed: %v", err) } if _, err := part.Write(fileBody); err != nil { t.Fatalf("write file body failed: %v", err) } if err := writer.Close(); err != nil { t.Fatalf("close multipart writer failed: %v", err) } req, err := http.NewRequest(http.MethodPost, url, &body) if err != nil { t.Fatalf("create request failed: %v", err) } if token != "" { req.Header.Set("Authorization", "Bearer "+token) } req.Header.Set("Content-Type", writer.FormDataContentType()) return req } func TestAvatarHandler_UploadAvatar(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "avatar-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "avatar-bootstrap-secret", "avataradmin", "avataradmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } if ok := registerUser(server.URL, "avataruser", "avataruser@test.com", "UserPass123!"); !ok { t.Fatal("register user failed") } userToken := getToken(server.URL, "avataruser", "UserPass123!") if userToken == "" { t.Fatal("get user token failed") } tests := []struct { name string userID string token string fileBody []byte filename string wantStatus int }{ { name: "admin_upload_for_any_user", userID: "2", token: adminToken, fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusForbidden, }, { name: "user_upload_own_avatar", userID: "2", token: userToken, fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusOK, }, { name: "unauthorized", userID: "1", token: "", fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusUnauthorized, }, { name: "forbidden_cross_user", userID: "1", token: userToken, fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusForbidden, }, { name: "invalid_user_id", userID: "invalid", token: adminToken, fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusBadRequest, }, { name: "invalid_file_type", userID: "1", token: adminToken, fileBody: []byte("this is not an image"), filename: "avatar.txt", wantStatus: http.StatusBadRequest, }, { name: "user_not_found", userID: "99999", token: adminToken, fileBody: minimalPNG, filename: "avatar.png", wantStatus: http.StatusForbidden, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := buildAvatarUploadRequest(t, server.URL+"/api/v1/users/"+tt.userID+"/avatar", tt.token, tt.fileBody, tt.filename) client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { body, _ := io.ReadAll(resp.Body) t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, string(body)) } }) } // Clean up uploaded avatars _ = os.RemoveAll("./uploads/avatars") }