package server import ( "context" "testing" productModel "perms-system-server/internal/model/product" pub "perms-system-server/internal/logic/pub" "perms-system-server/internal/testutil/mocks" "perms-system-server/pb" "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" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // --------------------------------------------------------------------------- // 审计 M-2(第 8 轮)—— PermServer.SyncPermissions gRPC 侧必须把 SyncPermsError{Code:404} // 映射为 codes.NotFound;此前落到 default 分支时会被统一为 codes.Internal,使接入方 SDK // 把"产品不存在"当作系统故障触发重试/告警。 // // TC 编号:TC-0981(404→NotFound),TC-0982(未识别 code 仍走 Internal 防止误收)。 // --------------------------------------------------------------------------- // TC-0981: gRPC 404 → codes.NotFound(配合 permserver.go:81 的 case 404 分支)。 func TestSyncPermissions_gRPC_LockByCodeTxNotFound_MapsToCodesNotFound(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret_grpc"), bcrypt.MinCost) require.NoError(t, err) mockProduct := mocks.NewMockSysProductModel(ctrl) mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_grpc_key"). Return(&productModel.SysProduct{ Id: 1, Code: "m2_grpc_prod", AppKey: "m2_grpc_key", AppSecret: string(hashedSecret), Status: 1, }, nil) // LockByCodeTx 命中 sqlx.ErrNotFound → service 内部构造 SyncPermsError{Code:404}。 mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_grpc_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}) srv := NewPermServer(svcCtx) _, err = srv.SyncPermissions(context.Background(), &pb.SyncPermissionsReq{ AppKey: "m2_grpc_key", AppSecret: "m2_secret_grpc", Perms: []*pb.PermItem{{Code: "p1", Name: "P1"}}, }) require.Error(t, err, "M-2:tx 内产品消失必须返回 gRPC 错误") st, ok := status.FromError(err) require.True(t, ok, "M-2:必须是 gRPC status.Error,不得为裸 error") assert.Equal(t, codes.NotFound, st.Code(), "M-2:SyncPermsError{Code:404} 必须映射为 codes.NotFound;若仍为 codes.Internal,"+ "说明 permserver.go 的 switch 缺少 case 404,接入方 SDK 会把业务未命中当作系统故障重试") assert.Equal(t, "产品不存在", st.Message(), "M-2:保留原始语义文案") } // TC-0982: 未映射的 SyncPermsError.Code(例如 500)必须继续落到 codes.Internal。 // 防御未来有人错误"兜底"把所有 SyncPermsError 全部变 NotFound。 func TestSyncPermissions_gRPC_UnmappedCode_StaysInternal(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret_grpc"), bcrypt.MinCost) require.NoError(t, err) mockProduct := mocks.NewMockSysProductModel(ctrl) mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_grpc_key2"). Return(&productModel.SysProduct{ Id: 1, Code: "m2_grpc_prod2", AppKey: "m2_grpc_key2", AppSecret: string(hashedSecret), Status: 1, }, nil) // 审计 M-R10-1:LockByCodeTx 拿到的行必须 Status=1 才能继续进入 diff 逻辑; // 否则会在事务内直接返回 SyncPermsError{Code:403},无法命中"未映射 code"这条路径。 mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_grpc_prod2"). Return(&productModel.SysProduct{Id: 1, Code: "m2_grpc_prod2", Status: 1}, nil) mockPerm := mocks.NewMockSysPermModel(ctrl) mockPerm.EXPECT().FindMapByProductCodeWithTx(gomock.Any(), gomock.Any(), "m2_grpc_prod2"). Return(nil, &pub.SyncPermsError{Code: 500, Message: "any low-level"}) 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}) srv := NewPermServer(svcCtx) _, err = srv.SyncPermissions(context.Background(), &pb.SyncPermissionsReq{ AppKey: "m2_grpc_key2", AppSecret: "m2_secret_grpc", Perms: []*pb.PermItem{{Code: "p1", Name: "P1"}}, }) require.Error(t, err) st, ok := status.FromError(err) require.True(t, ok) assert.Equal(t, codes.Internal, st.Code(), "M-2:未识别的 SyncPermsError.Code 必须仍落到 codes.Internal,不得被"+ "一刀切映射成 codes.NotFound 掩盖真正的系统故障") }