405 lines
9.9 KiB
Go
405 lines
9.9 KiB
Go
|
|
package handler
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
)
|
||
|
|
|
||
|
|
// 测试辅助函数
|
||
|
|
|
||
|
|
// testRoleResponse 用于测试的角色响应
|
||
|
|
type testRoleResponse struct {
|
||
|
|
Code string `json:"role_code"`
|
||
|
|
Name string `json:"role_name"`
|
||
|
|
Type string `json:"role_type"`
|
||
|
|
Level int `json:"level"`
|
||
|
|
IsActive bool `json:"is_active"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// testIAMService 模拟IAM服务
|
||
|
|
type testIAMService struct {
|
||
|
|
roles map[string]*testRoleResponse
|
||
|
|
userScopes map[int64][]string
|
||
|
|
}
|
||
|
|
|
||
|
|
type testRoleResponse2 struct {
|
||
|
|
Code string
|
||
|
|
Name string
|
||
|
|
Type string
|
||
|
|
Level int
|
||
|
|
IsActive bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func newTestIAMService() *testIAMService {
|
||
|
|
return &testIAMService{
|
||
|
|
roles: map[string]*testRoleResponse{
|
||
|
|
"viewer": {Code: "viewer", Name: "查看者", Type: "platform", Level: 10, IsActive: true},
|
||
|
|
"operator": {Code: "operator", Name: "运维", Type: "platform", Level: 30, IsActive: true},
|
||
|
|
},
|
||
|
|
userScopes: map[int64][]string{
|
||
|
|
1: {"platform:read", "platform:write"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *testIAMService) CreateRole(req *CreateRoleHTTPRequest) (*testRoleResponse, error) {
|
||
|
|
if _, exists := s.roles[req.Code]; exists {
|
||
|
|
return nil, errDuplicateRole
|
||
|
|
}
|
||
|
|
return &testRoleResponse{
|
||
|
|
Code: req.Code,
|
||
|
|
Name: req.Name,
|
||
|
|
Type: req.Type,
|
||
|
|
Level: req.Level,
|
||
|
|
IsActive: true,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *testIAMService) GetRole(roleCode string) (*testRoleResponse, error) {
|
||
|
|
if role, exists := s.roles[roleCode]; exists {
|
||
|
|
return role, nil
|
||
|
|
}
|
||
|
|
return nil, errNotFound
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *testIAMService) ListRoles(roleType string) ([]*testRoleResponse, error) {
|
||
|
|
var result []*testRoleResponse
|
||
|
|
for _, role := range s.roles {
|
||
|
|
if roleType == "" || role.Type == roleType {
|
||
|
|
result = append(result, role)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *testIAMService) CheckScope(userID int64, scope string) bool {
|
||
|
|
scopes, ok := s.userScopes[userID]
|
||
|
|
if !ok {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
for _, s := range scopes {
|
||
|
|
if s == scope || s == "*" {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// HTTP请求/响应类型
|
||
|
|
type CreateRoleHTTPRequest struct {
|
||
|
|
Code string `json:"code"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Type string `json:"type"`
|
||
|
|
Level int `json:"level"`
|
||
|
|
Scopes []string `json:"scopes"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// 错误
|
||
|
|
var (
|
||
|
|
errNotFound = &HTTPErrorResponse{Code: "NOT_FOUND", Message: "not found"}
|
||
|
|
errDuplicateRole = &HTTPErrorResponse{Code: "DUPLICATE", Message: "duplicate"}
|
||
|
|
)
|
||
|
|
|
||
|
|
// HTTPErrorResponse HTTP错误响应
|
||
|
|
type HTTPErrorResponse struct {
|
||
|
|
Code string `json:"code"`
|
||
|
|
Message string `json:"message"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (e *HTTPErrorResponse) Error() string {
|
||
|
|
return e.Message
|
||
|
|
}
|
||
|
|
|
||
|
|
// HTTPHandler 测试用的HTTP处理器
|
||
|
|
type HTTPHandler struct {
|
||
|
|
iam *testIAMService
|
||
|
|
}
|
||
|
|
|
||
|
|
func newHTTPHandler() *HTTPHandler {
|
||
|
|
return &HTTPHandler{iam: newTestIAMService()}
|
||
|
|
}
|
||
|
|
|
||
|
|
// handleCreateRole 创建角色
|
||
|
|
func (h *HTTPHandler) handleCreateRole(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req CreateRoleHTTPRequest
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeErrorHTTPTest(w, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
role, err := h.iam.CreateRole(&req)
|
||
|
|
if err != nil {
|
||
|
|
writeErrorHTTPTest(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSONHTTPTest(w, http.StatusCreated, map[string]interface{}{
|
||
|
|
"role": role,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// handleListRoles 列出角色
|
||
|
|
func (h *HTTPHandler) handleListRoles(w http.ResponseWriter, r *http.Request) {
|
||
|
|
roleType := r.URL.Query().Get("type")
|
||
|
|
|
||
|
|
roles, err := h.iam.ListRoles(roleType)
|
||
|
|
if err != nil {
|
||
|
|
writeErrorHTTPTest(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSONHTTPTest(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"roles": roles,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// handleGetRole 获取角色
|
||
|
|
func (h *HTTPHandler) handleGetRole(w http.ResponseWriter, r *http.Request) {
|
||
|
|
roleCode := r.URL.Query().Get("code")
|
||
|
|
if roleCode == "" {
|
||
|
|
writeErrorHTTPTest(w, http.StatusBadRequest, "MISSING_CODE", "role code is required")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
role, err := h.iam.GetRole(roleCode)
|
||
|
|
if err != nil {
|
||
|
|
if err == errNotFound {
|
||
|
|
writeErrorHTTPTest(w, http.StatusNotFound, "NOT_FOUND", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
writeErrorHTTPTest(w, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSONHTTPTest(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"role": role,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// handleCheckScope 检查Scope
|
||
|
|
func (h *HTTPHandler) handleCheckScope(w http.ResponseWriter, r *http.Request) {
|
||
|
|
scope := r.URL.Query().Get("scope")
|
||
|
|
if scope == "" {
|
||
|
|
writeErrorHTTPTest(w, http.StatusBadRequest, "MISSING_SCOPE", "scope is required")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
userID := int64(1)
|
||
|
|
hasScope := h.iam.CheckScope(userID, scope)
|
||
|
|
|
||
|
|
writeJSONHTTPTest(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"has_scope": hasScope,
|
||
|
|
"scope": scope,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func writeJSONHTTPTest(w http.ResponseWriter, status int, data interface{}) {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
w.WriteHeader(status)
|
||
|
|
json.NewEncoder(w).Encode(data)
|
||
|
|
}
|
||
|
|
|
||
|
|
func writeErrorHTTPTest(w http.ResponseWriter, status int, code, message string) {
|
||
|
|
writeJSONHTTPTest(w, status, map[string]interface{}{
|
||
|
|
"error": map[string]string{
|
||
|
|
"code": code,
|
||
|
|
"message": message,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 测试用例 ====================
|
||
|
|
|
||
|
|
// TestHTTPHandler_CreateRole_Success 测试创建角色成功
|
||
|
|
func TestHTTPHandler_CreateRole_Success(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
body := `{"code":"developer","name":"开发者","type":"platform","level":20}`
|
||
|
|
req := httptest.NewRequest("POST", "/api/v1/iam/roles", strings.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleCreateRole(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusCreated, rec.Code)
|
||
|
|
|
||
|
|
var resp map[string]interface{}
|
||
|
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||
|
|
|
||
|
|
role := resp["role"].(map[string]interface{})
|
||
|
|
assert.Equal(t, "developer", role["role_code"])
|
||
|
|
assert.Equal(t, "开发者", role["role_name"])
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_ListRoles_Success 测试列出角色成功
|
||
|
|
func TestHTTPHandler_ListRoles_Success(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/roles", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleListRoles(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||
|
|
|
||
|
|
var resp map[string]interface{}
|
||
|
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||
|
|
|
||
|
|
roles := resp["roles"].([]interface{})
|
||
|
|
assert.Len(t, roles, 2)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_ListRoles_WithType 测试按类型列出角色
|
||
|
|
func TestHTTPHandler_ListRoles_WithType(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/roles?type=platform", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleListRoles(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_GetRole_Success 测试获取角色成功
|
||
|
|
func TestHTTPHandler_GetRole_Success(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/roles?code=viewer", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleGetRole(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||
|
|
|
||
|
|
var resp map[string]interface{}
|
||
|
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||
|
|
|
||
|
|
role := resp["role"].(map[string]interface{})
|
||
|
|
assert.Equal(t, "viewer", role["role_code"])
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_GetRole_NotFound 测试获取不存在的角色
|
||
|
|
func TestHTTPHandler_GetRole_NotFound(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/roles?code=nonexistent", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleGetRole(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusNotFound, rec.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_CheckScope_HasScope 测试检查Scope存在
|
||
|
|
func TestHTTPHandler_CheckScope_HasScope(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/check-scope?scope=platform:read", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleCheckScope(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||
|
|
|
||
|
|
var resp map[string]interface{}
|
||
|
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||
|
|
|
||
|
|
assert.Equal(t, true, resp["has_scope"])
|
||
|
|
assert.Equal(t, "platform:read", resp["scope"])
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_CheckScope_NoScope 测试检查Scope不存在
|
||
|
|
func TestHTTPHandler_CheckScope_NoScope(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/check-scope?scope=platform:admin", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleCheckScope(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
||
|
|
|
||
|
|
var resp map[string]interface{}
|
||
|
|
json.Unmarshal(rec.Body.Bytes(), &resp)
|
||
|
|
|
||
|
|
assert.Equal(t, false, resp["has_scope"])
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_CheckScope_MissingScope 测试缺少Scope参数
|
||
|
|
func TestHTTPHandler_CheckScope_MissingScope(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/check-scope", nil)
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleCheckScope(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_CreateRole_InvalidJSON 测试无效JSON
|
||
|
|
func TestHTTPHandler_CreateRole_InvalidJSON(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
body := `invalid json`
|
||
|
|
req := httptest.NewRequest("POST", "/api/v1/iam/roles", strings.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleCreateRole(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestHTTPHandler_GetRole_MissingCode 测试缺少角色代码
|
||
|
|
func TestHTTPHandler_GetRole_MissingCode(t *testing.T) {
|
||
|
|
// arrange
|
||
|
|
handler := newHTTPHandler()
|
||
|
|
|
||
|
|
req := httptest.NewRequest("GET", "/api/v1/iam/roles", nil) // 没有code参数
|
||
|
|
|
||
|
|
// act
|
||
|
|
rec := httptest.NewRecorder()
|
||
|
|
handler.handleGetRole(rec, req)
|
||
|
|
|
||
|
|
// assert
|
||
|
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 确保函数被使用(避免编译错误)
|
||
|
|
var _ = context.Background
|