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" "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" ) // 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) t.Run("创建一级店铺成功", func(t *testing.T) { ctx := createContextWithUserID(1) req := &model.CreateShopRequest{ ShopName: "测试一级店铺", ShopCode: "SHOP_L1_001", ParentID: nil, ContactName: "张三", ContactPhone: "13800000001", Province: "北京市", City: "北京市", District: "朝阳区", Address: "朝阳路100号", } result, err := service.Create(ctx, req) require.NoError(t, err) assert.NotZero(t, result.ID) assert.Equal(t, "测试一级店铺", result.ShopName) assert.Equal(t, "SHOP_L1_001", result.ShopCode) 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) { ctx := createContextWithUserID(1) // 先创建一级店铺 parent := &model.Shop{ ShopName: "一级店铺", ShopCode: "PARENT_001", Level: 1, ContactName: "李四", ContactPhone: "13800000002", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, parent) require.NoError(t, err) // 创建二级店铺 req := &model.CreateShopRequest{ ShopName: "测试二级店铺", ShopCode: "SHOP_L2_001", ParentID: &parent.ID, ContactName: "王五", ContactPhone: "13800000003", } result, err := service.Create(ctx, req) require.NoError(t, err) assert.NotZero(t, result.ID) assert.Equal(t, 2, result.Level) assert.Equal(t, parent.ID, *result.ParentID) }) t.Run("层级校验-创建第8级店铺应失败", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建 7 级店铺层级 var shops []*model.Shop for i := 1; i <= 7; i++ { var parentID *uint if i > 1 { parentID = &shops[i-2].ID } shopModel := &model.Shop{ ShopName: testutils.GenerateUsername("店铺L", i), ShopCode: testutils.GenerateUsername("LEVEL", i), ParentID: parentID, Level: i, ContactName: "测试联系人", ContactPhone: testutils.GeneratePhone("138", i), Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) require.NoError(t, err) shops = append(shops, shopModel) } // 验证已创建 7 级 assert.Len(t, shops, 7) assert.Equal(t, 7, shops[6].Level) // 尝试创建第 8 级店铺(应该失败) req := &model.CreateShopRequest{ ShopName: "第8级店铺", ShopCode: "SHOP_L8_001", ParentID: &shops[6].ID, // 第7级店铺的ID ContactName: "测试", ContactPhone: "13800000008", } result, err := service.Create(ctx, req) assert.Error(t, err) assert.Nil(t, result) // 验证错误码 appErr, ok := err.(*errors.AppError) require.True(t, ok, "错误应该是 AppError 类型") assert.Equal(t, errors.CodeShopLevelExceeded, appErr.Code) assert.Contains(t, appErr.Message, "不能超过 7 级") }) t.Run("店铺编号唯一性检查-重复编号应失败", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建第一个店铺 req1 := &model.CreateShopRequest{ ShopName: "店铺A", ShopCode: "UNIQUE_CODE_001", ContactName: "张三", ContactPhone: "13800000001", } _, err := service.Create(ctx, req1) require.NoError(t, err) // 尝试创建相同编号的店铺(应该失败) req2 := &model.CreateShopRequest{ ShopName: "店铺B", ShopCode: "UNIQUE_CODE_001", // 重复编号 ContactName: "李四", ContactPhone: "13800000002", } result, err := service.Create(ctx, req2) assert.Error(t, err) assert.Nil(t, result) // 验证错误码 appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeShopCodeExists, appErr.Code) assert.Contains(t, appErr.Message, "编号已存在") }) t.Run("上级店铺不存在应失败", func(t *testing.T) { ctx := createContextWithUserID(1) nonExistentID := uint(99999) req := &model.CreateShopRequest{ ShopName: "测试店铺", ShopCode: "SHOP_INVALID_PARENT", ParentID: &nonExistentID, // 不存在的上级店铺 ID ContactName: "测试", ContactPhone: "13800000009", } 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.CodeInvalidParentID, appErr.Code) assert.Contains(t, appErr.Message, "上级店铺不存在") }) t.Run("未授权访问应失败", func(t *testing.T) { ctx := context.Background() // 没有用户 ID 的 context req := &model.CreateShopRequest{ ShopName: "测试店铺", ShopCode: "SHOP_UNAUTHORIZED", ContactName: "测试", ContactPhone: "13800000010", } 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.CodeUnauthorized, appErr.Code) assert.Contains(t, appErr.Message, "未授权") }) } // TestShopService_Update 测试更新店铺 func TestShopService_Update(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("更新店铺信息成功", func(t *testing.T) { ctx := createContextWithUserID(1) // 先创建店铺 shopModel := &model.Shop{ ShopName: "原始店铺名称", ShopCode: "ORIGINAL_CODE", Level: 1, ContactName: "原联系人", ContactPhone: "13800000001", Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) 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, } 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) }) t.Run("更新店铺编号-唯一性检查", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建两个店铺 shop1 := &model.Shop{ ShopName: "店铺1", ShopCode: "CODE_001", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shop1) require.NoError(t, err) shop2 := &model.Shop{ ShopName: "店铺2", ShopCode: "CODE_002", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err = shopStore.Create(ctx, shop2) require.NoError(t, err) // 尝试将 shop2 的编号改为 shop1 的编号(应该失败) duplicateCode := "CODE_001" req := &model.UpdateShopRequest{ ShopCode: &duplicateCode, } 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) }) t.Run("更新不存在的店铺应失败", func(t *testing.T) { ctx := createContextWithUserID(1) newName := "新名称" req := &model.UpdateShopRequest{ ShopName: &newName, } 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 newName := "新名称" req := &model.UpdateShopRequest{ ShopName: &newName, } 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) }) } // TestShopService_Disable 测试禁用店铺 func TestShopService_Disable(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("禁用店铺成功", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建店铺 shopModel := &model.Shop{ ShopName: "待禁用店铺", ShopCode: "SHOP_TO_DISABLE", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) require.NoError(t, err) assert.Equal(t, constants.StatusEnabled, shopModel.Status) // 禁用店铺 err = service.Disable(ctx, shopModel.ID) require.NoError(t, err) // 验证状态已更新 result, err := shopStore.GetByID(ctx, shopModel.ID) require.NoError(t, err) assert.Equal(t, constants.StatusDisabled, result.Status) assert.Equal(t, uint(1), result.Updater) }) t.Run("禁用不存在的店铺应失败", func(t *testing.T) { ctx := createContextWithUserID(1) err := service.Disable(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.Disable(ctx, 1) assert.Error(t, err) // 验证错误码 appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeUnauthorized, appErr.Code) }) } // TestShopService_Enable 测试启用店铺 func TestShopService_Enable(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("启用店铺成功", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建启用状态的店铺 shopModel := &model.Shop{ ShopName: "待启用店铺", ShopCode: "SHOP_TO_ENABLE", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) require.NoError(t, err) // 先禁用店铺 shopModel.Status = constants.StatusDisabled err = shopStore.Update(ctx, shopModel) require.NoError(t, err) // 验证已禁用 disabled, err := shopStore.GetByID(ctx, shopModel.ID) require.NoError(t, err) assert.Equal(t, constants.StatusDisabled, disabled.Status) // 启用店铺 err = service.Enable(ctx, shopModel.ID) require.NoError(t, err) // 验证状态已更新为启用 result, err := shopStore.GetByID(ctx, shopModel.ID) require.NoError(t, err) assert.Equal(t, constants.StatusEnabled, result.Status) assert.Equal(t, uint(1), result.Updater) }) t.Run("启用不存在的店铺应失败", func(t *testing.T) { ctx := createContextWithUserID(1) err := service.Enable(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.Enable(ctx, 1) assert.Error(t, err) // 验证错误码 appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeUnauthorized, appErr.Code) }) } // TestShopService_GetByID 测试获取店铺详情 func TestShopService_GetByID(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("获取存在的店铺", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建店铺 shopModel := &model.Shop{ ShopName: "测试店铺", ShopCode: "TEST_SHOP_001", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) require.NoError(t, err) // 获取店铺 result, err := service.GetByID(ctx, shopModel.ID) require.NoError(t, err) assert.Equal(t, shopModel.ID, result.ID) assert.Equal(t, "测试店铺", result.ShopName) assert.Equal(t, "TEST_SHOP_001", result.ShopCode) }) t.Run("获取不存在的店铺应失败", func(t *testing.T) { ctx := createContextWithUserID(1) result, err := service.GetByID(ctx, 99999) assert.Error(t, err) assert.Nil(t, result) // 验证错误码 appErr, ok := err.(*errors.AppError) require.True(t, ok) assert.Equal(t, errors.CodeShopNotFound, appErr.Code) }) } // TestShopService_List 测试查询店铺列表 func TestShopService_List(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("查询店铺列表", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建多个店铺 for i := 1; i <= 5; i++ { shopModel := &model.Shop{ ShopName: testutils.GenerateUsername("测试店铺", i), ShopCode: testutils.GenerateUsername("SHOP_LIST", i), Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shopModel) require.NoError(t, err) } // 查询列表 shops, total, err := service.List(ctx, nil, nil) require.NoError(t, err) assert.GreaterOrEqual(t, len(shops), 5) assert.GreaterOrEqual(t, total, int64(5)) }) } // TestShopService_GetSubordinateShopIDs 测试获取下级店铺 ID 列表 func TestShopService_GetSubordinateShopIDs(t *testing.T) { db, redisClient := testutils.SetupTestDB(t) defer testutils.TeardownTestDB(t, db, redisClient) shopStore := postgres.NewShopStore(db, redisClient) service := shop.New(shopStore) t.Run("获取下级店铺 ID 列表", func(t *testing.T) { ctx := createContextWithUserID(1) // 创建店铺层级 shop1 := &model.Shop{ ShopName: "一级店铺", ShopCode: "SUBORDINATE_L1", Level: 1, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err := shopStore.Create(ctx, shop1) require.NoError(t, err) shop2 := &model.Shop{ ShopName: "二级店铺", ShopCode: "SUBORDINATE_L2", ParentID: &shop1.ID, Level: 2, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err = shopStore.Create(ctx, shop2) require.NoError(t, err) shop3 := &model.Shop{ ShopName: "三级店铺", ShopCode: "SUBORDINATE_L3", ParentID: &shop2.ID, Level: 3, Status: constants.StatusEnabled, BaseModel: model.BaseModel{ Creator: 1, Updater: 1, }, } err = shopStore.Create(ctx, shop3) require.NoError(t, err) // 获取一级店铺的所有下级(包含自己) ids, err := service.GetSubordinateShopIDs(ctx, shop1.ID) require.NoError(t, err) assert.Contains(t, ids, shop1.ID) assert.Contains(t, ids, shop2.ID) assert.Contains(t, ids, shop3.ID) assert.Len(t, ids, 3) }) }