syncPermissions404_audit_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package server
  2. import (
  3. "context"
  4. "testing"
  5. productModel "perms-system-server/internal/model/product"
  6. pub "perms-system-server/internal/logic/pub"
  7. "perms-system-server/internal/testutil/mocks"
  8. "perms-system-server/pb"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. "github.com/zeromicro/go-zero/core/stores/sqlx"
  12. "go.uber.org/mock/gomock"
  13. "golang.org/x/crypto/bcrypt"
  14. "google.golang.org/grpc/codes"
  15. "google.golang.org/grpc/status"
  16. )
  17. // ---------------------------------------------------------------------------
  18. // 审计 M-2(第 8 轮)—— PermServer.SyncPermissions gRPC 侧必须把 SyncPermsError{Code:404}
  19. // 映射为 codes.NotFound;此前落到 default 分支时会被统一为 codes.Internal,使接入方 SDK
  20. // 把"产品不存在"当作系统故障触发重试/告警。
  21. //
  22. // TC 编号:TC-0981(404→NotFound),TC-0982(未识别 code 仍走 Internal 防止误收)。
  23. // ---------------------------------------------------------------------------
  24. // TC-0981: gRPC 404 → codes.NotFound(配合 permserver.go:81 的 case 404 分支)。
  25. func TestSyncPermissions_gRPC_LockByCodeTxNotFound_MapsToCodesNotFound(t *testing.T) {
  26. ctrl := gomock.NewController(t)
  27. defer ctrl.Finish()
  28. hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret_grpc"), bcrypt.MinCost)
  29. require.NoError(t, err)
  30. mockProduct := mocks.NewMockSysProductModel(ctrl)
  31. mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_grpc_key").
  32. Return(&productModel.SysProduct{
  33. Id: 1, Code: "m2_grpc_prod", AppKey: "m2_grpc_key",
  34. AppSecret: string(hashedSecret), Status: 1,
  35. }, nil)
  36. // LockByCodeTx 命中 sqlx.ErrNotFound → service 内部构造 SyncPermsError{Code:404}。
  37. mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_grpc_prod").
  38. Return((*productModel.SysProduct)(nil), sqlx.ErrNotFound)
  39. mockPerm := mocks.NewMockSysPermModel(ctrl)
  40. mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
  41. DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
  42. return fn(ctx, nil)
  43. })
  44. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Product: mockProduct, Perm: mockPerm})
  45. srv := NewPermServer(svcCtx)
  46. _, err = srv.SyncPermissions(context.Background(), &pb.SyncPermissionsReq{
  47. AppKey: "m2_grpc_key", AppSecret: "m2_secret_grpc",
  48. Perms: []*pb.PermItem{{Code: "p1", Name: "P1"}},
  49. })
  50. require.Error(t, err, "M-2:tx 内产品消失必须返回 gRPC 错误")
  51. st, ok := status.FromError(err)
  52. require.True(t, ok, "M-2:必须是 gRPC status.Error,不得为裸 error")
  53. assert.Equal(t, codes.NotFound, st.Code(),
  54. "M-2:SyncPermsError{Code:404} 必须映射为 codes.NotFound;若仍为 codes.Internal,"+
  55. "说明 permserver.go 的 switch 缺少 case 404,接入方 SDK 会把业务未命中当作系统故障重试")
  56. assert.Equal(t, "产品不存在", st.Message(), "M-2:保留原始语义文案")
  57. }
  58. // TC-0982: 未映射的 SyncPermsError.Code(例如 500)必须继续落到 codes.Internal。
  59. // 防御未来有人错误"兜底"把所有 SyncPermsError 全部变 NotFound。
  60. func TestSyncPermissions_gRPC_UnmappedCode_StaysInternal(t *testing.T) {
  61. ctrl := gomock.NewController(t)
  62. defer ctrl.Finish()
  63. hashedSecret, err := bcrypt.GenerateFromPassword([]byte("m2_secret_grpc"), bcrypt.MinCost)
  64. require.NoError(t, err)
  65. mockProduct := mocks.NewMockSysProductModel(ctrl)
  66. mockProduct.EXPECT().FindOneByAppKey(gomock.Any(), "m2_grpc_key2").
  67. Return(&productModel.SysProduct{
  68. Id: 1, Code: "m2_grpc_prod2", AppKey: "m2_grpc_key2",
  69. AppSecret: string(hashedSecret), Status: 1,
  70. }, nil)
  71. mockProduct.EXPECT().LockByCodeTx(gomock.Any(), gomock.Any(), "m2_grpc_prod2").
  72. Return(&productModel.SysProduct{Id: 1, Code: "m2_grpc_prod2"}, nil)
  73. mockPerm := mocks.NewMockSysPermModel(ctrl)
  74. mockPerm.EXPECT().FindMapByProductCodeWithTx(gomock.Any(), gomock.Any(), "m2_grpc_prod2").
  75. Return(nil, &pub.SyncPermsError{Code: 500, Message: "any low-level"})
  76. mockPerm.EXPECT().TransactCtx(gomock.Any(), gomock.Any()).
  77. DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
  78. return fn(ctx, nil)
  79. })
  80. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Product: mockProduct, Perm: mockPerm})
  81. srv := NewPermServer(svcCtx)
  82. _, err = srv.SyncPermissions(context.Background(), &pb.SyncPermissionsReq{
  83. AppKey: "m2_grpc_key2", AppSecret: "m2_secret_grpc",
  84. Perms: []*pb.PermItem{{Code: "p1", Name: "P1"}},
  85. })
  86. require.Error(t, err)
  87. st, ok := status.FromError(err)
  88. require.True(t, ok)
  89. assert.Equal(t, codes.Internal, st.Code(),
  90. "M-2:未识别的 SyncPermsError.Code 必须仍落到 codes.Internal,不得被"+
  91. "一刀切映射成 codes.NotFound 掩盖真正的系统故障")
  92. }