minioUploadHandler_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package minio
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "mime/multipart"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "perms-system-server/internal/config"
  10. "perms-system-server/internal/response"
  11. "perms-system-server/internal/svc"
  12. "perms-system-server/internal/testutil"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. func init() {
  17. response.Setup()
  18. }
  19. func newMinioTestSvcCtx() *svc.ServiceContext {
  20. cfg := testutil.GetTestConfig()
  21. cfg.Minio = config.MinioConf{
  22. Name: "test-minio",
  23. AccessKeyId: "test",
  24. AccessKeySecret: "test",
  25. Endpoint: "",
  26. Domain: "https://minio.test.com",
  27. UseSSL: false,
  28. FileType: map[string]config.MinioFileTypeConf{
  29. "avatar": {
  30. Bucket: "perms-system",
  31. Dir: "avatar/{yyyy}/{mm}/{dd}",
  32. AllowedContentTypes: []string{"image/jpeg", "image/png"},
  33. },
  34. "any": {
  35. Bucket: "perms-system",
  36. Dir: "any",
  37. AllowedContentTypes: []string{},
  38. },
  39. },
  40. }
  41. return svc.NewServiceContext(cfg)
  42. }
  43. func createMultipartRequest(t *testing.T, fileType, contentType, fileName string, fileContent []byte) *http.Request {
  44. t.Helper()
  45. body := &bytes.Buffer{}
  46. writer := multipart.NewWriter(body)
  47. if fileType != "" {
  48. writer.WriteField("fileType", fileType)
  49. }
  50. if fileName != "" {
  51. part, err := writer.CreateFormFile("file", fileName)
  52. require.NoError(t, err)
  53. _, err = part.Write(fileContent)
  54. require.NoError(t, err)
  55. }
  56. writer.Close()
  57. req := httptest.NewRequest(http.MethodPost, "/api/minio/upload", body)
  58. req.Header.Set("Content-Type", writer.FormDataContentType())
  59. if contentType != "" && fileName != "" {
  60. // multipart Content-Type is set in the part header by CreateFormFile;
  61. // to override it, we need to use CreatePart directly
  62. // For simplicity, handler reads Content-Type from the fileHeader which defaults to application/octet-stream
  63. // So we rebuild with explicit content type
  64. body2 := &bytes.Buffer{}
  65. writer2 := multipart.NewWriter(body2)
  66. if fileType != "" {
  67. writer2.WriteField("fileType", fileType)
  68. }
  69. h := make(map[string][]string)
  70. h["Content-Disposition"] = []string{`form-data; name="file"; filename="` + fileName + `"`}
  71. h["Content-Type"] = []string{contentType}
  72. part2, err := writer2.CreatePart(h)
  73. require.NoError(t, err)
  74. _, err = part2.Write(fileContent)
  75. require.NoError(t, err)
  76. writer2.Close()
  77. req = httptest.NewRequest(http.MethodPost, "/api/minio/upload", body2)
  78. req.Header.Set("Content-Type", writer2.FormDataContentType())
  79. }
  80. return req
  81. }
  82. // TC-1249: handler 缺少 file 字段
  83. func TestMinioUploadHandler_MissingFile(t *testing.T) {
  84. svcCtx := newMinioTestSvcCtx()
  85. handler := MinioUploadHandler(svcCtx)
  86. body := &bytes.Buffer{}
  87. writer := multipart.NewWriter(body)
  88. writer.WriteField("fileType", "avatar")
  89. writer.Close()
  90. req := httptest.NewRequest(http.MethodPost, "/api/minio/upload", body)
  91. req.Header.Set("Content-Type", writer.FormDataContentType())
  92. rr := httptest.NewRecorder()
  93. handler.ServeHTTP(rr, req)
  94. var respBody response.Body
  95. err := json.Unmarshal(rr.Body.Bytes(), &respBody)
  96. require.NoError(t, err)
  97. assert.False(t, respBody.Success)
  98. }
  99. // TC-1242: fileType 为空 → 400
  100. func TestMinioUploadHandler_EmptyFileType(t *testing.T) {
  101. svcCtx := newMinioTestSvcCtx()
  102. handler := MinioUploadHandler(svcCtx)
  103. req := createMultipartRequest(t, "", "image/png", "test.png", []byte("fake png content"))
  104. rr := httptest.NewRecorder()
  105. handler.ServeHTTP(rr, req)
  106. var respBody response.Body
  107. err := json.Unmarshal(rr.Body.Bytes(), &respBody)
  108. require.NoError(t, err)
  109. assert.False(t, respBody.Success)
  110. assert.Equal(t, 400, respBody.ErrorCode)
  111. assert.Contains(t, respBody.ErrorMessage, "fileType is required")
  112. }
  113. // TC-1243: fileType 不在配置中 → 400
  114. func TestMinioUploadHandler_UnknownFileType(t *testing.T) {
  115. svcCtx := newMinioTestSvcCtx()
  116. handler := MinioUploadHandler(svcCtx)
  117. req := createMultipartRequest(t, "unknown_type", "image/png", "test.png", []byte("fake content"))
  118. rr := httptest.NewRecorder()
  119. handler.ServeHTTP(rr, req)
  120. var respBody response.Body
  121. err := json.Unmarshal(rr.Body.Bytes(), &respBody)
  122. require.NoError(t, err)
  123. assert.False(t, respBody.Success)
  124. assert.Equal(t, 400, respBody.ErrorCode)
  125. assert.Contains(t, respBody.ErrorMessage, "fileType not configured")
  126. }
  127. // TC-1244: Content-Type 不在白名单中 → 400
  128. func TestMinioUploadHandler_InvalidContentType(t *testing.T) {
  129. svcCtx := newMinioTestSvcCtx()
  130. handler := MinioUploadHandler(svcCtx)
  131. req := createMultipartRequest(t, "avatar", "application/zip", "test.zip", []byte("fake zip content"))
  132. rr := httptest.NewRecorder()
  133. handler.ServeHTTP(rr, req)
  134. var respBody response.Body
  135. err := json.Unmarshal(rr.Body.Bytes(), &respBody)
  136. require.NoError(t, err)
  137. assert.False(t, respBody.Success)
  138. assert.Equal(t, 400, respBody.ErrorCode)
  139. assert.Contains(t, respBody.ErrorMessage, "invalid contentType")
  140. }