syncPermsCleanByProduct_r11_4_audit_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package pub
  2. import (
  3. "context"
  4. "fmt"
  5. "testing"
  6. "perms-system-server/internal/testutil"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "github.com/zeromicro/go-zero/core/stores/redis"
  10. )
  11. // ---------------------------------------------------------------------------
  12. // 覆盖目标:审计 L-R11-4 —— SyncPerms 纯新增(added>0 && updated==0 && disabled==0)
  13. // 不触发 UserDetailsLoader.CleanByProduct,避免把该产品下所有在线用户的 UD 缓存集体清空
  14. // 造成雪崩;updated/disabled 任一 > 0 时仍必须清。
  15. //
  16. // 用 Redis 层面的 productIndexKey(`<prefix>:ud:idx:p:<productCode>`)做间接观测:
  17. // - 测试前手工 SAdd 一个合成 cacheKey 到索引集合,建立"被 CleanByProduct 吃掉"的探针;
  18. // - 跑 ExecuteSyncPerms;
  19. // - 若 CleanByProduct 被调用,索引集合会被 SmembersCtx -> DelCtx 一并清空,Exists 返 0;
  20. // - 若未调用,合成 key 仍然在集合里,Exists 返 1。
  21. // ---------------------------------------------------------------------------
  22. func primeProductIndex(t *testing.T, rds *redis.Redis, cachePrefix, productCode string) string {
  23. t.Helper()
  24. idxKey := fmt.Sprintf("%s:ud:idx:p:%s", cachePrefix, productCode)
  25. canary := fmt.Sprintf("%s:ud:probe:%s", cachePrefix, testutil.UniqueId())
  26. _, err := rds.Sadd(idxKey, canary)
  27. require.NoError(t, err, "SAdd 到 productIndexKey 失败,Redis 不可用,测试前置条件失败")
  28. members, err := rds.Smembers(idxKey)
  29. require.NoError(t, err)
  30. require.Contains(t, members, canary, "primeProductIndex: canary 必须先出现在集合里")
  31. return idxKey
  32. }
  33. // TC-1064: L-R11-4 —— 纯新增不触发 CleanByProduct
  34. func TestSyncPerms_PureAddDoesNotTriggerCleanByProduct(t *testing.T) {
  35. ctx := context.Background()
  36. svcCtx := newTestSvcCtx()
  37. cfg := testutil.GetTestConfig()
  38. rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
  39. conn := testutil.GetTestSqlConn()
  40. pc := testutil.UniqueId()
  41. appKey := testutil.UniqueId()
  42. appSecret := testutil.UniqueId()
  43. _, cleanProduct := insertSyncTestProduct(t, ctx, pc, appKey, appSecret, 1)
  44. t.Cleanup(cleanProduct)
  45. t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_perm`", "productCode", pc) })
  46. idxKey := primeProductIndex(t, rds, cfg.CacheRedis.KeyPrefix, pc)
  47. t.Cleanup(func() { _, _ = rds.Del(idxKey) })
  48. result, err := ExecuteSyncPerms(ctx, svcCtx, appKey, appSecret, []SyncPermItem{
  49. {Code: "r11_4_add_a", Name: "A"},
  50. {Code: "r11_4_add_b", Name: "B"},
  51. {Code: "r11_4_add_c", Name: "C"},
  52. })
  53. require.NoError(t, err)
  54. require.NotNil(t, result)
  55. assert.Equal(t, int64(3), result.Added)
  56. assert.Equal(t, int64(0), result.Updated)
  57. assert.Equal(t, int64(0), result.Disabled)
  58. // 纯新增路径:CleanByProduct 不得被调用,索引集合必须仍保留 canary。
  59. exists, err := rds.Exists(idxKey)
  60. require.NoError(t, err)
  61. assert.True(t, exists,
  62. "L-R11-4:added=3 / updated=0 / disabled=0 属于纯新增,不得触发 CleanByProduct;"+
  63. "productIndexKey 若被删除说明 SyncPerms 仍在走全产品清缓存路径,回归:会把该产品"+
  64. "所有在线用户下一次请求同时打穿回 DB")
  65. }
  66. // TC-1065: L-R11-4 —— updated > 0 时必须触发 CleanByProduct
  67. func TestSyncPerms_UpdateTriggersCleanByProduct(t *testing.T) {
  68. ctx := context.Background()
  69. svcCtx := newTestSvcCtx()
  70. cfg := testutil.GetTestConfig()
  71. rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
  72. conn := testutil.GetTestSqlConn()
  73. pc := testutil.UniqueId()
  74. appKey := testutil.UniqueId()
  75. appSecret := testutil.UniqueId()
  76. _, cleanProduct := insertSyncTestProduct(t, ctx, pc, appKey, appSecret, 1)
  77. t.Cleanup(cleanProduct)
  78. t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_perm`", "productCode", pc) })
  79. // 第一次同步:纯新增,为随后的 update 打底;这次不触发 Clean,CleanByProduct 仍然可被后续触发。
  80. _, err := ExecuteSyncPerms(ctx, svcCtx, appKey, appSecret, []SyncPermItem{
  81. {Code: "r11_4_upd", Name: "OldName"},
  82. })
  83. require.NoError(t, err)
  84. idxKey := primeProductIndex(t, rds, cfg.CacheRedis.KeyPrefix, pc)
  85. t.Cleanup(func() { _, _ = rds.Del(idxKey) })
  86. // 第二次同步:同一 Code 改 Name → updated=1。
  87. result, err := ExecuteSyncPerms(ctx, svcCtx, appKey, appSecret, []SyncPermItem{
  88. {Code: "r11_4_upd", Name: "NewName"},
  89. })
  90. require.NoError(t, err)
  91. require.NotNil(t, result)
  92. assert.Equal(t, int64(1), result.Updated,
  93. "前置:同名 Code 改 Name 必须 updated=1,否则后续断言失去意义")
  94. exists, err := rds.Exists(idxKey)
  95. require.NoError(t, err)
  96. assert.False(t, exists,
  97. "L-R11-4:updated>0 必须触发 CleanByProduct;若 canary 仍在,说明 Logic 把"+
  98. "updated 情况也误归入'纯新增'分支,已存在 UD 缓存中的旧 perms 将长期对外返回")
  99. }
  100. // TC-1066: L-R11-4 —— disabled > 0 时必须触发 CleanByProduct
  101. func TestSyncPerms_DisableTriggersCleanByProduct(t *testing.T) {
  102. ctx := context.Background()
  103. svcCtx := newTestSvcCtx()
  104. cfg := testutil.GetTestConfig()
  105. rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
  106. conn := testutil.GetTestSqlConn()
  107. pc := testutil.UniqueId()
  108. appKey := testutil.UniqueId()
  109. appSecret := testutil.UniqueId()
  110. _, cleanProduct := insertSyncTestProduct(t, ctx, pc, appKey, appSecret, 1)
  111. t.Cleanup(cleanProduct)
  112. t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_perm`", "productCode", pc) })
  113. // 先注入两个 perm。
  114. _, err := ExecuteSyncPerms(ctx, svcCtx, appKey, appSecret, []SyncPermItem{
  115. {Code: "r11_4_keep", Name: "K"},
  116. {Code: "r11_4_drop", Name: "D"},
  117. })
  118. require.NoError(t, err)
  119. idxKey := primeProductIndex(t, rds, cfg.CacheRedis.KeyPrefix, pc)
  120. t.Cleanup(func() { _, _ = rds.Del(idxKey) })
  121. // 第二次只同步 r11_4_keep,r11_4_drop 会被 DisableNotInCodesWithTx 置 disabled。
  122. result, err := ExecuteSyncPerms(ctx, svcCtx, appKey, appSecret, []SyncPermItem{
  123. {Code: "r11_4_keep", Name: "K"},
  124. })
  125. require.NoError(t, err)
  126. assert.Equal(t, int64(1), result.Disabled,
  127. "前置:第二次只同步 keep,drop 必须被 disabled=1")
  128. exists, err := rds.Exists(idxKey)
  129. require.NoError(t, err)
  130. assert.False(t, exists,
  131. "L-R11-4:disabled>0 必须触发 CleanByProduct,否则已缓存的 UD.perms 里仍挂着"+
  132. "已禁用权限,权限网关会把不再有效的权限判为 allow,产生权限残留")
  133. }