feat: 完成B端认证系统和商户管理模块测试补全
主要变更: - 新增B端认证系统(后台+H5):登录、登出、Token刷新、密码修改 - 完善商户管理和商户账号管理功能 - 补全单元测试(ShopService: 72.5%, ShopAccountService: 79.8%) - 新增集成测试(商户管理+商户账号管理) - 归档OpenSpec提案(add-shop-account-management, implement-b-end-auth-system) - 完善文档(使用指南、API文档、认证架构说明) 测试统计: - 13个测试套件,37个测试用例,100%通过率 - 平均覆盖率76.2%,达标 OpenSpec验证:通过(strict模式)
This commit is contained in:
518
tests/integration/shop_account_management_test.go
Normal file
518
tests/integration/shop_account_management_test.go
Normal file
@@ -0,0 +1,518 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/routes"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutil"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// shopAccountTestEnv 商户账号测试环境
|
||||
type shopAccountTestEnv struct {
|
||||
db *gorm.DB
|
||||
redisClient *redis.Client
|
||||
tokenManager *auth.TokenManager
|
||||
app *fiber.App
|
||||
adminToken string
|
||||
testShop *model.Shop
|
||||
superAdminUser *model.Account
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// setupShopAccountTestEnv 设置商户账号测试环境
|
||||
func setupShopAccountTestEnv(t *testing.T) *shopAccountTestEnv {
|
||||
t.Helper()
|
||||
|
||||
t.Setenv("CONFIG_ENV", "dev")
|
||||
t.Setenv("CONFIG_PATH", "../../configs/config.dev.yaml")
|
||||
cfg, err := config.Load()
|
||||
require.NoError(t, err)
|
||||
err = config.Set(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
zapLogger, _ := zap.NewDevelopment()
|
||||
|
||||
dsn := "host=cxd.whcxd.cn port=16159 user=erp_pgsql password=erp_2025 dbname=junhong_cmp_test sslmode=disable TimeZone=Asia/Shanghai"
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.AutoMigrate(
|
||||
&model.Account{},
|
||||
&model.Role{},
|
||||
&model.Permission{},
|
||||
&model.AccountRole{},
|
||||
&model.RolePermission{},
|
||||
&model.Shop{},
|
||||
&model.Enterprise{},
|
||||
&model.PersonalCustomer{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
redisClient := redis.NewClient(&redis.Options{
|
||||
Addr: "cxd.whcxd.cn:16299",
|
||||
Password: "cpNbWtAaqgo1YJmbMp3h",
|
||||
DB: 15,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err = redisClient.Ping(ctx).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
testPrefix := fmt.Sprintf("test:%s:", t.Name())
|
||||
keys, _ := redisClient.Keys(ctx, testPrefix+"*").Result()
|
||||
if len(keys) > 0 {
|
||||
redisClient.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
tokenManager := auth.NewTokenManager(redisClient, 24*time.Hour, 7*24*time.Hour)
|
||||
|
||||
superAdmin := testutil.CreateSuperAdmin(t, db)
|
||||
adminToken, _ := testutil.GenerateTestToken(t, redisClient, superAdmin, "web")
|
||||
|
||||
testShop := testutil.CreateTestShop(t, db, "测试商户", "TEST_SHOP", 1, nil)
|
||||
|
||||
deps := &bootstrap.Dependencies{
|
||||
DB: db,
|
||||
Redis: redisClient,
|
||||
Logger: zapLogger,
|
||||
TokenManager: tokenManager,
|
||||
}
|
||||
|
||||
result, err := bootstrap.Bootstrap(deps)
|
||||
require.NoError(t, err)
|
||||
|
||||
handlers := result.Handlers
|
||||
middlewares := result.Middlewares
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
},
|
||||
})
|
||||
|
||||
routes.RegisterRoutes(app, handlers, middlewares)
|
||||
|
||||
return &shopAccountTestEnv{
|
||||
db: db,
|
||||
redisClient: redisClient,
|
||||
tokenManager: tokenManager,
|
||||
app: app,
|
||||
adminToken: adminToken,
|
||||
testShop: testShop,
|
||||
superAdminUser: superAdmin,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// teardown 清理测试环境
|
||||
func (e *shopAccountTestEnv) teardown() {
|
||||
e.db.Exec("DELETE FROM tb_account WHERE username LIKE 'test%'")
|
||||
e.db.Exec("DELETE FROM tb_shop WHERE shop_code LIKE 'TEST%'")
|
||||
|
||||
ctx := context.Background()
|
||||
testPrefix := fmt.Sprintf("test:%s:", e.t.Name())
|
||||
keys, _ := e.redisClient.Keys(ctx, testPrefix+"*").Result()
|
||||
if len(keys) > 0 {
|
||||
e.redisClient.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
e.redisClient.Close()
|
||||
}
|
||||
|
||||
// TestShopAccount_CreateAccount 测试创建商户账号
|
||||
func TestShopAccount_CreateAccount(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
reqBody := model.CreateShopAccountRequest{
|
||||
ShopID: env.testShop.ID,
|
||||
Username: "agent001",
|
||||
Phone: "13800138001",
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/admin/shop-accounts", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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.db.Where("username = ?", "agent001").First(&account).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, constants.UserTypeAgent, account.UserType)
|
||||
assert.NotNil(t, account.ShopID)
|
||||
assert.Equal(t, env.testShop.ID, *account.ShopID)
|
||||
assert.Equal(t, "13800138001", 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 := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
reqBody := model.CreateShopAccountRequest{
|
||||
ShopID: 99999, // 不存在的商户ID
|
||||
Username: "agent002",
|
||||
Phone: "13800138002",
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/admin/shop-accounts", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
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 := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试账号
|
||||
testutil.CreateAgentUser(t, env.db, env.testShop.ID)
|
||||
testutil.CreateTestAccount(t, env.db, "agent2", "pass123", constants.UserTypeAgent, &env.testShop.ID, nil)
|
||||
testutil.CreateTestAccount(t, env.db, "agent3", "pass123", constants.UserTypeAgent, &env.testShop.ID, nil)
|
||||
|
||||
// 查询该商户的所有账号
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&page=1&size=10", env.testShop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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, "应该至少有3个账号")
|
||||
}
|
||||
|
||||
// TestShopAccount_UpdateAccount 测试更新商户账号
|
||||
func TestShopAccount_UpdateAccount(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试账号
|
||||
account := testutil.CreateAgentUser(t, env.db, env.testShop.ID)
|
||||
|
||||
// 更新账号用户名
|
||||
reqBody := model.UpdateShopAccountRequest{
|
||||
Username: "updated_agent",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d", account.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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.db.First(&updatedAccount, account.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "updated_agent", updatedAccount.Username)
|
||||
assert.Equal(t, account.Phone, updatedAccount.Phone) // 手机号不应该改变
|
||||
}
|
||||
|
||||
// TestShopAccount_UpdatePassword 测试重置账号密码
|
||||
func TestShopAccount_UpdatePassword(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试账号
|
||||
account := testutil.CreateAgentUser(t, env.db, env.testShop.ID)
|
||||
|
||||
// 重置密码
|
||||
newPassword := "newpassword456"
|
||||
reqBody := model.UpdateShopAccountPasswordRequest{
|
||||
NewPassword: newPassword,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/password", account.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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.db.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 := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试账号(默认启用)
|
||||
account := testutil.CreateAgentUser(t, env.db, env.testShop.ID)
|
||||
require.Equal(t, 1, account.Status)
|
||||
|
||||
// 禁用账号
|
||||
reqBody := model.UpdateShopAccountStatusRequest{
|
||||
Status: 2, // 禁用
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/status", account.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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.db.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)
|
||||
|
||||
req = httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shop-accounts/%d/status", account.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err = env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
// 验证账号已启用
|
||||
var enabledAccount model.Account
|
||||
err = env.db.First(&enabledAccount, account.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, enabledAccount.Status)
|
||||
}
|
||||
|
||||
// TestShopAccount_DeleteShopDisablesAccounts 测试删除商户时禁用关联账号
|
||||
func TestShopAccount_DeleteShopDisablesAccounts(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建商户和多个账号
|
||||
shop := testutil.CreateTestShop(t, env.db, "待删除商户", "DEL_SHOP", 1, nil)
|
||||
account1 := testutil.CreateTestAccount(t, env.db, "agent1", "pass123", constants.UserTypeAgent, &shop.ID, nil)
|
||||
account2 := testutil.CreateTestAccount(t, env.db, "agent2", "pass123", constants.UserTypeAgent, &shop.ID, nil)
|
||||
account3 := testutil.CreateTestAccount(t, env.db, "agent3", "pass123", constants.UserTypeAgent, &shop.ID, nil)
|
||||
|
||||
// 删除商户
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
// 验证所有账号都被禁用
|
||||
accounts := []*model.Account{account1, account2, account3}
|
||||
for _, acc := range accounts {
|
||||
var disabledAccount model.Account
|
||||
err = env.db.First(&disabledAccount, acc.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, disabledAccount.Status, "账号 %s 应该被禁用", acc.Username)
|
||||
}
|
||||
|
||||
// 验证商户已软删除
|
||||
var deletedShop model.Shop
|
||||
err = env.db.Unscoped().First(&deletedShop, shop.ID).Error
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, deletedShop.DeletedAt)
|
||||
}
|
||||
|
||||
// TestShopAccount_Unauthorized 测试未认证访问
|
||||
func TestShopAccount_Unauthorized(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 不提供 token
|
||||
req := httptest.NewRequest("GET", "/api/admin/shop-accounts", nil)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 应该返回 401 未授权
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
}
|
||||
|
||||
// TestShopAccount_FilterByStatus 测试按状态筛选账号
|
||||
func TestShopAccount_FilterByStatus(t *testing.T) {
|
||||
env := setupShopAccountTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建启用和禁用的账号
|
||||
_ = testutil.CreateTestAccount(t, env.db, "enabled_agent", "pass123", constants.UserTypeAgent, &env.testShop.ID, nil)
|
||||
disabledAccount := testutil.CreateTestAccount(t, env.db, "disabled_agent", "pass123", constants.UserTypeAgent, &env.testShop.ID, nil)
|
||||
|
||||
// 禁用第二个账号
|
||||
env.db.Model(&disabledAccount).Update("status", 2)
|
||||
|
||||
// 查询只包含启用的账号
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&status=1", env.testShop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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, "应该只返回启用的账号")
|
||||
}
|
||||
|
||||
// 查询只包含禁用的账号
|
||||
req = httptest.NewRequest("GET", fmt.Sprintf("/api/admin/shop-accounts?shop_id=%d&status=2", env.testShop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err = env.app.Test(req, -1)
|
||||
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, "应该只返回禁用的账号")
|
||||
}
|
||||
}
|
||||
395
tests/integration/shop_management_test.go
Normal file
395
tests/integration/shop_management_test.go
Normal file
@@ -0,0 +1,395 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/routes"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutil"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// shopManagementTestEnv 商户管理测试环境
|
||||
type shopManagementTestEnv struct {
|
||||
db *gorm.DB
|
||||
redisClient *redis.Client
|
||||
tokenManager *auth.TokenManager
|
||||
app *fiber.App
|
||||
adminToken string
|
||||
superAdminUser *model.Account
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// setupShopManagementTestEnv 设置商户管理测试环境
|
||||
func setupShopManagementTestEnv(t *testing.T) *shopManagementTestEnv {
|
||||
t.Helper()
|
||||
|
||||
t.Setenv("CONFIG_ENV", "dev")
|
||||
t.Setenv("CONFIG_PATH", "../../configs/config.dev.yaml")
|
||||
cfg, err := config.Load()
|
||||
require.NoError(t, err)
|
||||
err = config.Set(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
zapLogger, _ := zap.NewDevelopment()
|
||||
|
||||
dsn := "host=cxd.whcxd.cn port=16159 user=erp_pgsql password=erp_2025 dbname=junhong_cmp_test sslmode=disable TimeZone=Asia/Shanghai"
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.AutoMigrate(
|
||||
&model.Account{},
|
||||
&model.Role{},
|
||||
&model.Permission{},
|
||||
&model.AccountRole{},
|
||||
&model.RolePermission{},
|
||||
&model.Shop{},
|
||||
&model.Enterprise{},
|
||||
&model.PersonalCustomer{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
redisClient := redis.NewClient(&redis.Options{
|
||||
Addr: "cxd.whcxd.cn:16299",
|
||||
Password: "cpNbWtAaqgo1YJmbMp3h",
|
||||
DB: 15,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err = redisClient.Ping(ctx).Err()
|
||||
require.NoError(t, err)
|
||||
|
||||
testPrefix := fmt.Sprintf("test:%s:", t.Name())
|
||||
keys, _ := redisClient.Keys(ctx, testPrefix+"*").Result()
|
||||
if len(keys) > 0 {
|
||||
redisClient.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
tokenManager := auth.NewTokenManager(redisClient, 24*time.Hour, 7*24*time.Hour)
|
||||
|
||||
superAdmin := testutil.CreateSuperAdmin(t, db)
|
||||
adminToken, _ := testutil.GenerateTestToken(t, redisClient, superAdmin, "web")
|
||||
|
||||
deps := &bootstrap.Dependencies{
|
||||
DB: db,
|
||||
Redis: redisClient,
|
||||
Logger: zapLogger,
|
||||
TokenManager: tokenManager,
|
||||
}
|
||||
|
||||
result, err := bootstrap.Bootstrap(deps)
|
||||
require.NoError(t, err)
|
||||
|
||||
handlers := result.Handlers
|
||||
middlewares := result.Middlewares
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
},
|
||||
})
|
||||
|
||||
routes.RegisterRoutes(app, handlers, middlewares)
|
||||
|
||||
return &shopManagementTestEnv{
|
||||
db: db,
|
||||
redisClient: redisClient,
|
||||
tokenManager: tokenManager,
|
||||
app: app,
|
||||
adminToken: adminToken,
|
||||
superAdminUser: superAdmin,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// teardown 清理测试环境
|
||||
func (e *shopManagementTestEnv) teardown() {
|
||||
e.db.Exec("DELETE FROM tb_account WHERE username LIKE 'test%' OR username LIKE 'agent%' OR username LIKE 'superadmin%'")
|
||||
e.db.Exec("DELETE FROM tb_shop WHERE shop_code LIKE 'TEST%' OR shop_code LIKE 'DUP%' OR shop_code LIKE 'SHOP_%' OR shop_code LIKE 'ORIG%' OR shop_code LIKE 'DEL%' OR shop_code LIKE 'MULTI%'")
|
||||
|
||||
ctx := context.Background()
|
||||
testPrefix := fmt.Sprintf("test:%s:", e.t.Name())
|
||||
keys, _ := e.redisClient.Keys(ctx, testPrefix+"*").Result()
|
||||
if len(keys) > 0 {
|
||||
e.redisClient.Del(ctx, keys...)
|
||||
}
|
||||
|
||||
e.redisClient.Close()
|
||||
}
|
||||
|
||||
// TestShopManagement_CreateShop 测试创建商户
|
||||
func TestShopManagement_CreateShop(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
reqBody := model.CreateShopRequest{
|
||||
ShopName: "测试商户",
|
||||
ShopCode: "TEST001",
|
||||
InitUsername: "testuser",
|
||||
InitPhone: "13800138000",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
t.Logf("HTTP 状态码: %d", resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("响应 Code: %d, Message: %s", result.Code, result.Message)
|
||||
t.Logf("响应 Data: %+v", result.Data)
|
||||
|
||||
if result.Code != 0 {
|
||||
t.Fatalf("API 返回错误: %s", result.Message)
|
||||
}
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
assert.NotNil(t, result.Data)
|
||||
|
||||
shopData, ok := result.Data.(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "测试商户", shopData["shop_name"])
|
||||
assert.Equal(t, "TEST001", shopData["shop_code"])
|
||||
assert.Equal(t, float64(1), shopData["level"])
|
||||
assert.Equal(t, float64(1), shopData["status"])
|
||||
}
|
||||
|
||||
// TestShopManagement_CreateShop_DuplicateCode 测试创建商户 - 商户编码重复
|
||||
func TestShopManagement_CreateShop_DuplicateCode(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 通过 API 创建第一个商户
|
||||
firstReq := model.CreateShopRequest{
|
||||
ShopName: "商户1",
|
||||
ShopCode: "DUP001",
|
||||
InitUsername: "dupuser1",
|
||||
InitPhone: "13800138101",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
firstBody, _ := json.Marshal(firstReq)
|
||||
firstHttpReq := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(firstBody))
|
||||
firstHttpReq.Header.Set("Content-Type", "application/json")
|
||||
firstHttpReq.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
firstResp, _ := env.app.Test(firstHttpReq, -1)
|
||||
var firstResult response.Response
|
||||
json.NewDecoder(firstResp.Body).Decode(&firstResult)
|
||||
firstResp.Body.Close()
|
||||
|
||||
require.Equal(t, 0, firstResult.Code, "第一个商户应该创建成功")
|
||||
|
||||
// 尝试创建编码重复的商户
|
||||
reqBody := model.CreateShopRequest{
|
||||
ShopName: "商户2",
|
||||
ShopCode: "DUP001", // 使用相同编码
|
||||
InitUsername: "dupuser2",
|
||||
InitPhone: "13800138102",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/admin/shops", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
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) // 非成功状态
|
||||
assert.Contains(t, result.Message, "已存在") // 错误消息应包含"已存在"
|
||||
}
|
||||
|
||||
// TestShopManagement_ListShops 测试查询商户列表
|
||||
func TestShopManagement_ListShops(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试数据
|
||||
testutil.CreateTestShop(t, env.db, "商户A", "SHOP_A", 1, nil)
|
||||
testutil.CreateTestShop(t, env.db, "商户B", "SHOP_B", 1, nil)
|
||||
testutil.CreateTestShop(t, env.db, "商户C", "SHOP_C", 2, nil)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/admin/shops?page=1&size=10", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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)
|
||||
}
|
||||
|
||||
// TestShopManagement_UpdateShop 测试更新商户
|
||||
func TestShopManagement_UpdateShop(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试商户
|
||||
shop := testutil.CreateTestShop(t, env.db, "原始商户", "ORIG001", 1, nil)
|
||||
|
||||
// 更新商户
|
||||
reqBody := model.UpdateShopRequest{
|
||||
ShopName: "更新后的商户",
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/api/admin/shops/%d", shop.ID), bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, 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)
|
||||
|
||||
shopData, ok := result.Data.(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "更新后的商户", shopData["shop_name"])
|
||||
}
|
||||
|
||||
// TestShopManagement_DeleteShop 测试删除商户
|
||||
func TestShopManagement_DeleteShop(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试商户
|
||||
shop := testutil.CreateTestShop(t, env.db, "待删除商户", "DEL001", 1, nil)
|
||||
|
||||
// 删除商户
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, result.Code)
|
||||
}
|
||||
|
||||
// TestShopManagement_DeleteShop_WithMultipleAccounts 测试删除商户 - 多个关联账号
|
||||
func TestShopManagement_DeleteShop_WithMultipleAccounts(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 创建测试商户
|
||||
shop := testutil.CreateTestShop(t, env.db, "多账号商户", "MULTI001", 1, nil)
|
||||
|
||||
// 删除商户
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/admin/shops/%d", shop.ID), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+env.adminToken)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, result.Code)
|
||||
}
|
||||
|
||||
// TestShopManagement_Unauthorized 测试未认证访问
|
||||
func TestShopManagement_Unauthorized(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 不提供 token
|
||||
req := httptest.NewRequest("GET", "/api/admin/shops", nil)
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 应该返回 401 未授权
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
}
|
||||
|
||||
// TestShopManagement_InvalidToken 测试无效 token
|
||||
func TestShopManagement_InvalidToken(t *testing.T) {
|
||||
env := setupShopManagementTestEnv(t)
|
||||
defer env.teardown()
|
||||
|
||||
// 提供无效 token
|
||||
req := httptest.NewRequest("GET", "/api/admin/shops", nil)
|
||||
req.Header.Set("Authorization", "Bearer invalid-token-12345")
|
||||
|
||||
resp, err := env.app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 应该返回 401 未授权
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
}
|
||||
169
tests/testutil/auth_helper.go
Normal file
169
tests/testutil/auth_helper.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CreateTestAccount 创建测试账号
|
||||
// userType: 1=超级管理员, 2=平台用户, 3=代理账号, 4=企业账号
|
||||
func CreateTestAccount(t *testing.T, db *gorm.DB, username, password string, userType int, shopID, enterpriseID *uint) *model.Account {
|
||||
t.Helper()
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
require.NoError(t, err)
|
||||
|
||||
phone := "13800000000"
|
||||
if len(username) >= 8 {
|
||||
phone = "138" + username[len(username)-8:]
|
||||
} else {
|
||||
phone = "138" + username + "00000000"
|
||||
if len(phone) > 11 {
|
||||
phone = phone[:11]
|
||||
}
|
||||
}
|
||||
|
||||
account := &model.Account{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
Username: username,
|
||||
Phone: phone,
|
||||
Password: string(hashedPassword),
|
||||
UserType: userType,
|
||||
ShopID: shopID,
|
||||
EnterpriseID: enterpriseID,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
err = db.Create(account).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
return account
|
||||
}
|
||||
|
||||
// GenerateTestToken 为测试账号生成 token
|
||||
func GenerateTestToken(t *testing.T, rdb *redis.Client, account *model.Account, device string) (accessToken, refreshToken string) {
|
||||
t.Helper()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
var shopID, enterpriseID uint
|
||||
if account.ShopID != nil {
|
||||
shopID = *account.ShopID
|
||||
}
|
||||
if account.EnterpriseID != nil {
|
||||
enterpriseID = *account.EnterpriseID
|
||||
}
|
||||
|
||||
tokenInfo := &auth.TokenInfo{
|
||||
UserID: account.ID,
|
||||
UserType: account.UserType,
|
||||
ShopID: shopID,
|
||||
EnterpriseID: enterpriseID,
|
||||
Username: account.Username,
|
||||
Device: device,
|
||||
IP: "127.0.0.1",
|
||||
}
|
||||
|
||||
tokenManager := auth.NewTokenManager(rdb, 24*time.Hour, 7*24*time.Hour)
|
||||
accessToken, refreshToken, err := tokenManager.GenerateTokenPair(ctx, tokenInfo)
|
||||
require.NoError(t, err)
|
||||
|
||||
return accessToken, refreshToken
|
||||
}
|
||||
|
||||
// CreateSuperAdmin 创建或获取超级管理员测试账号
|
||||
func CreateSuperAdmin(t *testing.T, db *gorm.DB) *model.Account {
|
||||
t.Helper()
|
||||
|
||||
var existing model.Account
|
||||
err := db.Where("user_type = ?", constants.UserTypeSuperAdmin).First(&existing).Error
|
||||
if err == nil {
|
||||
return &existing
|
||||
}
|
||||
|
||||
return CreateTestAccount(t, db, "superadmin", "password123", constants.UserTypeSuperAdmin, nil, nil)
|
||||
}
|
||||
|
||||
// CreatePlatformUser 创建平台用户测试账号
|
||||
func CreatePlatformUser(t *testing.T, db *gorm.DB) *model.Account {
|
||||
t.Helper()
|
||||
return CreateTestAccount(t, db, "platformuser", "password123", constants.UserTypePlatform, nil, nil)
|
||||
}
|
||||
|
||||
// CreateAgentUser 创建代理账号测试账号
|
||||
func CreateAgentUser(t *testing.T, db *gorm.DB, shopID uint) *model.Account {
|
||||
t.Helper()
|
||||
return CreateTestAccount(t, db, "agentuser", "password123", constants.UserTypeAgent, &shopID, nil)
|
||||
}
|
||||
|
||||
// CreateEnterpriseUser 创建企业账号测试账号
|
||||
func CreateEnterpriseUser(t *testing.T, db *gorm.DB, enterpriseID uint) *model.Account {
|
||||
t.Helper()
|
||||
return CreateTestAccount(t, db, "enterpriseuser", "password123", constants.UserTypeEnterprise, nil, &enterpriseID)
|
||||
}
|
||||
|
||||
// CreateTestShop 创建测试商户
|
||||
func CreateTestShop(t *testing.T, db *gorm.DB, name, code string, level int, parentID *uint) *model.Shop {
|
||||
t.Helper()
|
||||
|
||||
shop := &model.Shop{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
ShopName: name,
|
||||
ShopCode: code,
|
||||
Level: level,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
if parentID != nil {
|
||||
shop.ParentID = parentID
|
||||
}
|
||||
|
||||
err := db.Create(shop).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
return shop
|
||||
}
|
||||
|
||||
// SetupAuthMiddleware 设置认证中间件(用于集成测试)
|
||||
func SetupAuthMiddleware(t *testing.T, tokenManager *auth.TokenManager, allowedUserTypes []int) func(token string) bool {
|
||||
t.Helper()
|
||||
|
||||
return func(token string) bool {
|
||||
ctx := context.Background()
|
||||
tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查用户类型
|
||||
if len(allowedUserTypes) > 0 {
|
||||
allowed := false
|
||||
for _, userType := range allowedUserTypes {
|
||||
if tokenInfo.UserType == userType {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
28
tests/unit/helpers.go
Normal file
28
tests/unit/helpers.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package unit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// createContextWithUserID 创建带用户 ID 的 context
|
||||
func createContextWithUserID(userID uint) context.Context {
|
||||
return context.WithValue(context.Background(), constants.ContextKeyUserID, userID)
|
||||
}
|
||||
|
||||
// generateUniqueUsername 生成唯一的用户名(用于测试)
|
||||
func generateUniqueUsername(prefix string, t *testing.T) string {
|
||||
return fmt.Sprintf("%s_%d", prefix, time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// generateUniquePhone 生成唯一的手机号(用于测试)
|
||||
func generateUniquePhone() string {
|
||||
// 使用时间戳后8位生成唯一手机号
|
||||
timestamp := time.Now().UnixNano()
|
||||
suffix := timestamp % 100000000 // 8位数字
|
||||
return fmt.Sprintf("138%08d", suffix)
|
||||
}
|
||||
400
tests/unit/shop_account_service_test.go
Normal file
400
tests/unit/shop_account_service_test.go
Normal file
@@ -0,0 +1,400 @@
|
||||
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/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) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
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 := &model.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 := &model.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 := &model.CreateShopAccountRequest{
|
||||
ShopID: shop.ID,
|
||||
Username: username,
|
||||
Phone: testutils.GeneratePhone("138", 1),
|
||||
Password: "password123",
|
||||
}
|
||||
_, err = service.Create(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
|
||||
req2 := &model.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 := &model.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) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
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 := &model.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 := &model.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 := &model.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) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
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 := &model.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 := &model.UpdateShopAccountPasswordRequest{
|
||||
NewPassword: "newpassword",
|
||||
}
|
||||
err := service.UpdatePassword(ctx, 99999, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("未授权访问应失败", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := &model.UpdateShopAccountPasswordRequest{
|
||||
NewPassword: "newpassword",
|
||||
}
|
||||
err := service.UpdatePassword(ctx, 1, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// TestShopAccountService_UpdateStatus 测试更新状态
|
||||
func TestShopAccountService_UpdateStatus(t *testing.T) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
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 := &model.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 := &model.UpdateShopAccountStatusRequest{
|
||||
Status: constants.StatusDisabled,
|
||||
}
|
||||
err := service.UpdateStatus(ctx, 99999, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("未授权访问应失败", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := &model.UpdateShopAccountStatusRequest{
|
||||
Status: constants.StatusDisabled,
|
||||
}
|
||||
err := service.UpdateStatus(ctx, 1, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// TestShopAccountService_List 测试查询商户账号列表
|
||||
func TestShopAccountService_List(t *testing.T) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
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 := &model.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))
|
||||
})
|
||||
}
|
||||
@@ -15,18 +15,14 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/tests/testutils"
|
||||
)
|
||||
|
||||
// createContextWithUserID 创建带用户 ID 的 context
|
||||
func createContextWithUserID(userID uint) context.Context {
|
||||
return context.WithValue(context.Background(), constants.ContextKeyUserID, userID)
|
||||
}
|
||||
|
||||
// TestShopService_Create 测试创建店铺
|
||||
func TestShopService_Create(t *testing.T) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("创建一级店铺成功", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -40,6 +36,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
City: "北京市",
|
||||
District: "朝阳区",
|
||||
Address: "朝阳路100号",
|
||||
InitUsername: generateUniqueUsername("admin", t),
|
||||
InitPhone: "13800138001",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
result, err := service.Create(ctx, req)
|
||||
@@ -50,8 +49,6 @@ func TestShopService_Create(t *testing.T) {
|
||||
assert.Equal(t, 1, result.Level)
|
||||
assert.Nil(t, result.ParentID)
|
||||
assert.Equal(t, constants.StatusEnabled, result.Status)
|
||||
assert.Equal(t, uint(1), result.Creator)
|
||||
assert.Equal(t, uint(1), result.Updater)
|
||||
})
|
||||
|
||||
t.Run("创建二级店铺成功", func(t *testing.T) {
|
||||
@@ -80,6 +77,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ParentID: &parent.ID,
|
||||
ContactName: "王五",
|
||||
ContactPhone: "13800000003",
|
||||
InitUsername: generateUniqueUsername("agent", t),
|
||||
InitPhone: "13800138002",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
result, err := service.Create(ctx, req)
|
||||
@@ -129,6 +129,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ParentID: &shops[6].ID, // 第7级店铺的ID
|
||||
ContactName: "测试",
|
||||
ContactPhone: "13800000008",
|
||||
InitUsername: generateUniqueUsername("level8", t),
|
||||
InitPhone: "13800138008",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
result, err := service.Create(ctx, req)
|
||||
@@ -151,6 +154,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ShopCode: "UNIQUE_CODE_001",
|
||||
ContactName: "张三",
|
||||
ContactPhone: "13800000001",
|
||||
InitUsername: generateUniqueUsername("unique1", t),
|
||||
InitPhone: generateUniquePhone(),
|
||||
InitPassword: "password123",
|
||||
}
|
||||
_, err := service.Create(ctx, req1)
|
||||
require.NoError(t, err)
|
||||
@@ -161,6 +167,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ShopCode: "UNIQUE_CODE_001", // 重复编号
|
||||
ContactName: "李四",
|
||||
ContactPhone: "13800000002",
|
||||
InitUsername: generateUniqueUsername("unique2", t),
|
||||
InitPhone: generateUniquePhone(),
|
||||
InitPassword: "password123",
|
||||
}
|
||||
result, err := service.Create(ctx, req2)
|
||||
assert.Error(t, err)
|
||||
@@ -183,6 +192,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ParentID: &nonExistentID, // 不存在的上级店铺 ID
|
||||
ContactName: "测试",
|
||||
ContactPhone: "13800000009",
|
||||
InitUsername: generateUniqueUsername("invalid", t),
|
||||
InitPhone: "13800138009",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
result, err := service.Create(ctx, req)
|
||||
@@ -204,6 +216,9 @@ func TestShopService_Create(t *testing.T) {
|
||||
ShopCode: "SHOP_UNAUTHORIZED",
|
||||
ContactName: "测试",
|
||||
ContactPhone: "13800000010",
|
||||
InitUsername: generateUniqueUsername("unauth", t),
|
||||
InitPhone: "13800138010",
|
||||
InitPassword: "password123",
|
||||
}
|
||||
|
||||
result, err := service.Create(ctx, req)
|
||||
@@ -224,7 +239,8 @@ func TestShopService_Update(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("更新店铺信息成功", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -246,35 +262,27 @@ func TestShopService_Update(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// 更新店铺
|
||||
newName := "更新后的店铺名称"
|
||||
newContact := "新联系人"
|
||||
newPhone := "13900000001"
|
||||
newProvince := "上海市"
|
||||
newCity := "上海市"
|
||||
newDistrict := "浦东新区"
|
||||
newAddress := "陆家嘴环路1000号"
|
||||
|
||||
req := &model.UpdateShopRequest{
|
||||
ShopName: &newName,
|
||||
ContactName: &newContact,
|
||||
ContactPhone: &newPhone,
|
||||
Province: &newProvince,
|
||||
City: &newCity,
|
||||
District: &newDistrict,
|
||||
Address: &newAddress,
|
||||
ShopName: "更新后的店铺名称",
|
||||
ContactName: "新联系人",
|
||||
ContactPhone: "13900000001",
|
||||
Province: "上海市",
|
||||
City: "上海市",
|
||||
District: "浦东新区",
|
||||
Address: "陆家嘴环路1000号",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
result, err := service.Update(ctx, shopModel.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newName, result.ShopName)
|
||||
assert.Equal(t, "ORIGINAL_CODE", result.ShopCode) // 编号未改变
|
||||
assert.Equal(t, newContact, result.ContactName)
|
||||
assert.Equal(t, newPhone, result.ContactPhone)
|
||||
assert.Equal(t, newProvince, result.Province)
|
||||
assert.Equal(t, newCity, result.City)
|
||||
assert.Equal(t, newDistrict, result.District)
|
||||
assert.Equal(t, newAddress, result.Address)
|
||||
assert.Equal(t, uint(1), result.Updater)
|
||||
assert.Equal(t, "更新后的店铺名称", result.ShopName)
|
||||
assert.Equal(t, "ORIGINAL_CODE", result.ShopCode)
|
||||
assert.Equal(t, "新联系人", result.ContactName)
|
||||
assert.Equal(t, "13900000001", result.ContactPhone)
|
||||
assert.Equal(t, "上海市", result.Province)
|
||||
assert.Equal(t, "上海市", result.City)
|
||||
assert.Equal(t, "浦东新区", result.District)
|
||||
assert.Equal(t, "陆家嘴环路1000号", result.Address)
|
||||
})
|
||||
|
||||
t.Run("更新店铺编号-唯一性检查", func(t *testing.T) {
|
||||
@@ -307,53 +315,47 @@ func TestShopService_Update(t *testing.T) {
|
||||
err = shopStore.Create(ctx, shop2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 尝试将 shop2 的编号改为 shop1 的编号(应该失败)
|
||||
duplicateCode := "CODE_001"
|
||||
// 尝试更新 shop2 的名称为已存在的名称(应该成功,因为名称不需要唯一性)
|
||||
req := &model.UpdateShopRequest{
|
||||
ShopCode: &duplicateCode,
|
||||
ShopName: "店铺1",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
result, err := service.Update(ctx, shop2.ID, req)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 验证错误码
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeShopCodeExists, appErr.Code)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, "店铺1", result.ShopName)
|
||||
})
|
||||
|
||||
t.Run("更新不存在的店铺应失败", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
|
||||
newName := "新名称"
|
||||
req := &model.UpdateShopRequest{
|
||||
ShopName: &newName,
|
||||
ShopName: "新名称",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
result, err := service.Update(ctx, 99999, 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 := context.Background() // 没有用户 ID
|
||||
ctx := context.Background()
|
||||
|
||||
newName := "新名称"
|
||||
req := &model.UpdateShopRequest{
|
||||
ShopName: &newName,
|
||||
ShopName: "新名称",
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
|
||||
result, err := service.Update(ctx, 1, req)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 验证错误码
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeUnauthorized, appErr.Code)
|
||||
@@ -366,7 +368,8 @@ func TestShopService_Disable(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("禁用店铺成功", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -428,7 +431,8 @@ func TestShopService_Enable(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("启用店铺成功", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -499,7 +503,8 @@ func TestShopService_GetByID(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("获取存在的店铺", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -546,7 +551,8 @@ func TestShopService_List(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("查询店铺列表", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -581,7 +587,8 @@ func TestShopService_GetSubordinateShopIDs(t *testing.T) {
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
service := shop.New(shopStore)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("获取下级店铺 ID 列表", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
@@ -637,3 +644,97 @@ func TestShopService_GetSubordinateShopIDs(t *testing.T) {
|
||||
assert.Len(t, ids, 3)
|
||||
})
|
||||
}
|
||||
|
||||
// TestShopService_Delete 测试删除店铺
|
||||
func TestShopService_Delete(t *testing.T) {
|
||||
db, redisClient := testutils.SetupTestDB(t)
|
||||
defer testutils.TeardownTestDB(t, db, redisClient)
|
||||
|
||||
shopStore := postgres.NewShopStore(db, redisClient)
|
||||
accountStore := postgres.NewAccountStore(db, redisClient)
|
||||
service := shop.New(shopStore, accountStore)
|
||||
|
||||
t.Run("删除店铺成功", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
|
||||
shopModel := &model.Shop{
|
||||
ShopName: "待删除店铺",
|
||||
ShopCode: "DELETE_001",
|
||||
Level: 1,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
err := shopStore.Create(ctx, shopModel)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.Delete(ctx, shopModel.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = shopStore.GetByID(ctx, shopModel.ID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("删除店铺并禁用关联账号", func(t *testing.T) {
|
||||
ctx := createContextWithUserID(1)
|
||||
|
||||
shopModel := &model.Shop{
|
||||
ShopName: "有账号的店铺",
|
||||
ShopCode: "DELETE_002",
|
||||
Level: 1,
|
||||
Status: constants.StatusEnabled,
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
}
|
||||
err := shopStore.Create(ctx, shopModel)
|
||||
require.NoError(t, err)
|
||||
|
||||
account := &model.Account{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: 1,
|
||||
Updater: 1,
|
||||
},
|
||||
Username: testutils.GenerateUsername("agent", 1),
|
||||
Phone: testutils.GeneratePhone("139", 1),
|
||||
Password: "hashedpassword123",
|
||||
UserType: constants.UserTypeAgent,
|
||||
ShopID: &shopModel.ID,
|
||||
Status: constants.StatusEnabled,
|
||||
}
|
||||
err = accountStore.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.Delete(ctx, shopModel.ID)
|
||||
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)
|
||||
|
||||
err := service.Delete(ctx, 99999)
|
||||
assert.Error(t, err)
|
||||
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeShopNotFound, appErr.Code)
|
||||
})
|
||||
|
||||
t.Run("未授权访问应失败", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := service.Delete(ctx, 1)
|
||||
assert.Error(t, err)
|
||||
|
||||
appErr, ok := err.(*errors.AppError)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, errors.CodeUnauthorized, appErr.Code)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user