| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- package product
- import (
- "context"
- "errors"
- "testing"
- productModel "perms-system-server/internal/model/product"
- userModel "perms-system-server/internal/model/user"
- "perms-system-server/internal/response"
- "perms-system-server/internal/testutil/ctxhelper"
- "perms-system-server/internal/testutil/mocks"
- "perms-system-server/internal/types"
- "github.com/go-sql-driver/mysql"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/stores/sqlx"
- "go.uber.org/mock/gomock"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计 M-5 修复 —— 旧实现用 strings.Contains(err, "uk_code") 来分辨
- // "产品码冲突" vs 其它唯一键冲突,文案随 MySQL 版本、驱动甚至索引重命名漂移,
- // 极易把真实冲突静默降级为通用 500;修复后统一返回 ErrConflict("数据冲突,请稍后重试"),
- // 由 pre-check 负责业务语义。本文件锚定"非特定文案也能兜到 409"。
- // ---------------------------------------------------------------------------
- // TC-0827: M-5 —— 事务内冒出 1062 错误(错误消息里不含 "uk_code" 字样)时,
- // 仍必须返回 409 通用冲突,而不是被旧的 strings.Contains 分支漏掉降级成 500。
- func TestCreateProduct_DuplicateEntry_UnknownIndexName_MapsTo409(t *testing.T) {
- ctrl := gomock.NewController(t)
- t.Cleanup(ctrl.Finish)
- // 关键:索引名选一个完全不含 "uk_code" 的,让旧 strings.Contains 分支必然 miss。
- dupErr := &mysql.MySQLError{
- Number: 1062,
- Message: "Duplicate entry 'abc' for key 'sys_product_PRIMARY'",
- }
- mockProduct := mocks.NewMockSysProductModel(ctrl)
- mockProduct.EXPECT().FindOneByCode(gomock.Any(), "m5_code").
- Return(nil, productModel.ErrNotFound)
- mockProduct.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
- DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
- return fn(ctx, nil)
- })
- // 直接让 InsertWithTx 冒出 1062
- mockProduct.EXPECT().InsertWithTx(gomock.Any(), nil, gomock.Any()).
- Return(nil, dupErr)
- mockUser := mocks.NewMockSysUserModel(ctrl)
- mockUser.EXPECT().FindOneByUsername(gomock.Any(), "admin_m5_code").
- Return(nil, userModel.ErrNotFound)
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
- Product: mockProduct,
- User: mockUser,
- })
- resp, err := NewCreateProductLogic(ctxhelper.SuperAdminCtx(), svcCtx).CreateProduct(&types.CreateProductReq{
- Code: "m5_code",
- Name: "M5 Product",
- })
- assert.Nil(t, resp)
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce), "必须是结构化 CodeError")
- assert.Equal(t, 409, ce.Code(),
- "M-5:任何 1062 都应统一返回 409;修复前不含 uk_code 的索引名会被吞成 500")
- assert.Contains(t, ce.Error(), "数据冲突",
- "错误消息应当是通用的'数据冲突,请稍后重试',不再尝试解析索引名文案")
- }
|