diff --git a/internal/api/handler/rbac_handler_test.go b/internal/api/handler/rbac_handler_test.go new file mode 100644 index 0000000..9a3b328 --- /dev/null +++ b/internal/api/handler/rbac_handler_test.go @@ -0,0 +1,740 @@ +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) +}