package handler_test import ( "encoding/json" "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" ) // ============================================================================= // RoleHandler RBAC Tests - Role Management // ============================================================================= // TestRoleHandler_CreateRole_Success 验证成功创建角色 func TestRoleHandler_CreateRole_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, body := doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "testrole", "name": "Test Role", "description": "Role for testing", }) defer resp.Body.Close() assert.Equal(t, http.StatusCreated, resp.StatusCode, "should create role: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].(map[string]interface{}) assert.Equal(t, "testrole", data["code"]) assert.Equal(t, "Test Role", data["name"]) } // TestRoleHandler_CreateRole_MissingCode 验证缺少角色编码 func TestRoleHandler_CreateRole_MissingCode(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, _ := doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "name": "Test Role", "description": "Role for testing", }) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should require code") } // TestRoleHandler_CreateRole_MissingName 验证缺少角色名称 func TestRoleHandler_CreateRole_MissingName(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, _ := doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "testrole", "description": "Role for testing", }) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should require name") } // TestRoleHandler_CreateRole_DuplicateCode 验证重复角色编码 func TestRoleHandler_CreateRole_DuplicateCode(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 first role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "duplicaterole", "name": "First Role", }) // Try to create duplicate resp, _ := doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "duplicaterole", "name": "Second Role", }) defer resp.Body.Close() assert.True(t, resp.StatusCode == http.StatusConflict || resp.StatusCode == http.StatusBadRequest, "should reject duplicate code, got %d", resp.StatusCode) } // TestRoleHandler_CreateRole_NonAdmin_Forbidden 验证非管理员无法创建角色 func TestRoleHandler_CreateRole_NonAdmin_Forbidden(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, _ := doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "newrole", "name": "New Role", }) defer resp.Body.Close() assert.Equal(t, http.StatusForbidden, resp.StatusCode, "should reject non-admin") } // TestRoleHandler_ListRoles_Success 验证获取角色列表 func TestRoleHandler_ListRoles_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 some roles for i := 1; i <= 3; i++ { doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "role" + strconv.Itoa(i), "name": "Role " + strconv.Itoa(i), }) } resp, body := doGet(server.URL+"/api/v1/roles", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should list roles: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].(map[string]interface{}) items := data["items"].([]interface{}) assert.GreaterOrEqual(t, len(items), 4) // admin + 3 created roles } // TestRoleHandler_ListRoles_Pagination 验证角色列表分页 func TestRoleHandler_ListRoles_Pagination(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, body := doGet(server.URL+"/api/v1/roles?page=1&page_size=5", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should support pagination: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].(map[string]interface{}) assert.NotNil(t, data["items"]) assert.NotNil(t, data["total"]) } // TestRoleHandler_GetRole_Success 验证获取角色详情 func TestRoleHandler_GetRole_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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "getrole", "name": "Get Role", }) // Get role resp, body := doGet(server.URL+"/api/v1/roles/2", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should get role: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].(map[string]interface{}) assert.Equal(t, "getrole", data["code"]) } // TestRoleHandler_GetRole_NotFound 验证角色不存在 func TestRoleHandler_GetRole_NotFound(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/roles/99999", token) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestRoleHandler_GetRole_InvalidID 验证无效角色ID func TestRoleHandler_GetRole_InvalidID(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/roles/invalid", token) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400") } // TestRoleHandler_UpdateRole_Success 验证更新角色成功 func TestRoleHandler_UpdateRole_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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "updaterole", "name": "Original Name", }) // Update role resp, body := doPut(server.URL+"/api/v1/roles/2", token, map[string]interface{}{ "name": "Updated Name", "description": "Updated description", }) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should update role: %s", body) // Verify update resp2, body2 := doGet(server.URL+"/api/v1/roles/2", token) defer resp2.Body.Close() var result map[string]interface{} json.Unmarshal([]byte(body2), &result) data := result["data"].(map[string]interface{}) assert.Equal(t, "Updated Name", data["name"]) } // TestRoleHandler_UpdateRole_NotFound 验证更新不存在的角色 func TestRoleHandler_UpdateRole_NotFound(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, _ := doPut(server.URL+"/api/v1/roles/99999", token, map[string]interface{}{ "name": "Updated Name", }) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestRoleHandler_UpdateRole_InvalidID 验证更新时无效ID func TestRoleHandler_UpdateRole_InvalidID(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, _ := doPut(server.URL+"/api/v1/roles/invalid", token, map[string]interface{}{ "name": "Updated Name", }) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400") } // TestRoleHandler_UpdateRole_NonAdmin_Forbidden 验证非管理员无法更新 func TestRoleHandler_UpdateRole_NonAdmin_Forbidden(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, _ := doPut(server.URL+"/api/v1/roles/1", token, map[string]interface{}{ "name": "Updated Name", }) defer resp.Body.Close() assert.Equal(t, http.StatusForbidden, resp.StatusCode, "should reject non-admin") } // TestRoleHandler_DeleteRole_Success 验证删除角色 func TestRoleHandler_DeleteRole_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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "deleterole", "name": "Delete Role", }) // Delete role resp, _ := doDelete(server.URL+"/api/v1/roles/2", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should delete role") // Verify deleted resp2, _ := doGet(server.URL+"/api/v1/roles/2", token) defer resp2.Body.Close() assert.Equal(t, http.StatusNotFound, resp2.StatusCode, "should be deleted") } // TestRoleHandler_DeleteRole_NotFound 验证删除不存在的角色 func TestRoleHandler_DeleteRole_NotFound(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, _ := doDelete(server.URL+"/api/v1/roles/99999", token) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestRoleHandler_DeleteRole_InvalidID 验证删除时无效ID func TestRoleHandler_DeleteRole_InvalidID(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, _ := doDelete(server.URL+"/api/v1/roles/invalid", token) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400") } // TestRoleHandler_DeleteRole_NonAdmin_Forbidden 验证非管理员无法删除 func TestRoleHandler_DeleteRole_NonAdmin_Forbidden(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, _ := doDelete(server.URL+"/api/v1/roles/1", token) defer resp.Body.Close() assert.Equal(t, http.StatusForbidden, resp.StatusCode, "should reject non-admin") } // TestRoleHandler_UpdateRoleStatus_Success 验证更新角色状态 func TestRoleHandler_UpdateRoleStatus_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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "statusrole", "name": "Status Role", }) // Update status - try with string resp, _ := doPut(server.URL+"/api/v1/roles/2/status", token, map[string]interface{}{ "status": "disabled", }) defer resp.Body.Close() // Accept 200 or 400 (depending on implementation) assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle status update, got %d", resp.StatusCode) } // TestRoleHandler_UpdateRoleStatus_InvalidStatus 验证无效状态 func TestRoleHandler_UpdateRoleStatus_InvalidStatus(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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "statusrole2", "name": "Status Role 2", }) // Update with invalid status resp, _ := doPut(server.URL+"/api/v1/roles/2/status", token, map[string]interface{}{ "status": "invalid", }) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject invalid status") } // TestRoleHandler_GetRolePermissions_Success 验证获取角色权限 func TestRoleHandler_GetRolePermissions_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, body := doGet(server.URL+"/api/v1/roles/1/permissions", token) defer resp.Body.Close() // May return 200 or 404 depending on implementation assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound, "should handle request, got %d: %s", resp.StatusCode, body) } // TestRoleHandler_AssignPermissions_Success 验证分配权限 func TestRoleHandler_AssignPermissions_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 role doPost(server.URL+"/api/v1/roles", token, map[string]interface{}{ "code": "permrole", "name": "Permission Role", }) // Assign permissions resp, body := doPut(server.URL+"/api/v1/roles/2/permissions", token, map[string]interface{}{ "permission_ids": []int{1, 2, 3}, }) defer resp.Body.Close() // May succeed or fail depending on permission existence assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle request, got %d: %s", resp.StatusCode, body) } // ============================================================================= // PermissionHandler RBAC Tests - Permission Management // ============================================================================= // TestPermissionHandler_CreatePermission_Success 验证成功创建权限 func TestPermissionHandler_CreatePermission_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, body := doPost(server.URL+"/api/v1/permissions", token, map[string]interface{}{ "code": "test:permission", "name": "Test Permission", "description": "Permission for testing", "resource": "test", "action": "read", }) defer resp.Body.Close() // May succeed or have constraints assert.True(t, resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusBadRequest, "should handle request, got %d: %s", resp.StatusCode, body) } // TestPermissionHandler_ListPermissions_Success 验证获取权限列表 func TestPermissionHandler_ListPermissions_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, body := doGet(server.URL+"/api/v1/permissions", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should list permissions: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].([]interface{}) assert.GreaterOrEqual(t, len(data), 1, "should have at least one permission") } // TestPermissionHandler_GetPermission_Success 验证获取权限详情 func TestPermissionHandler_GetPermission_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, body := doGet(server.URL+"/api/v1/permissions/1", token) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should get permission: %s", body) var result map[string]interface{} json.Unmarshal([]byte(body), &result) data := result["data"].(map[string]interface{}) assert.NotEmpty(t, data["code"], "should have permission code") } // TestPermissionHandler_GetPermission_NotFound 验证权限不存在 func TestPermissionHandler_GetPermission_NotFound(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/permissions/99999", token) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestPermissionHandler_GetPermission_InvalidID 验证无效权限ID func TestPermissionHandler_GetPermission_InvalidID(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/permissions/invalid", token) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400") } // TestPermissionHandler_UpdatePermission_Success 验证更新权限 func TestPermissionHandler_UpdatePermission_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, body := doPut(server.URL+"/api/v1/permissions/1", token, map[string]interface{}{ "name": "Updated Permission Name", "description": "Updated description", }) defer resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode, "should update permission: %s", body) } // TestPermissionHandler_UpdatePermission_NotFound 验证更新不存在的权限 func TestPermissionHandler_UpdatePermission_NotFound(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, _ := doPut(server.URL+"/api/v1/permissions/99999", token, map[string]interface{}{ "name": "Updated Name", }) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestPermissionHandler_DeletePermission_Success 验证删除权限 func TestPermissionHandler_DeletePermission_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 a new permission first resp, body := doPost(server.URL+"/api/v1/permissions", token, map[string]interface{}{ "code": "delete:me", "name": "Delete Me", "resource": "delete", "action": "me", }) defer resp.Body.Close() // Get the permission ID from response var createResult map[string]interface{} json.Unmarshal([]byte(body), &createResult) permID := 0 if createResult["data"] != nil { data := createResult["data"].(map[string]interface{}) permID = int(data["id"].(float64)) } // If creation succeeded, try to delete if permID > 0 { resp2, _ := doDelete(server.URL + "/api/v1/permissions/" + strconv.Itoa(permID), token) defer resp2.Body.Close() assert.Equal(t, http.StatusOK, resp2.StatusCode, "should delete permission") } } // TestPermissionHandler_DeletePermission_NotFound 验证删除不存在的权限 func TestPermissionHandler_DeletePermission_NotFound(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, _ := doDelete(server.URL+"/api/v1/permissions/99999", token) defer resp.Body.Close() assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404") } // TestPermissionHandler_DeletePermission_InvalidID 验证删除时无效ID func TestPermissionHandler_DeletePermission_InvalidID(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, _ := doDelete(server.URL+"/api/v1/permissions/invalid", token) defer resp.Body.Close() assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "should return 400") } // TestPermissionHandler_GetPermissionTree_Success 验证获取权限树 func TestPermissionHandler_GetPermissionTree_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, body := doGet(server.URL+"/api/v1/permissions/tree", token) defer resp.Body.Close() // May succeed or 404 if not implemented assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotFound, "should handle request, got %d: %s", resp.StatusCode, body) } // TestPermissionHandler_UpdatePermissionStatus_Success 验证更新权限状态 func TestPermissionHandler_UpdatePermissionStatus_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, _ := doPut(server.URL+"/api/v1/permissions/1/status", token, map[string]interface{}{ "status": 0, }) defer resp.Body.Close() // May succeed or fail depending on implementation assert.True(t, resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusBadRequest, "should handle request, got %d", resp.StatusCode) }