mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Enhance file upload functionality to support multiple files; update related tests and response structure
This commit is contained in:
@@ -24,6 +24,7 @@ type SaveFileResponse struct {
|
|||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadFilesResponse represents a response to an upload files request
|
||||||
type UploadFilesResponse struct {
|
type UploadFilesResponse struct {
|
||||||
FilePaths []string `json:"filePaths"`
|
FilePaths []string `json:"filePaths"`
|
||||||
}
|
}
|
||||||
@@ -330,6 +331,16 @@ func (h *Handler) UploadFile() http.HandlerFunc {
|
|||||||
"clientIP", r.RemoteAddr,
|
"clientIP", r.RemoteAddr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parse multipart form (max 32MB in memory)
|
||||||
|
err := r.ParseMultipartForm(32 << 20)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to parse multipart form",
|
||||||
|
"error", err.Error(),
|
||||||
|
)
|
||||||
|
respondError(w, "Failed to parse form", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
form := r.MultipartForm
|
form := r.MultipartForm
|
||||||
if form == nil || len(form.File) == 0 {
|
if form == nil || len(form.File) == 0 {
|
||||||
log.Debug("no files found in form")
|
log.Debug("no files found in form")
|
||||||
@@ -338,12 +349,6 @@ func (h *Handler) UploadFile() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadPath := r.URL.Query().Get("file_path")
|
uploadPath := r.URL.Query().Get("file_path")
|
||||||
if uploadPath == "" {
|
|
||||||
log.Debug("missing file_path parameter")
|
|
||||||
respondError(w, "file_path is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedPath, err := url.PathUnescape(uploadPath)
|
decodedPath, err := url.PathUnescape(uploadPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to decode file path",
|
log.Error("failed to decode file path",
|
||||||
|
|||||||
@@ -241,23 +241,22 @@ func testFileHandlers(t *testing.T, dbConfig DatabaseConfig) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("upload file", func(t *testing.T) {
|
t.Run("upload file", func(t *testing.T) {
|
||||||
t.Run("successful upload", func(t *testing.T) {
|
t.Run("successful single file upload", func(t *testing.T) {
|
||||||
fileName := "uploaded-test.txt"
|
fileName := "uploaded-test.txt"
|
||||||
fileContent := "This is an uploaded file"
|
fileContent := "This is an uploaded file"
|
||||||
|
|
||||||
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape("uploads"), fileName, fileContent, h.RegularTestUser)
|
files := map[string]string{fileName: fileContent}
|
||||||
|
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape("uploads"), files, h.RegularTestUser)
|
||||||
require.Equal(t, http.StatusOK, rr.Code)
|
require.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
|
||||||
// Verify response structure
|
// Verify response structure for multiple files API
|
||||||
var response struct {
|
var response struct {
|
||||||
FilePath string `json:"filePath"`
|
FilePaths []string `json:"filePaths"`
|
||||||
Size int64 `json:"size"`
|
|
||||||
UpdatedAt string `json:"updatedAt"`
|
|
||||||
}
|
}
|
||||||
err := json.NewDecoder(rr.Body).Decode(&response)
|
err := json.NewDecoder(rr.Body).Decode(&response)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "uploads/"+fileName, response.FilePath)
|
require.Len(t, response.FilePaths, 1)
|
||||||
assert.Equal(t, int64(len(fileContent)), response.Size)
|
assert.Equal(t, "uploads/"+fileName, response.FilePaths[0])
|
||||||
|
|
||||||
// Verify file was saved
|
// Verify file was saved
|
||||||
rr = h.makeRequest(t, http.MethodGet, baseURL+"/content?file_path="+url.QueryEscape("uploads/"+fileName), nil, h.RegularTestUser)
|
rr = h.makeRequest(t, http.MethodGet, baseURL+"/content?file_path="+url.QueryEscape("uploads/"+fileName), nil, h.RegularTestUser)
|
||||||
@@ -265,8 +264,62 @@ func testFileHandlers(t *testing.T, dbConfig DatabaseConfig) {
|
|||||||
assert.Equal(t, fileContent, rr.Body.String())
|
assert.Equal(t, fileContent, rr.Body.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("successful multiple files upload", func(t *testing.T) {
|
||||||
|
files := map[string]string{
|
||||||
|
"file1.txt": "Content of first file",
|
||||||
|
"file2.md": "# Content of second file",
|
||||||
|
"file3.py": "print('Content of third file')",
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape("batch"), files, h.RegularTestUser)
|
||||||
|
require.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
|
||||||
|
// Verify response structure
|
||||||
|
var response struct {
|
||||||
|
FilePaths []string `json:"filePaths"`
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(rr.Body).Decode(&response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, response.FilePaths, 3)
|
||||||
|
|
||||||
|
// Verify all files were saved with correct paths
|
||||||
|
expectedPaths := []string{"batch/file1.txt", "batch/file2.md", "batch/file3.py"}
|
||||||
|
for _, expectedPath := range expectedPaths {
|
||||||
|
assert.Contains(t, response.FilePaths, expectedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file contents
|
||||||
|
for fileName, expectedContent := range files {
|
||||||
|
filePath := "batch/" + fileName
|
||||||
|
rr = h.makeRequest(t, http.MethodGet, baseURL+"/content?file_path="+url.QueryEscape(filePath), nil, h.RegularTestUser)
|
||||||
|
require.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
assert.Equal(t, expectedContent, rr.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("upload without file", func(t *testing.T) {
|
t.Run("upload without file", func(t *testing.T) {
|
||||||
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape("test"), "", "", h.RegularTestUser)
|
// Empty map means no files
|
||||||
|
files := map[string]string{}
|
||||||
|
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape("test"), files, h.RegularTestUser)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("upload with missing file_path parameter", func(t *testing.T) {
|
||||||
|
fileName := "test.txt"
|
||||||
|
fileContent := "test content"
|
||||||
|
files := map[string]string{fileName: fileContent}
|
||||||
|
|
||||||
|
rr := h.makeUploadRequest(t, baseURL+"/upload", files, h.RegularTestUser)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("upload with invalid file_path", func(t *testing.T) {
|
||||||
|
fileName := "test.txt"
|
||||||
|
fileContent := "test content"
|
||||||
|
invalidPath := "../../../etc/passwd"
|
||||||
|
files := map[string]string{fileName: fileContent}
|
||||||
|
|
||||||
|
rr := h.makeUploadRequest(t, baseURL+"/upload?file_path="+url.QueryEscape(invalidPath), files, h.RegularTestUser)
|
||||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -328,18 +328,20 @@ func (h *testHarness) makeRequestRaw(t *testing.T, method, path string, body io.
|
|||||||
return h.executeRequest(req)
|
return h.executeRequest(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeUploadRequest creates a multipart form request for file uploads
|
// makeUploadRequest creates a multipart form request for file uploads (single or multiple)
|
||||||
// If fileName is empty, creates an empty multipart form without a file
|
// For single file: use map with one entry, e.g., map[string]string{"file.txt": "content"}
|
||||||
func (h *testHarness) makeUploadRequest(t *testing.T, path, fileName, fileContent string, testUser *testUser) *httptest.ResponseRecorder {
|
// For multiple files: use map with multiple entries
|
||||||
|
// For empty form (no files): pass empty map
|
||||||
|
func (h *testHarness) makeUploadRequest(t *testing.T, path string, files map[string]string, testUser *testUser) *httptest.ResponseRecorder {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// Create multipart form
|
// Create multipart form
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
writer := multipart.NewWriter(&buf)
|
writer := multipart.NewWriter(&buf)
|
||||||
|
|
||||||
// Only add file part if fileName is not empty
|
// Add all files
|
||||||
if fileName != "" {
|
for fileName, fileContent := range files {
|
||||||
part, err := writer.CreateFormFile("file", fileName)
|
part, err := writer.CreateFormFile("files", fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create form file: %v", err)
|
t.Fatalf("Failed to create form file: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user