| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- 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。
- }
|