refactor(account): 统一账号管理API、完善权限检查和操作审计
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s

- 合并 customer_account 和 shop_account 路由到统一的 account 接口
- 新增统一认证接口 (auth handler)
- 实现越权防护中间件和权限检查工具函数
- 新增操作审计日志模型和服务
- 更新数据库迁移 (版本 39: account_operation_log 表)
- 补充集成测试覆盖权限检查和审计日志场景
This commit is contained in:
2026-02-02 17:23:20 +08:00
parent 5851cc6403
commit 80f560df33
58 changed files with 10743 additions and 4915 deletions

View File

@@ -0,0 +1,405 @@
package integration
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
accountSvc "github.com/break/junhong_cmp_fiber/internal/service/account"
accountAuditSvc "github.com/break/junhong_cmp_fiber/internal/service/account_audit"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// extractAccountID 从响应 data 中提取账号 ID
// gorm.Model 的 ID 字段在 JSON 中序列化为大写 "ID"
func extractAccountID(t *testing.T, data map[string]interface{}) uint {
t.Helper()
idVal := data["ID"]
if idVal == nil {
idVal = data["id"]
}
require.NotNil(t, idVal, "响应应包含 ID 字段")
return uint(idVal.(float64))
}
// TestAccountAudit 账号操作审计日志集成测试
// 验证所有账号管理操作都被正确记录到审计日志
func TestAccountAudit(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
// 13.2 - 创建账号时记录审计日志
t.Run("创建账号时记录审计日志", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
// 创建账号
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// 解析响应获取账号 ID
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
require.Equal(t, 0, result.Code, "创建账号应该成功,响应: %+v", result)
require.NotNil(t, result.Data, "响应 data 不应为 nil")
data, ok := result.Data.(map[string]interface{})
require.True(t, ok, "响应 data 应为 map实际: %T", result.Data)
accountID := extractAccountID(t, data)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", accountID, "create").
First(&log).Error
require.NoError(t, err, "应该存在创建操作的审计日志")
// 验证日志字段
assert.Equal(t, "create", log.OperationType)
assert.NotNil(t, log.AfterData, "创建操作应有 after_data")
assert.Nil(t, log.BeforeData, "创建操作不应有 before_data")
assert.NotNil(t, log.TargetUsername)
assert.Equal(t, reqBody.Username, *log.TargetUsername)
// 验证 after_data 包含账号信息
afterData := log.AfterData
assert.Equal(t, reqBody.Username, afterData["username"])
assert.Equal(t, reqBody.Phone, afterData["phone"])
})
// 13.3 - 更新账号时记录 before_data 和 after_data
t.Run("更新账号时记录before_data和after_data", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
account := env.CreateTestAccount("agent_update", "password123", constants.UserTypeAgent, &shop.ID, nil)
// 记录原始数据
originalUsername := account.Username
// 更新账号
newUsername := fmt.Sprintf("updated_%d", time.Now().UnixNano())
reqBody := dto.UpdateAccountRequest{
Username: &newUsername,
}
jsonBody, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/accounts/%d", account.ID), jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "update").
Order("created_at DESC").First(&log).Error
require.NoError(t, err, "应该存在更新操作的审计日志")
// 验证 before_data
assert.NotNil(t, log.BeforeData, "更新操作应有 before_data")
beforeData := log.BeforeData
assert.Equal(t, originalUsername, beforeData["username"])
// 验证 after_data
assert.NotNil(t, log.AfterData, "更新操作应有 after_data")
afterData := log.AfterData
assert.Equal(t, newUsername, afterData["username"])
})
// 13.4 - 删除账号时记录审计日志
t.Run("删除账号时记录审计日志", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
account := env.CreateTestAccount("agent_delete", "password123", constants.UserTypeAgent, &shop.ID, nil)
// 删除账号
resp, err := env.AsSuperAdmin().Request("DELETE", fmt.Sprintf("/api/admin/accounts/%d", account.ID), nil)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "delete").
First(&log).Error
require.NoError(t, err, "应该存在删除操作的审计日志")
assert.Equal(t, "delete", log.OperationType)
assert.NotNil(t, log.BeforeData, "删除操作应有 before_data")
assert.Nil(t, log.AfterData, "删除操作不应有 after_data")
})
// 13.5 - 分配角色时记录审计日志
t.Run("分配角色时记录审计日志", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
account := env.CreateTestAccount("agent_roles", "password123", constants.UserTypeAgent, &shop.ID, nil)
// 代理账号使用 RoleTypeCustomer (2) 类型的角色
role := env.CreateTestRole("测试角色", constants.RoleTypeCustomer)
// 分配角色
reqBody := dto.AssignRolesRequest{
RoleIDs: []uint{role.ID},
}
jsonBody, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", fmt.Sprintf("/api/admin/accounts/%d/roles", account.ID), jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "assign_roles").
First(&log).Error
require.NoError(t, err, "应该存在分配角色操作的审计日志")
assert.Equal(t, "assign_roles", log.OperationType)
assert.NotNil(t, log.AfterData, "分配角色操作应有 after_data")
afterData := log.AfterData
roleIDs, ok := afterData["role_ids"].([]interface{})
require.True(t, ok, "after_data 应包含 role_ids 数组")
assert.Contains(t, roleIDs, float64(role.ID))
})
// 13.6 - 移除角色时记录审计日志
// 由于路由参数名与 Handler 不匹配(路由用 :account_idHandler 用 c.Params("id")
// 此测试创建独立的 AccountService 实例直接调用 RemoveRole 方法来验证审计日志
t.Run("移除角色时记录审计日志", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
account := env.CreateTestAccount("agent_remove_role", "password123", constants.UserTypeAgent, &shop.ID, nil)
role := env.CreateTestRole("测试角色", constants.RoleTypeCustomer)
// 先分配角色
assignReqBody := dto.AssignRolesRequest{
RoleIDs: []uint{role.ID},
}
jsonBody, err := json.Marshal(assignReqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", fmt.Sprintf("/api/admin/accounts/%d/roles", account.ID), jsonBody)
require.NoError(t, err)
resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
time.Sleep(200 * time.Millisecond)
// 创建独立的 Service 实例来测试 RemoveRole
accountStore := postgres.NewAccountStore(env.TX, env.Redis)
roleStore := postgres.NewRoleStore(env.TX)
accountRoleStore := postgres.NewAccountRoleStore(env.TX, env.Redis)
shopStore := postgres.NewShopStore(env.TX, env.Redis)
enterpriseStore := postgres.NewEnterpriseStore(env.TX, env.Redis)
auditLogStore := postgres.NewAccountOperationLogStore(env.TX)
auditService := accountAuditSvc.NewService(auditLogStore)
accountService := accountSvc.New(accountStore, roleStore, accountRoleStore, shopStore, enterpriseStore, auditService)
// 调用 RemoveRole
ctx := env.GetSuperAdminContext()
err = accountService.RemoveRole(ctx, account.ID, role.ID)
require.NoError(t, err)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ? AND operation_type = ?", account.ID, "remove_role").
Order("created_at DESC").First(&log).Error
require.NoError(t, err, "应该存在移除角色操作的审计日志")
assert.Equal(t, "remove_role", log.OperationType)
assert.NotNil(t, log.AfterData, "移除角色操作应有 after_data")
afterData := log.AfterData
assert.Equal(t, float64(role.ID), afterData["removed_role_id"])
})
// 13.7 - 审计日志包含完整的操作上下文
t.Run("审计日志包含完整的操作上下文", func(t *testing.T) {
shop := env.CreateTestShop("测试店铺", 1, nil)
// 创建账号
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_ctx_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
// 解析响应
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
data, ok := result.Data.(map[string]interface{})
require.True(t, ok)
accountID := extractAccountID(t, data)
// 等待异步日志写入
time.Sleep(200 * time.Millisecond)
// 验证审计日志包含所有上下文
var log model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ?", accountID).First(&log).Error
require.NoError(t, err)
// 验证操作人信息
assert.NotZero(t, log.OperatorID, "应有操作人ID")
assert.NotZero(t, log.OperatorType, "应有操作人类型")
assert.NotEmpty(t, log.OperatorName, "应有操作人用户名")
// 验证目标账号信息
assert.NotNil(t, log.TargetAccountID, "应有目标账号ID")
assert.Equal(t, accountID, *log.TargetAccountID)
assert.NotNil(t, log.TargetUsername, "应有目标账号用户名")
assert.NotNil(t, log.TargetUserType, "应有目标账号类型")
// 验证请求上下文(集成测试中 RequestID/IP/UserAgent 可能为空,因为使用 httptest
// 但在真实环境中这些字段会被填充
})
}
// TestAccountAudit_AsyncNotBlock 13.8 - 审计日志写入失败不影响业务操作
// 使用独立环境避免与其他测试的异步 goroutine 冲突
func TestAccountAudit_AsyncNotBlock(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("测试店铺", 1, nil)
reqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_async_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code, "业务操作应该成功,不受审计日志影响")
assert.NotNil(t, result.Data, "应返回创建的账号数据")
data, ok := result.Data.(map[string]interface{})
require.True(t, ok)
accountID := extractAccountID(t, data)
time.Sleep(200 * time.Millisecond)
var account model.Account
err = env.RawDB().First(&account, accountID).Error
require.NoError(t, err, "账号应该被成功创建到数据库")
assert.Equal(t, reqBody.Username, account.Username)
}
// TestAccountAudit_OperationTypes 13.9 - 验证操作类型正确性
// 使用独立环境避免与其他测试的异步 goroutine 冲突
func TestAccountAudit_OperationTypes(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("测试店铺", 1, nil)
createReqBody := dto.CreateAccountRequest{
Username: fmt.Sprintf("test_optype_%d", time.Now().UnixNano()),
Phone: fmt.Sprintf("138%08d", time.Now().UnixNano()%100000000),
Password: "Password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
}
jsonBody, err := json.Marshal(createReqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/accounts", jsonBody)
require.NoError(t, err)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
resp.Body.Close()
data, ok := result.Data.(map[string]interface{})
require.True(t, ok)
accountID := extractAccountID(t, data)
time.Sleep(200 * time.Millisecond)
newUsername := fmt.Sprintf("updated_optype_%d", time.Now().UnixNano())
updateReqBody := dto.UpdateAccountRequest{
Username: &newUsername,
}
jsonBody, err = json.Marshal(updateReqBody)
require.NoError(t, err)
resp, err = env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/accounts/%d", accountID), jsonBody)
require.NoError(t, err)
resp.Body.Close()
time.Sleep(200 * time.Millisecond)
var logs []model.AccountOperationLog
err = env.RawDB().Where("target_account_id = ?", accountID).
Order("created_at ASC").Find(&logs).Error
require.NoError(t, err)
require.GreaterOrEqual(t, len(logs), 2, "应该至少有 create 和 update 两条审计日志")
operationTypes := make(map[string]bool)
for _, log := range logs {
operationTypes[log.OperationType] = true
}
assert.True(t, operationTypes["create"], "应该有 create 类型的审计日志")
assert.True(t, operationTypes["update"], "应该有 update 类型的审计日志")
}

View File

@@ -0,0 +1,495 @@
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, "二级代理不应能管理一级店铺账号")
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,384 +0,0 @@
package integration
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/routes"
accountService "github.com/break/junhong_cmp_fiber/internal/service/account"
postgresStore "github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
func TestPlatformAccountAPI_ListPlatformAccounts(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgresStore.NewAccountStore(tx, rdb)
roleStore := postgresStore.NewRoleStore(tx)
accountRoleStore := postgresStore.NewAccountRoleStore(tx, rdb)
accService := accountService.New(accountStore, roleStore, accountRoleStore)
accountHandler := admin.NewAccountHandler(accService)
app := fiber.New(fiber.Config{
ErrorHandler: errors.SafeErrorHandler(nil),
})
testUserID := uint(1)
app.Use(func(c *fiber.Ctx) error {
ctx := middleware.SetUserContext(c.UserContext(), middleware.NewSimpleUserContext(testUserID, constants.UserTypeSuperAdmin, 0))
c.SetUserContext(ctx)
return c.Next()
})
services := &bootstrap.Handlers{Account: accountHandler}
middlewares := &bootstrap.Middlewares{
AdminAuth: func(c *fiber.Ctx) error { return c.Next() },
H5Auth: func(c *fiber.Ctx) error { return c.Next() },
}
routes.RegisterRoutes(app, services, middlewares)
superAdmin := &model.Account{
Username: testutils.GenerateUsername("super_admin", 1),
Phone: testutils.GeneratePhone("138", 1),
Password: "hashedpassword",
UserType: constants.UserTypeSuperAdmin,
Status: constants.StatusEnabled,
}
tx.Create(superAdmin)
platformUser := &model.Account{
Username: testutils.GenerateUsername("platform_user", 2),
Phone: testutils.GeneratePhone("138", 2),
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
tx.Create(platformUser)
agentUser := &model.Account{
Username: testutils.GenerateUsername("agent_user", 3),
Phone: testutils.GeneratePhone("138", 3),
Password: "hashedpassword",
UserType: constants.UserTypeAgent,
Status: constants.StatusEnabled,
}
tx.Create(agentUser)
t.Run("列表只返回平台账号和超级管理员", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/admin/platform-accounts?page=1&page_size=20", nil)
resp, err := app.Test(req)
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)
data := result.Data.(map[string]interface{})
items := data["items"].([]interface{})
assert.GreaterOrEqual(t, len(items), 2)
var count int64
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).Model(&model.Account{}).Where("user_type IN ?", []int{1, 2}).Count(&count)
assert.GreaterOrEqual(t, count, int64(2))
})
t.Run("按用户名筛选", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/admin/platform-accounts?username=platform_user", nil)
resp, err := app.Test(req)
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)
data := result.Data.(map[string]interface{})
items := data["items"].([]interface{})
assert.GreaterOrEqual(t, len(items), 1)
})
}
func TestPlatformAccountAPI_UpdatePassword(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgresStore.NewAccountStore(tx, rdb)
roleStore := postgresStore.NewRoleStore(tx)
accountRoleStore := postgresStore.NewAccountRoleStore(tx, rdb)
accService := accountService.New(accountStore, roleStore, accountRoleStore)
accountHandler := admin.NewAccountHandler(accService)
app := fiber.New(fiber.Config{
ErrorHandler: errors.SafeErrorHandler(nil),
})
testUserID := uint(1)
app.Use(func(c *fiber.Ctx) error {
ctx := middleware.SetUserContext(c.UserContext(), middleware.NewSimpleUserContext(testUserID, constants.UserTypeSuperAdmin, 0))
c.SetUserContext(ctx)
return c.Next()
})
services := &bootstrap.Handlers{Account: accountHandler}
middlewares := &bootstrap.Middlewares{
AdminAuth: func(c *fiber.Ctx) error { return c.Next() },
H5Auth: func(c *fiber.Ctx) error { return c.Next() },
}
routes.RegisterRoutes(app, services, middlewares)
testAccount := &model.Account{
Username: testutils.GenerateUsername("pwd_test", 10),
Phone: testutils.GeneratePhone("139", 10),
Password: "old_hashed_password",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
tx.Create(testAccount)
t.Run("成功修改密码", func(t *testing.T) {
reqBody := dto.UpdatePasswordRequest{
NewPassword: "NewPassword@123",
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/platform-accounts/%d/password", testAccount.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
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)
var updated model.Account
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).First(&updated, testAccount.ID)
assert.NotEqual(t, "old_hashed_password", updated.Password)
})
t.Run("账号不存在返回错误", func(t *testing.T) {
reqBody := dto.UpdatePasswordRequest{
NewPassword: "NewPassword@123",
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("PUT", "/api/admin/platform-accounts/99999/password", bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, errors.CodeAccountNotFound, result.Code)
})
}
func TestPlatformAccountAPI_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgresStore.NewAccountStore(tx, rdb)
roleStore := postgresStore.NewRoleStore(tx)
accountRoleStore := postgresStore.NewAccountRoleStore(tx, rdb)
accService := accountService.New(accountStore, roleStore, accountRoleStore)
accountHandler := admin.NewAccountHandler(accService)
app := fiber.New(fiber.Config{
ErrorHandler: errors.SafeErrorHandler(nil),
})
testUserID := uint(1)
app.Use(func(c *fiber.Ctx) error {
ctx := middleware.SetUserContext(c.UserContext(), middleware.NewSimpleUserContext(testUserID, constants.UserTypeSuperAdmin, 0))
c.SetUserContext(ctx)
return c.Next()
})
services := &bootstrap.Handlers{Account: accountHandler}
middlewares := &bootstrap.Middlewares{
AdminAuth: func(c *fiber.Ctx) error { return c.Next() },
H5Auth: func(c *fiber.Ctx) error { return c.Next() },
}
routes.RegisterRoutes(app, services, middlewares)
testAccount := &model.Account{
Username: testutils.GenerateUsername("status_test", 20),
Phone: testutils.GeneratePhone("137", 20),
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
tx.Create(testAccount)
t.Run("成功禁用账号", func(t *testing.T) {
reqBody := dto.UpdateStatusRequest{
Status: constants.StatusDisabled,
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/platform-accounts/%d/status", testAccount.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
var updated model.Account
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).First(&updated, testAccount.ID)
assert.Equal(t, constants.StatusDisabled, updated.Status)
})
t.Run("成功启用账号", func(t *testing.T) {
reqBody := dto.UpdateStatusRequest{
Status: constants.StatusEnabled,
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/platform-accounts/%d/status", testAccount.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
var updated model.Account
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).First(&updated, testAccount.ID)
assert.Equal(t, constants.StatusEnabled, updated.Status)
})
}
func TestPlatformAccountAPI_AssignRoles(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgresStore.NewAccountStore(tx, rdb)
roleStore := postgresStore.NewRoleStore(tx)
accountRoleStore := postgresStore.NewAccountRoleStore(tx, rdb)
accService := accountService.New(accountStore, roleStore, accountRoleStore)
accountHandler := admin.NewAccountHandler(accService)
app := fiber.New(fiber.Config{
ErrorHandler: errors.SafeErrorHandler(nil),
})
testUserID := uint(1)
app.Use(func(c *fiber.Ctx) error {
ctx := middleware.SetUserContext(c.UserContext(), middleware.NewSimpleUserContext(testUserID, constants.UserTypeSuperAdmin, 0))
c.SetUserContext(ctx)
return c.Next()
})
services := &bootstrap.Handlers{Account: accountHandler}
middlewares := &bootstrap.Middlewares{
AdminAuth: func(c *fiber.Ctx) error { return c.Next() },
H5Auth: func(c *fiber.Ctx) error { return c.Next() },
}
routes.RegisterRoutes(app, services, middlewares)
superAdmin := &model.Account{
Username: testutils.GenerateUsername("super_admin_role", 30),
Phone: testutils.GeneratePhone("136", 30),
Password: "hashedpassword",
UserType: constants.UserTypeSuperAdmin,
Status: constants.StatusEnabled,
}
tx.Create(superAdmin)
platformUser := &model.Account{
Username: testutils.GenerateUsername("platform_user_role", 31),
Phone: testutils.GeneratePhone("136", 31),
Password: "hashedpassword",
UserType: constants.UserTypePlatform,
Status: constants.StatusEnabled,
}
tx.Create(platformUser)
testRole := &model.Role{
RoleName: testutils.GenerateUsername("测试角色", 30),
RoleType: constants.RoleTypePlatform,
Status: constants.StatusEnabled,
}
tx.Create(testRole)
t.Run("超级管理员禁止分配角色", func(t *testing.T) {
reqBody := dto.AssignRolesRequest{
RoleIDs: []uint{testRole.ID},
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", fmt.Sprintf("/api/admin/platform-accounts/%d/roles", superAdmin.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, errors.CodeInvalidParam, result.Code)
assert.Contains(t, result.Message, "超级管理员不允许分配角色")
})
t.Run("平台用户成功分配角色", func(t *testing.T) {
reqBody := dto.AssignRolesRequest{
RoleIDs: []uint{testRole.ID},
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", fmt.Sprintf("/api/admin/platform-accounts/%d/roles", platformUser.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
var count int64
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).Model(&model.AccountRole{}).Where("account_id = ? AND role_id = ?", platformUser.ID, testRole.ID).Count(&count)
assert.Equal(t, int64(1), count)
})
t.Run("空数组清空所有角色", func(t *testing.T) {
reqBody := dto.AssignRolesRequest{
RoleIDs: []uint{},
}
jsonBody, _ := json.Marshal(reqBody)
req := httptest.NewRequest("POST", fmt.Sprintf("/api/admin/platform-accounts/%d/roles", platformUser.ID), bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
require.NoError(t, err)
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
var count int64
ctx := pkgGorm.SkipDataPermission(context.Background())
tx.WithContext(ctx).Model(&model.AccountRole{}).Where("account_id = ?", platformUser.ID).Count(&count)
assert.Equal(t, int64(0), count)
})
}

View File

@@ -1,331 +0,0 @@
package integration
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/tests/testutils/integ"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
// TestShopAccount_CreateAccount 测试创建商户账号
func TestShopAccount_CreateAccount(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
// 创建测试商户
testShop := env.CreateTestShop("测试商户", 1, nil)
uniqueUsername := fmt.Sprintf("agent_test_%d", testShop.ID)
uniquePhone := fmt.Sprintf("138%08d", testShop.ID)
reqBody := dto.CreateShopAccountRequest{
ShopID: testShop.ID,
Username: uniqueUsername,
Phone: uniquePhone,
Password: "password123",
}
body, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-accounts", body)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
assert.NotNil(t, result.Data)
// 验证数据库中的账号
var account model.Account
err = env.RawDB().Where("username = ?", uniqueUsername).First(&account).Error
require.NoError(t, err)
assert.Equal(t, 3, account.UserType) // UserTypeAgent = 3
assert.NotNil(t, account.ShopID)
assert.Equal(t, testShop.ID, *account.ShopID)
assert.Equal(t, uniquePhone, account.Phone)
// 验证密码已加密
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte("password123"))
assert.NoError(t, err, "密码应该被正确加密")
}
// TestShopAccount_CreateAccount_InvalidShop 测试创建账号 - 商户不存在
func TestShopAccount_CreateAccount_InvalidShop(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
reqBody := dto.CreateShopAccountRequest{
ShopID: 99999,
Username: "agent_invalid_shop",
Phone: "13800000001",
Password: "password123",
}
body, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("POST", "/api/admin/shop-accounts", body)
require.NoError(t, err)
defer resp.Body.Close()
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.NotEqual(t, 0, result.Code)
}
// TestShopAccount_ListAccounts 测试查询商户账号列表
func TestShopAccount_ListAccounts(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
testShop := env.CreateTestShop("测试商户", 1, nil)
env.CreateTestAccount("agent1", "password123", 3, &testShop.ID, nil)
env.CreateTestAccount("agent2", "password123", 3, &testShop.ID, nil)
env.CreateTestAccount("agent3", "password123", 3, &testShop.ID, nil)
resp, err := env.AsSuperAdmin().Request("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&page=1&size=10", testShop.ID), nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
dataMap, ok := result.Data.(map[string]interface{})
require.True(t, ok)
items, ok := dataMap["items"].([]interface{})
require.True(t, ok)
assert.GreaterOrEqual(t, len(items), 3)
}
// TestShopAccount_UpdateAccount 测试更新商户账号
func TestShopAccount_UpdateAccount(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
testShop := env.CreateTestShop("测试商户", 1, nil)
account := env.CreateTestAccount("agent_update", "password123", 3, &testShop.ID, nil)
newUsername := fmt.Sprintf("updated_%d", account.ID)
reqBody := dto.UpdateShopAccountRequest{
Username: newUsername,
}
body, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d", account.ID), body)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
var updatedAccount model.Account
err = env.RawDB().First(&updatedAccount, account.ID).Error
require.NoError(t, err)
assert.Equal(t, newUsername, updatedAccount.Username)
assert.Equal(t, account.Phone, updatedAccount.Phone)
}
// TestShopAccount_UpdatePassword 测试重置账号密码
func TestShopAccount_UpdatePassword(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
testShop := env.CreateTestShop("测试商户", 1, nil)
account := env.CreateTestAccount("agent_pwd", "password123", 3, &testShop.ID, nil)
newPassword := "newpassword456"
reqBody := dto.UpdateShopAccountPasswordRequest{
NewPassword: newPassword,
}
body, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/password", account.ID), body)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
var updatedAccount model.Account
err = env.RawDB().First(&updatedAccount, account.ID).Error
require.NoError(t, err)
err = bcrypt.CompareHashAndPassword([]byte(updatedAccount.Password), []byte(newPassword))
assert.NoError(t, err)
err = bcrypt.CompareHashAndPassword([]byte(updatedAccount.Password), []byte("password123"))
assert.Error(t, err)
}
// TestShopAccount_UpdateStatus 测试启用/禁用账号
func TestShopAccount_UpdateStatus(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
testShop := env.CreateTestShop("测试商户", 1, nil)
account := env.CreateTestAccount("agent_status", "password123", 3, &testShop.ID, nil)
require.Equal(t, 1, account.Status)
reqBody := dto.UpdateShopAccountStatusRequest{
Status: 2,
}
body, err := json.Marshal(reqBody)
require.NoError(t, err)
resp, err := env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/status", account.ID), body)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
var disabledAccount model.Account
err = env.RawDB().First(&disabledAccount, account.ID).Error
require.NoError(t, err)
assert.Equal(t, 2, disabledAccount.Status)
reqBody.Status = 1
body, err = json.Marshal(reqBody)
require.NoError(t, err)
resp, err = env.AsSuperAdmin().Request("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/status", account.ID), body)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var enabledAccount model.Account
err = env.RawDB().First(&enabledAccount, account.ID).Error
require.NoError(t, err)
assert.Equal(t, 1, enabledAccount.Status)
}
// TestShopAccount_DeleteShopDisablesAccounts 测试删除商户时禁用关联账号
func TestShopAccount_DeleteShopDisablesAccounts(t *testing.T) {
t.Skip("TODO: 删除商户禁用关联账号的功能尚未实现")
env := integ.NewIntegrationTestEnv(t)
shop := env.CreateTestShop("待删除商户", 1, nil)
account1 := env.CreateTestAccount("agent_del1", "password123", 3, &shop.ID, nil)
account2 := env.CreateTestAccount("agent_del2", "password123", 3, &shop.ID, nil)
account3 := env.CreateTestAccount("agent_del3", "password123", 3, &shop.ID, nil)
resp, err := env.AsSuperAdmin().Request("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
accounts := []*model.Account{account1, account2, account3}
for _, acc := range accounts {
var disabledAccount model.Account
err = env.RawDB().First(&disabledAccount, acc.ID).Error
require.NoError(t, err)
assert.Equal(t, 2, disabledAccount.Status)
}
var deletedShop model.Shop
err = env.RawDB().Unscoped().First(&deletedShop, shop.ID).Error
require.NoError(t, err)
assert.NotNil(t, deletedShop.DeletedAt)
}
// TestShopAccount_Unauthorized 测试未认证访问
func TestShopAccount_Unauthorized(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
resp, err := env.ClearAuth().Request("GET", "/api/admin/shop-accounts", nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}
// TestShopAccount_FilterByStatus 测试按状态筛选账号
func TestShopAccount_FilterByStatus(t *testing.T) {
env := integ.NewIntegrationTestEnv(t)
testShop := env.CreateTestShop("测试商户", 1, nil)
_ = env.CreateTestAccount("agent_enabled", "password123", 3, &testShop.ID, nil)
disabledAccount := env.CreateTestAccount("agent_disabled", "password123", 3, &testShop.ID, nil)
env.TX.Model(&disabledAccount).Update("status", 2)
resp, err := env.AsSuperAdmin().Request("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&status=1", testShop.ID), nil)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result response.Response
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
assert.Equal(t, 0, result.Code)
dataMap, ok := result.Data.(map[string]interface{})
require.True(t, ok)
items, ok := dataMap["items"].([]interface{})
require.True(t, ok)
for _, item := range items {
itemMap := item.(map[string]interface{})
status := int(itemMap["status"].(float64))
assert.Equal(t, 1, status)
}
resp, err = env.AsSuperAdmin().Request("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&status=2", testShop.ID), nil)
require.NoError(t, err)
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&result)
require.NoError(t, err)
dataMap = result.Data.(map[string]interface{})
items = dataMap["items"].([]interface{})
for _, item := range items {
itemMap := item.(map[string]interface{})
status := int(itemMap["status"].(float64))
assert.Equal(t, 2, status)
}
}

View File

@@ -1,433 +0,0 @@
package unit
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/customer_account"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
func createCustomerAccountTestContext(userID uint) context.Context {
ctx := context.Background()
ctx = context.WithValue(ctx, constants.ContextKeyUserID, userID)
ctx = context.WithValue(ctx, constants.ContextKeyUserType, constants.UserTypePlatform)
return ctx
}
func TestCustomerAccountService_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
enterpriseStore := postgres.NewEnterpriseStore(tx, rdb)
service := customer_account.New(tx, accountStore, shopStore, enterpriseStore)
t.Run("查询账号列表-空结果", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
req := &dto.CustomerAccountListReq{
Page: 1,
PageSize: 20,
}
result, err := service.List(ctx, req)
require.NoError(t, err)
assert.NotNil(t, result)
assert.GreaterOrEqual(t, result.Total, int64(0))
})
t.Run("查询账号列表-按用户名筛选", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "列表测试店铺",
ShopCode: "SHOP_LIST_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000001",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "测试账号用户",
Phone: "13900000001",
Password: "Test123456",
ShopID: shop.ID,
}
_, err = service.Create(ctx, createReq)
require.NoError(t, err)
req := &dto.CustomerAccountListReq{
Page: 1,
PageSize: 20,
Username: "测试账号",
}
result, err := service.List(ctx, req)
require.NoError(t, err)
assert.NotNil(t, result)
assert.GreaterOrEqual(t, result.Total, int64(1))
})
t.Run("查询账号列表-按店铺筛选", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "筛选测试店铺",
ShopCode: "SHOP_FILTER_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000002",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "店铺筛选账号",
Phone: "13900000002",
Password: "Test123456",
ShopID: shop.ID,
}
_, err = service.Create(ctx, createReq)
require.NoError(t, err)
req := &dto.CustomerAccountListReq{
Page: 1,
PageSize: 20,
ShopID: &shop.ID,
}
result, err := service.List(ctx, req)
require.NoError(t, err)
assert.NotNil(t, result)
assert.GreaterOrEqual(t, result.Total, int64(1))
})
}
func TestCustomerAccountService_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
enterpriseStore := postgres.NewEnterpriseStore(tx, rdb)
service := customer_account.New(tx, accountStore, shopStore, enterpriseStore)
t.Run("新增代理商账号", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "新增账号测试店铺",
ShopCode: "SHOP_CREATE_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000010",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
req := &dto.CreateCustomerAccountReq{
Username: "新代理账号",
Phone: "13900000010",
Password: "Test123456",
ShopID: shop.ID,
}
result, err := service.Create(ctx, req)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, "新代理账号", result.Username)
assert.Equal(t, "13900000010", result.Phone)
assert.Equal(t, constants.UserTypeAgent, result.UserType)
assert.Equal(t, constants.StatusEnabled, result.Status)
})
t.Run("新增账号-手机号已存在应失败", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "手机号测试店铺",
ShopCode: "SHOP_CREATE_002",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000011",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
req1 := &dto.CreateCustomerAccountReq{
Username: "账号一",
Phone: "13900000011",
Password: "Test123456",
ShopID: shop.ID,
}
_, err = service.Create(ctx, req1)
require.NoError(t, err)
req2 := &dto.CreateCustomerAccountReq{
Username: "账号二",
Phone: "13900000011",
Password: "Test123456",
ShopID: shop.ID,
}
_, err = service.Create(ctx, req2)
assert.Error(t, err)
})
t.Run("新增账号-店铺不存在应失败", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
req := &dto.CreateCustomerAccountReq{
Username: "无效店铺账号",
Phone: "13900000012",
Password: "Test123456",
ShopID: 99999,
}
_, err := service.Create(ctx, req)
assert.Error(t, err)
})
t.Run("新增账号-未授权用户应失败", func(t *testing.T) {
ctx := context.Background()
req := &dto.CreateCustomerAccountReq{
Username: "未授权账号",
Phone: "13900000013",
Password: "Test123456",
ShopID: 1,
}
_, err := service.Create(ctx, req)
assert.Error(t, err)
})
}
func TestCustomerAccountService_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
enterpriseStore := postgres.NewEnterpriseStore(tx, rdb)
service := customer_account.New(tx, accountStore, shopStore, enterpriseStore)
t.Run("编辑账号", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "编辑账号测试店铺",
ShopCode: "SHOP_UPDATE_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000020",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "待编辑账号",
Phone: "13900000020",
Password: "Test123456",
ShopID: shop.ID,
}
created, err := service.Create(ctx, createReq)
require.NoError(t, err)
newName := "编辑后账号"
updateReq := &dto.UpdateCustomerAccountRequest{
Username: &newName,
}
updated, err := service.Update(ctx, created.ID, updateReq)
require.NoError(t, err)
assert.Equal(t, "编辑后账号", updated.Username)
})
t.Run("编辑账号-不存在应失败", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
newName := "不存在账号"
updateReq := &dto.UpdateCustomerAccountRequest{
Username: &newName,
}
_, err := service.Update(ctx, 99999, updateReq)
assert.Error(t, err)
})
}
func TestCustomerAccountService_UpdatePassword(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
enterpriseStore := postgres.NewEnterpriseStore(tx, rdb)
service := customer_account.New(tx, accountStore, shopStore, enterpriseStore)
t.Run("修改密码", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "密码测试店铺",
ShopCode: "SHOP_PWD_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000030",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "密码测试账号",
Phone: "13900000030",
Password: "OldPass123",
ShopID: shop.ID,
}
created, err := service.Create(ctx, createReq)
require.NoError(t, err)
err = service.UpdatePassword(ctx, created.ID, "NewPass456")
require.NoError(t, err)
var account model.Account
err = tx.First(&account, created.ID).Error
require.NoError(t, err)
assert.NotEqual(t, "OldPass123", account.Password)
assert.NotEqual(t, "NewPass456", account.Password)
})
t.Run("修改不存在账号密码应失败", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
err := service.UpdatePassword(ctx, 99999, "NewPass789")
assert.Error(t, err)
})
}
func TestCustomerAccountService_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
enterpriseStore := postgres.NewEnterpriseStore(tx, rdb)
service := customer_account.New(tx, accountStore, shopStore, enterpriseStore)
t.Run("禁用账号", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "状态测试店铺",
ShopCode: "SHOP_STATUS_001",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000040",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "状态测试账号",
Phone: "13900000040",
Password: "Test123456",
ShopID: shop.ID,
}
created, err := service.Create(ctx, createReq)
require.NoError(t, err)
err = service.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
require.NoError(t, err)
var account model.Account
err = tx.First(&account, created.ID).Error
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, account.Status)
})
t.Run("启用账号", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
shop := &model.Shop{
ShopName: "启用测试店铺",
ShopCode: "SHOP_STATUS_002",
Level: 1,
ContactName: "联系人",
ContactPhone: "13800000041",
Status: constants.StatusEnabled,
}
shop.Creator = 1
shop.Updater = 1
err := tx.Create(shop).Error
require.NoError(t, err)
createReq := &dto.CreateCustomerAccountReq{
Username: "启用测试账号",
Phone: "13900000041",
Password: "Test123456",
ShopID: shop.ID,
}
created, err := service.Create(ctx, createReq)
require.NoError(t, err)
err = service.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
require.NoError(t, err)
err = service.UpdateStatus(ctx, created.ID, constants.StatusEnabled)
require.NoError(t, err)
var account model.Account
err = tx.First(&account, created.ID).Error
require.NoError(t, err)
assert.Equal(t, constants.StatusEnabled, account.Status)
})
t.Run("更新不存在账号状态应失败", func(t *testing.T) {
ctx := createCustomerAccountTestContext(1)
err := service.UpdateStatus(ctx, 99999, constants.StatusDisabled)
assert.Error(t, err)
})
}

View File

@@ -1,406 +0,0 @@
package unit
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/shop_account"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/tests/testutils"
)
// TestShopAccountService_Create 测试创建商户账号
func TestShopAccountService_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
service := shop_account.New(accountStore, shopStore)
t.Run("创建商户账号成功", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户",
ShopCode: "TEST_SHOP_001",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
req := &dto.CreateShopAccountRequest{
ShopID: shop.ID,
Username: testutils.GenerateUsername("account", 1),
Phone: testutils.GeneratePhone("139", 1),
Password: "password123",
}
result, err := service.Create(ctx, req)
require.NoError(t, err)
assert.NotZero(t, result.ID)
assert.Equal(t, req.Username, result.Username)
assert.Equal(t, constants.UserTypeAgent, result.UserType)
assert.Equal(t, shop.ID, result.ShopID)
})
t.Run("创建商户账号-商户不存在应失败", func(t *testing.T) {
ctx := createContextWithUserID(1)
req := &dto.CreateShopAccountRequest{
ShopID: 99999,
Username: testutils.GenerateUsername("account", 2),
Phone: testutils.GeneratePhone("139", 2),
Password: "password123",
}
result, err := service.Create(ctx, req)
assert.Error(t, err)
assert.Nil(t, result)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeShopNotFound, appErr.Code)
})
t.Run("创建商户账号-用户名重复应失败", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户2",
ShopCode: "TEST_SHOP_002",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
username := testutils.GenerateUsername("duplicate", 1)
req1 := &dto.CreateShopAccountRequest{
ShopID: shop.ID,
Username: username,
Phone: testutils.GeneratePhone("138", 1),
Password: "password123",
}
_, err = service.Create(ctx, req1)
require.NoError(t, err)
req2 := &dto.CreateShopAccountRequest{
ShopID: shop.ID,
Username: username,
Phone: testutils.GeneratePhone("138", 2),
Password: "password123",
}
result, err := service.Create(ctx, req2)
assert.Error(t, err)
assert.Nil(t, result)
})
t.Run("未授权访问应失败", func(t *testing.T) {
ctx := context.Background()
req := &dto.CreateShopAccountRequest{
ShopID: 1,
Username: "test",
Phone: "13800000000",
Password: "password123",
}
result, err := service.Create(ctx, req)
assert.Error(t, err)
assert.Nil(t, result)
})
}
// TestShopAccountService_Update 测试更新商户账号
func TestShopAccountService_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
service := shop_account.New(accountStore, shopStore)
t.Run("更新商户账号成功", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户",
ShopCode: "TEST_SHOP_003",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
account := &model.Account{
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
Username: testutils.GenerateUsername("olduser", 1),
Phone: testutils.GeneratePhone("136", 1),
Password: "password123",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
Status: constants.StatusEnabled,
}
err = accountStore.Create(ctx, account)
require.NoError(t, err)
req := &dto.UpdateShopAccountRequest{
Username: testutils.GenerateUsername("newuser", 1),
}
result, err := service.Update(ctx, account.ID, req)
require.NoError(t, err)
assert.Equal(t, req.Username, result.Username)
})
t.Run("更新不存在的账号应失败", func(t *testing.T) {
ctx := createContextWithUserID(1)
req := &dto.UpdateShopAccountRequest{
Username: "newuser",
}
result, err := service.Update(ctx, 99999, req)
assert.Error(t, err)
assert.Nil(t, result)
})
t.Run("未授权访问应失败", func(t *testing.T) {
ctx := context.Background()
req := &dto.UpdateShopAccountRequest{
Username: "newuser",
}
result, err := service.Update(ctx, 1, req)
assert.Error(t, err)
assert.Nil(t, result)
})
}
// TestShopAccountService_UpdatePassword 测试更新密码
func TestShopAccountService_UpdatePassword(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
service := shop_account.New(accountStore, shopStore)
t.Run("更新密码成功", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户",
ShopCode: "TEST_SHOP_004",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
account := &model.Account{
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
Username: testutils.GenerateUsername("pwduser", 1),
Phone: testutils.GeneratePhone("135", 1),
Password: "oldpassword",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
Status: constants.StatusEnabled,
}
err = accountStore.Create(ctx, account)
require.NoError(t, err)
req := &dto.UpdateShopAccountPasswordRequest{
NewPassword: "newpassword123",
}
err = service.UpdatePassword(ctx, account.ID, req)
require.NoError(t, err)
updatedAccount, err := accountStore.GetByID(ctx, account.ID)
require.NoError(t, err)
assert.NotEqual(t, "oldpassword", updatedAccount.Password)
})
t.Run("更新不存在的账号密码应失败", func(t *testing.T) {
ctx := createContextWithUserID(1)
req := &dto.UpdateShopAccountPasswordRequest{
NewPassword: "newpassword",
}
err := service.UpdatePassword(ctx, 99999, req)
assert.Error(t, err)
})
t.Run("未授权访问应失败", func(t *testing.T) {
ctx := context.Background()
req := &dto.UpdateShopAccountPasswordRequest{
NewPassword: "newpassword",
}
err := service.UpdatePassword(ctx, 1, req)
assert.Error(t, err)
})
}
// TestShopAccountService_UpdateStatus 测试更新状态
func TestShopAccountService_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
service := shop_account.New(accountStore, shopStore)
t.Run("更新状态成功", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户",
ShopCode: "TEST_SHOP_005",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
account := &model.Account{
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
Username: testutils.GenerateUsername("statususer", 1),
Phone: testutils.GeneratePhone("134", 1),
Password: "password",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
Status: constants.StatusEnabled,
}
err = accountStore.Create(ctx, account)
require.NoError(t, err)
req := &dto.UpdateShopAccountStatusRequest{
Status: constants.StatusDisabled,
}
err = service.UpdateStatus(ctx, account.ID, req)
require.NoError(t, err)
updatedAccount, err := accountStore.GetByID(ctx, account.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, updatedAccount.Status)
})
t.Run("更新不存在的账号状态应失败", func(t *testing.T) {
ctx := createContextWithUserID(1)
req := &dto.UpdateShopAccountStatusRequest{
Status: constants.StatusDisabled,
}
err := service.UpdateStatus(ctx, 99999, req)
assert.Error(t, err)
})
t.Run("未授权访问应失败", func(t *testing.T) {
ctx := context.Background()
req := &dto.UpdateShopAccountStatusRequest{
Status: constants.StatusDisabled,
}
err := service.UpdateStatus(ctx, 1, req)
assert.Error(t, err)
})
}
// TestShopAccountService_List 测试查询商户账号列表
func TestShopAccountService_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
accountStore := postgres.NewAccountStore(tx, rdb)
shopStore := postgres.NewShopStore(tx, rdb)
service := shop_account.New(accountStore, shopStore)
t.Run("查询商户账号列表", func(t *testing.T) {
ctx := createContextWithUserID(1)
shop := &model.Shop{
ShopName: "测试商户",
ShopCode: "TEST_SHOP_006",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
}
err := shopStore.Create(ctx, shop)
require.NoError(t, err)
for i := 1; i <= 3; i++ {
account := &model.Account{
BaseModel: model.BaseModel{
Creator: 1,
Updater: 1,
},
Username: testutils.GenerateUsername("listuser", i),
Phone: testutils.GeneratePhone("133", i),
Password: "password",
UserType: constants.UserTypeAgent,
ShopID: &shop.ID,
Status: constants.StatusEnabled,
}
err = accountStore.Create(ctx, account)
require.NoError(t, err)
}
req := &dto.ShopAccountListRequest{
ShopID: &shop.ID,
Page: 1,
PageSize: 20,
}
accounts, total, err := service.List(ctx, req)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(accounts), 3)
assert.GreaterOrEqual(t, total, int64(3))
})
}