152 lines
4.0 KiB
Go
152 lines
4.0 KiB
Go
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")
|
|
}
|