package logic import ( "testing" "time" "github.com/gogf/gf/v2/os/gtime" "tool-api/internal/consts" "tool-api/internal/model/entity" ) // TestCountActiveMembers_PermanentCounted 看板「有效会员」必须计入永久会员 // (level_expire_at = NULL,管理员手动分配)。这是本次修复(#24)的核心回归点: // 旧 SQL 用 `level_expire_at IS NOT NULL` 把永久会员全部漏掉了。 func TestCountActiveMembers_PermanentCounted(t *testing.T) { future := gtime.New(time.Now().Add(24 * time.Hour)) past := gtime.New(time.Now().Add(-24 * time.Hour)) users := []*entity.Users{ {LevelKey: "v2", LevelExpireAt: nil}, // ① 永久(NULL)→ 应计入 {LevelKey: "v2", LevelExpireAt: future}, // ② 未过期 → 应计入 {LevelKey: "v2", LevelExpireAt: past}, // ③ 已过期 → 回落默认等级,不计入 } if got := countActiveMembers(users); got != 2 { t.Fatalf("countActiveMembers=%d want 2", got) } } // TestLevelDistribution_EffectiveKey 等级分布按「生效等级」分组: // 已过期用户必须落入默认等级桶,而不是其原始 level_key。 func TestLevelDistribution_EffectiveKey(t *testing.T) { future := gtime.New(time.Now().Add(24 * time.Hour)) past := gtime.New(time.Now().Add(-24 * time.Hour)) users := []*entity.Users{ {LevelKey: "v2", LevelExpireAt: nil}, {LevelKey: "v2", LevelExpireAt: future}, {LevelKey: "v2", LevelExpireAt: past}, } dist := levelDistribution(users) if dist["v2"] != 2 { t.Fatalf("v2 桶=%d want 2 (%v)", dist["v2"], dist) } if dist[consts.DefaultLevelKey] != 1 { t.Fatalf("默认等级桶=%d want 1 (%v)", dist[consts.DefaultLevelKey], dist) } } // TestCountActiveMembers_DefaultExcluded 默认等级(含空 key)不算有效会员。 func TestCountActiveMembers_DefaultExcluded(t *testing.T) { users := []*entity.Users{ {LevelKey: consts.DefaultLevelKey, LevelExpireAt: nil}, {LevelKey: "", LevelExpireAt: nil}, } if got := countActiveMembers(users); got != 0 { t.Fatalf("countActiveMembers=%d want 0", got) } if n := countActiveMembers(nil); n != 0 { t.Fatalf("nil 应返回 0,got %d", n) } } // TestValidateLevelKeyUnchanged 编辑等级时 level_key 是跨端契约字段,不允许变更(T01.6)。 // 服务端闸门:前端把输入框 disabled 只是体验层,这里固化「变更即报错」的行为, // 取消失败回归(例如未来有人误把 level_key 写进编辑 data map)。 func TestValidateLevelKeyUnchanged(t *testing.T) { // 未变更 → 放行 if err := validateLevelKeyUnchanged("v2", "v2"); err != nil { t.Fatalf("相同 level_key 应放行,got err=%v", err) } // 变更 → 报错 if err := validateLevelKeyUnchanged("v2", "v3"); err == nil { t.Fatalf("变更 level_key 应报错,got nil") } // 清空(旧展开式回填的典型故障)→ 报错 if err := validateLevelKeyUnchanged("v2", ""); err == nil { t.Fatalf("清空 level_key 应报错,got nil") } }