| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- package pub
- import (
- "context"
- "testing"
- productModel "perms-system-server/internal/model/product"
- "perms-system-server/internal/response"
- "perms-system-server/internal/testutil/mocks"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/stores/sqlx"
- "go.uber.org/mock/gomock"
- "golang.org/x/crypto/bcrypt"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计 M-2(第 8 轮)—— SyncPermsError{Code: 404} 必须被 REST 侧映射为 HTTP 404
- // "产品不存在"(ErrNotFound),而不是 default 分支里 err 的原文。
- //
- // 404 的触发条件:
- // 前置 FindOneByAppKey 已经拉到产品行(走通 401/403 校验),但事务内 LockByCodeTx 再查
- // 同一产品码时 sqlx.ErrNotFound —— 即事务打开前后并发有人把产品删了。目前仓库里没有
- // DeleteProduct Logic,该分支在生产里还到不了;但契约必须稳,否则将来加 DeleteProduct
- // 时前端/SDK 分类会错乱。
- //
- // 命名规则:TC-0979
- // ---------------------------------------------------------------------------
- // TC-0979: REST SyncPerms —— tx 内 LockByCodeTx ErrNotFound → HTTP 404
- func TestSyncPerms_LockByCodeTxNotFound_MapsToHTTP404(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret"), bcrypt.MinCost)
- require.NoError(t, err)
- mockProduct := mocks.NewMockSysProductModel(ctrl)
- mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_key").
- Return(&productModel.SysProduct{
- Id: 1, Code: "m2_prod", AppKey: "m2_key",
- AppSecret: string(hashedSecret), Status: 1,
- }, nil)
- // 关键:tx 内 LockByCodeTx 拿到 ErrNotFound → service 返回 SyncPermsError{Code:404, "产品不存在"}。
- mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_prod").
- Return((*productModel.SysProduct)(nil), sqlx.ErrNotFound)
- mockPerm := mocks.NewMockSysPermModel(ctrl)
- mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
- DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
- return fn(ctx, nil)
- })
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Product: mockProduct, Perm: mockPerm})
- logic := NewSyncPermsLogic(context.Background(), svcCtx)
- resp, err := logic.SyncPerms(&types.SyncPermsReq{
- AppKey: "m2_key", AppSecret: "m2_secret",
- Perms: []types.SyncPermItem{{Code: "p1", Name: "P1"}},
- })
- assert.Nil(t, resp)
- require.Error(t, err, "M-2:tx 内产品消失必须返回错误")
- var ce *response.CodeError
- require.ErrorAs(t, err, &ce,
- "M-2:必须映射成 response.CodeError 结构化错误,不能透传 SyncPermsError 原文")
- assert.Equal(t, 404, ce.Code(),
- "M-2:SyncPermsError{Code:404} 必须落到 HTTP 404 分支;若仍是 500 说明 syncPermsLogic 的 switch 缺少 404 case")
- assert.Equal(t, "产品不存在", ce.Error(), "M-2:保留原始语义文案")
- }
- // TC-0980(负值域对称):未映射的 se.Code(例如 500)依旧走 default,原样透传,不得被误收进 404。
- // 防御未来有人想"把所有 SyncPermsError 都按 404 处理"的随手改动。
- func TestSyncPerms_UnmappedSyncPermsErrCode_StillFallsThroughDefault(t *testing.T) {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret"), bcrypt.MinCost)
- require.NoError(t, err)
- mockProduct := mocks.NewMockSysProductModel(ctrl)
- mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_key2").
- Return(&productModel.SysProduct{
- Id: 1, Code: "m2_prod2", AppKey: "m2_key2",
- AppSecret: string(hashedSecret), Status: 1,
- }, nil)
- // 审计 M-R10-1:LockByCodeTx 拿到的行必须 Status=1 才能继续进入 diff 逻辑
- mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_prod2").
- Return(&productModel.SysProduct{Id: 1, Code: "m2_prod2", Status: 1}, nil)
- mockPerm := mocks.NewMockSysPermModel(ctrl)
- mockPerm.EXPECT().FindMapByProductCodeWithTx(gomock.Any(), gomock.Any(), "m2_prod2").
- Return(nil, assertAnyErr("internal storage bug"))
- mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
- DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
- return fn(ctx, nil)
- })
- svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Product: mockProduct, Perm: mockPerm})
- logic := NewSyncPermsLogic(context.Background(), svcCtx)
- _, err = logic.SyncPerms(&types.SyncPermsReq{
- AppKey: "m2_key2", AppSecret: "m2_secret",
- Perms: []types.SyncPermItem{{Code: "p1", Name: "P1"}},
- })
- require.Error(t, err)
- var se *SyncPermsError
- require.ErrorAs(t, err, &se, "M-2:未映射 code 走 default,原 SyncPermsError 被原样透传")
- assert.Equal(t, 500, se.Code, "M-2:500 必须保持 500 原语义,不得被误归类为 404")
- var ce *response.CodeError
- assert.False(t, assert.ObjectsAreEqual(err, ce),
- "M-2:500 分支绝不能被映射成 response.CodeError{Code:404}")
- }
- // assertAnyErr 构造任意错误,用来模拟 tx 内非业务分支错误。
- func assertAnyErr(msg string) error {
- return &localErr{s: msg}
- }
- type localErr struct{ s string }
- func (e *localErr) Error() string { return e.s }
|