refactor(account): 统一账号管理API、完善权限检查和操作审计
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s
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:
405
tests/integration/account_audit_test.go
Normal file
405
tests/integration/account_audit_test.go
Normal 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_id,Handler 用 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 类型的审计日志")
|
||||
}
|
||||
495
tests/integration/account_permission_test.go
Normal file
495
tests/integration/account_permission_test.go
Normal 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
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user