Files
junhong_cmp_fiber/tests/integration/account_permission_test.go
huang 80f560df33
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s
refactor(account): 统一账号管理API、完善权限检查和操作审计
- 合并 customer_account 和 shop_account 路由到统一的 account 接口
- 新增统一认证接口 (auth handler)
- 实现越权防护中间件和权限检查工具函数
- 新增操作审计日志模型和服务
- 更新数据库迁移 (版本 39: account_operation_log 表)
- 补充集成测试覆盖权限检查和审计日志场景
2026-02-02 17:23:20 +08:00

496 lines
16 KiB
Go

package integration
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
)
// TestAccountPermission_12_2 企业账号访问账号管理接口被路由层拦截
func TestAccountPermission_12_2(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("测试店铺", 1, nil)
enterprise := env.CreateTestEnterprise("测试企业", &shop.ID)
enterpriseAccount := env.CreateTestAccount(
"enterprise_user",
"password123",
constants.UserTypeEnterprise,
nil,
&enterprise.ID,
)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeEnterprise,
EnterpriseID: &enterprise.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(enterpriseAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode, "企业账号应被路由层拦截")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Contains(t, result.Message, "权限", "错误消息应包含权限相关信息")
}
// TestAccountPermission_12_3 代理账号创建自己店铺的账号成功
func TestAccountPermission_12_3(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("代理店铺1", 1, nil)
agentAccount := env.CreateTestAccount(
"agent_user",
"password123",
constants.UserTypeAgent,
&shop.ID,
nil,
)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("agent_same_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "代理账号应能创建自己店铺的账号")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "业务码应为0")
}
// TestAccountPermission_12_4 代理账号创建下级店铺的账号成功
func TestAccountPermission_12_4(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
parentShop := env.CreateTestShop("父店铺", 1, nil)
childShop := env.CreateTestShop("子店铺", 2, &parentShop.ID)
agentAccount := env.CreateTestAccount(
"agent_parent",
"password123",
constants.UserTypeAgent,
&parentShop.ID,
nil,
)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("agent_child_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &childShop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "代理账号应能创建下级店铺的账号")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "业务码应为0")
}
// TestAccountPermission_12_5 代理账号创建其他店铺的账号失败
func TestAccountPermission_12_5(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop1 := env.CreateTestShop("独立店铺1", 1, nil)
shop2 := env.CreateTestShop("独立店铺2", 1, nil)
agentAccount := env.CreateTestAccount(
"agent_shop1",
"password123",
constants.UserTypeAgent,
&shop1.ID,
nil,
)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("agent_other_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop2.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode, "代理账号不应能创建其他店铺的账号")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Contains(t, result.Message, "权限", "错误消息应包含权限相关信息")
}
// TestAccountPermission_12_6 代理账号创建平台账号失败
func TestAccountPermission_12_6(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("代理店铺", 1, nil)
agentAccount := env.CreateTestAccount(
"agent_try_platform",
"password123",
constants.UserTypeAgent,
&shop.ID,
nil,
)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("platform_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypePlatform,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(agentAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode, "代理账号不应能创建平台账号")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Contains(t, result.Message, "权限", "错误消息应包含权限相关信息")
}
// TestAccountPermission_12_7 平台账号创建任意类型账号成功
func TestAccountPermission_12_7(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("平台测试店铺", 1, nil)
platformAccount := env.CreateTestAccount(
"platform_user",
"password123",
constants.UserTypePlatform,
nil,
nil,
)
t.Run("创建平台账号", func(t *testing.T) {
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("platform_new_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypePlatform,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(platformAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "平台账号应能创建平台账号")
})
t.Run("创建代理账号", func(t *testing.T) {
time.Sleep(time.Millisecond)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("agent_new_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(platformAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "平台账号应能创建代理账号")
})
}
// TestAccountPermission_12_8 超级管理员创建任意类型账号成功
func TestAccountPermission_12_8(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("超管测试店铺", 1, nil)
enterprise := env.CreateTestEnterprise("超管测试企业", &shop.ID)
t.Run("创建平台账号", func(t *testing.T) {
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("superadmin_platform_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypePlatform,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "超级管理员应能创建平台账号")
})
t.Run("创建代理账号", func(t *testing.T) {
time.Sleep(time.Millisecond)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("superadmin_agent_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "超级管理员应能创建代理账号")
})
t.Run("创建企业账号", func(t *testing.T) {
time.Sleep(time.Millisecond)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("superadmin_ent_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeEnterprise,
EnterpriseID: &enterprise.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "超级管理员应能创建企业账号")
})
}
// TestAccountPermission_12_9 查询不存在的账号返回统一错误
func TestAccountPermission_12_9(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("查询测试店铺", 1, nil)
agentAccount := env.CreateTestAccount(
"agent_query",
"password123",
constants.UserTypeAgent,
&shop.ID,
nil,
)
resp, err := env.AsUser(agentAccount).Request("GET", "/api/admin/accounts/99999", nil)
require.NoError(t, err)
// GORM 自动过滤后,查询不存在的账号返回 "账号不存在" (400)
// 或 "无权限操作该资源或资源不存在" (403)
assert.True(t,
resp.StatusCode == fiber.StatusBadRequest ||
resp.StatusCode == fiber.StatusForbidden ||
resp.StatusCode == fiber.StatusNotFound,
"查询不存在的账号应返回错误状态码")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.NotEqual(t, 0, result.Code, "业务码应不为0")
}
// TestAccountPermission_12_10 查询越权的账号返回相同错误消息
func TestAccountPermission_12_10(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop1 := env.CreateTestShop("越权测试店铺1", 1, nil)
shop2 := env.CreateTestShop("越权测试店铺2", 1, nil)
agentAccount1 := env.CreateTestAccount(
"agent_auth1",
"password123",
constants.UserTypeAgent,
&shop1.ID,
nil,
)
agentAccount2 := env.CreateTestAccount(
"agent_auth2",
"password123",
constants.UserTypeAgent,
&shop2.ID,
nil,
)
resp, err := env.AsUser(agentAccount1).Request(
"GET",
fmt.Sprintf("/api/admin/accounts/%d", agentAccount2.ID),
nil,
)
require.NoError(t, err)
// GORM 自动过滤使越权查询返回与不存在相同的错误,防止信息泄露
assert.True(t,
resp.StatusCode == fiber.StatusBadRequest ||
resp.StatusCode == fiber.StatusForbidden ||
resp.StatusCode == fiber.StatusNotFound,
"查询越权账号应返回错误状态码")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.NotEqual(t, 0, result.Code, "业务码应不为0")
}
// TestAccountPermission_12_11 代理账号更新其他店铺的账号失败
func TestAccountPermission_12_11(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop1 := env.CreateTestShop("更新测试店铺1", 1, nil)
shop2 := env.CreateTestShop("更新测试店铺2", 1, nil)
agentAccount1 := env.CreateTestAccount(
"agent_update1",
"password123",
constants.UserTypeAgent,
&shop1.ID,
nil,
)
agentAccount2 := env.CreateTestAccount(
"agent_update2",
"password123",
constants.UserTypeAgent,
&shop2.ID,
nil,
)
newUsername := "updated_username"
reqBody := dto.UpdateAccountRequest{
Username: &newUsername,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(agentAccount1).Request(
"PUT",
fmt.Sprintf("/api/admin/accounts/%d", agentAccount2.ID),
jsonBody,
)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode, "代理账号不应能更新其他店铺的账号")
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Contains(t, result.Message, "权限", "错误消息应包含权限相关信息")
}
// TestEnterpriseAccountRouteBlocking 测试企业账号访问各类型账号管理接口的路由层拦截
func TestEnterpriseAccountRouteBlocking(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("路由拦截测试店铺", 1, nil)
enterprise := env.CreateTestEnterprise("路由拦截测试企业", &shop.ID)
enterpriseAccount := env.CreateTestAccount(
"enterprise_route_test",
"password123",
constants.UserTypeEnterprise,
nil,
&enterprise.ID,
)
t.Run("企业账号访问企业账号列表接口被拦截", func(t *testing.T) {
resp, err := env.AsUser(enterpriseAccount).Request("GET", "/api/admin/accounts", nil)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode)
})
t.Run("企业账号访问企业账号详情接口被拦截", func(t *testing.T) {
resp, err := env.AsUser(enterpriseAccount).Request("GET", "/api/admin/accounts/1", nil)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode)
})
t.Run("企业账号访问创建企业账号接口被拦截", func(t *testing.T) {
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeEnterprise,
EnterpriseID: &enterprise.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(enterpriseAccount).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode)
})
}
// TestAgentAccountHierarchyPermission 测试代理账号的层级权限
func TestAgentAccountHierarchyPermission(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
level1Shop := env.CreateTestShop("一级店铺", 1, nil)
level2Shop := env.CreateTestShop("二级店铺", 2, &level1Shop.ID)
level3Shop := env.CreateTestShop("三级店铺", 3, &level2Shop.ID)
level2Agent := env.CreateTestAccount(
"level2_agent",
"password123",
constants.UserTypeAgent,
&level2Shop.ID,
nil,
)
t.Run("二级代理可以管理三级店铺账号", func(t *testing.T) {
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("level3_new_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &level3Shop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(level2Agent).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode, "二级代理应能管理三级店铺账号")
})
t.Run("二级代理不能管理一级店铺账号", func(t *testing.T) {
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("level1_new_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &level1Shop.ID,
}
jsonBody, _ := json.Marshal(reqBody)
resp, err := env.AsUser(level2Agent).Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode, "二级代理不应能管理一级店铺账号")
})
}