package handler_test import ( "bytes" "io" "mime/multipart" "net/http" "testing" "github.com/stretchr/testify/assert" ) // ============================================================================= // ExportHandler Tests - Data Export/Import // ============================================================================= // TestExportHandler_ExportUsers_Success 验证导出用户数据 func TestExportHandler_ExportUsers_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/users", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusInternalServerError, "should export users, got %d", resp.StatusCode) } // TestExportHandler_ExportUsers_WithFormat 验证指定格式导出 func TestExportHandler_ExportUsers_WithFormat(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } // CSV format resp1, _ := doGet(server.URL+"/api/v1/exports/users?format=csv", token) defer resp1.Body.Close() assert.True(t, resp1.StatusCode == http.StatusOK || resp1.StatusCode == http.StatusForbidden, "should export CSV, got %d", resp1.StatusCode) // Excel format resp2, _ := doGet(server.URL+"/api/v1/exports/users?format=excel", token) defer resp2.Body.Close() assert.True(t, resp2.StatusCode == http.StatusOK || resp2.StatusCode == http.StatusForbidden || resp2.StatusCode == http.StatusBadRequest, "should export Excel, got %d", resp2.StatusCode) } // TestExportHandler_ExportUsers_WithFields 验证指定字段导出 func TestExportHandler_ExportUsers_WithFields(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/users?fields=id,username,email&format=csv", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden, "should export with fields, got %d", resp.StatusCode) } // TestExportHandler_ExportUsers_WithFilter 验证带过滤条件导出 func TestExportHandler_ExportUsers_WithFilter(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/users?keyword=admin&status=1&format=csv", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusBadRequest, "should export with filter, got %d", resp.StatusCode) } // TestExportHandler_ExportUsers_NonAdmin 验证非管理员导出 func TestExportHandler_ExportUsers_NonAdmin(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "regular", "regular@test.com", "Pass123!") token := getToken(server.URL, "regular", "Pass123!") assert.NotEmpty(t, token) resp, _ := doGet(server.URL+"/api/v1/exports/users", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK, "should handle non-admin export, got %d", resp.StatusCode) } // TestExportHandler_ExportUsers_Unauthorized 验证未认证导出 func TestExportHandler_ExportUsers_Unauthorized(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doGet(server.URL+"/api/v1/exports/users", "") defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden, "should require auth, got %d", resp.StatusCode) } // TestExportHandler_ImportUsers_Success 验证导入用户数据 func TestExportHandler_ImportUsers_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } // Create multipart form with CSV data var body bytes.Buffer writer := multipart.NewWriter(&body) part, _ := writer.CreateFormFile("file", "users.csv") csvData := "username,email,password\nuser1,user1@test.com,Pass123!\nuser2,user2@test.com,Pass123!" part.Write([]byte(csvData)) writer.Close() req, _ := http.NewRequest("POST", server.URL+"/api/v1/exports/users?format=csv", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, err := client.Do(req) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusInternalServerError, "should import users, got %d: %s", resp.StatusCode, string(respBody)) } // TestExportHandler_ImportUsers_NoFile 验证无文件导入 func TestExportHandler_ImportUsers_NoFile(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } // Create empty multipart form var body bytes.Buffer writer := multipart.NewWriter(&body) writer.Close() req, _ := http.NewRequest("POST", server.URL+"/api/v1/exports/users", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusOK, "should require file, got %d", resp.StatusCode) } // TestExportHandler_ImportUsers_InvalidFormat 验证无效格式导入 func TestExportHandler_ImportUsers_InvalidFormat(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } var body bytes.Buffer writer := multipart.NewWriter(&body) part, _ := writer.CreateFormFile("file", "users.txt") part.Write([]byte("invalid content")) writer.Close() req, _ := http.NewRequest("POST", server.URL+"/api/v1/exports/users?format=invalid", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden, "should handle invalid format, got %d", resp.StatusCode) } // TestExportHandler_ImportUsers_NonAdmin 验证非管理员导入 func TestExportHandler_ImportUsers_NonAdmin(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() registerUser(server.URL, "regular", "regular@test.com", "Pass123!") token := getToken(server.URL, "regular", "Pass123!") assert.NotEmpty(t, token) var body bytes.Buffer writer := multipart.NewWriter(&body) part, _ := writer.CreateFormFile("file", "users.csv") part.Write([]byte("username,email\nuser1,user1@test.com")) writer.Close() req, _ := http.NewRequest("POST", server.URL+"/api/v1/exports/users", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK, "should handle non-admin import, got %d", resp.StatusCode) } // TestExportHandler_GetImportTemplate_Success 验证获取导入模板 func TestExportHandler_GetImportTemplate_Success(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/template", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusInternalServerError, "should get template, got %d", resp.StatusCode) } // TestExportHandler_GetImportTemplate_CSV 验证 CSV 模板 func TestExportHandler_GetImportTemplate_CSV(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/template?format=csv", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden, "should get CSV template, got %d", resp.StatusCode) } // TestExportHandler_GetImportTemplate_Excel 验证 Excel 模板 func TestExportHandler_GetImportTemplate_Excel(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/template?format=excel", token) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusBadRequest, "should get Excel template, got %d", resp.StatusCode) } // TestExportHandler_GetImportTemplate_Unauthorized 验证未认证获取模板 func TestExportHandler_GetImportTemplate_Unauthorized(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() resp, _ := doGet(server.URL+"/api/v1/exports/template", "") defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden, "should require auth, got %d", resp.StatusCode) } // TestExportHandler_ExportResponse_ContentType 验证导出响应内容类型 func TestExportHandler_ExportResponse_ContentType(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/users?format=csv", token) defer resp.Body.Close() if resp.StatusCode == http.StatusOK { contentType := resp.Header.Get("Content-Type") // Content-Type may or may not be set depending on implementation t.Logf("Content-Type: %s", contentType) } } // TestExportHandler_ExportResponse_ContentDisposition 验证导出响应文件名 func TestExportHandler_ExportResponse_ContentDisposition(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() token := bootstrapAdminToken(server.URL, "admin", "admin@test.com", "AdminPass123!") if token == "" { t.Fatal("bootstrap admin token should succeed") } resp, _ := doGet(server.URL+"/api/v1/exports/users?format=csv", token) defer resp.Body.Close() if resp.StatusCode == http.StatusOK { disposition := resp.Header.Get("Content-Disposition") // Disposition may or may not be set depending on implementation t.Logf("Content-Disposition: %s", disposition) } }