package loaders import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // --------------------------------------------------------------------------- // 覆盖目标:审计第 6 轮 M-1 修复回归 —— UserDetailsLoader.CleanByUserIds // * 对多个 userId 的所有产品下缓存 + 用户索引 key 必须整体删除; // * 对空输入必须立即返回,不打 Redis; // * 基于 SUNION + 批 DEL,单次 RTT 常数,调用方(UpdateDept)调一次即可清完。 // --------------------------------------------------------------------------- // TC-0846: 预埋 3 用户 × 2 产品的缓存,CleanByUserIds 一次性清光所有 ud: key 与 idx: key。 func TestCleanByUserIds_WipesAllUserProductKeysAndIndexes(t *testing.T) { rds := testRedis() loader := newTestLoader() ctx := context.Background() type cell struct { uid int64 pc string } cells := []cell{ {1000001, "pcX"}, {1000001, "pcY"}, {1000002, "pcX"}, {1000002, "pcY"}, {1000003, "pcX"}, {1000003, "pcY"}, } // 预埋缓存:每个 cell 写一条 value 到 cacheKey,并 SADD 到 user / product 索引。 cacheKeys := make([]string, 0, len(cells)) for _, c := range cells { ck := loader.cacheKey(c.uid, c.pc) require.NoError(t, rds.SetCtx(ctx, ck, "dummy")) _, _ = rds.SaddCtx(ctx, loader.userIndexKey(c.uid), ck) _, _ = rds.SaddCtx(ctx, loader.productIndexKey(c.pc), ck) cacheKeys = append(cacheKeys, ck) } // 调用 CleanByUserIds 触发 SUNION + 批 DEL。 loader.CleanByUserIds(ctx, []int64{1000001, 1000002, 1000003}) // 6 条 ud: key 必须全消失。 for _, ck := range cacheKeys { exist, err := rds.ExistsCtx(ctx, ck) require.NoError(t, err) assert.False(t, exist, "M-1:cacheKey %s 必须被清理", ck) } // 3 条 user 索引 key 必须也被清掉(否则会漏缓存)。 for _, uid := range []int64{1000001, 1000002, 1000003} { exist, err := rds.ExistsCtx(ctx, loader.userIndexKey(uid)) require.NoError(t, err) assert.False(t, exist, "M-1:user 索引集合必须被 DEL,否则下次 Clean 会复活假指针") } // 清理 product 索引残留(修复 SLA 不负责 product 索引,其残留 key 已在 user 索引里一并清掉 // 的那一组;但为了测试幂等性,手动 cleanup)。 t.Cleanup(func() { _, _ = rds.DelCtx(ctx, loader.productIndexKey("pcX"), loader.productIndexKey("pcY")) }) } // TC-0847: 空 ids 切片必须直接返回,不打 Redis。 // 如果源码退化成把空 SUNION 交给 Redis,会收到 "SUNION wrong number of arguments" 错误; // 我们通过断言 Redis 未产生任何错误以及函数未 panic 来验证。 func TestCleanByUserIds_EmptyIds_NoOp(t *testing.T) { loader := newTestLoader() // 只要不 panic、返回即可;如果源码 foundation 有 wrong-args 会 logx.Errorf 输出, // 这里做最小断言:调用返回控制权。 loader.CleanByUserIds(context.Background(), nil) loader.CleanByUserIds(context.Background(), []int64{}) // 若走到了 SUNION 分支,Redis 会在 wrong-args 下被 logx 记 Errorf, // 业务回调仍然返回,此时不应 panic;通过到达本行说明 OK。 }