test: 补齐 handler/repository/domain 层单元测试
This commit is contained in:
151
internal/api/handler/avatar_handler_test.go
Normal file
151
internal/api/handler/avatar_handler_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user