package redis_test import ( "fmt" "net" "os" "testing" "time" "apigo.cc/go/config" "apigo.cc/go/redis" ) type userInfo struct { ID int Name string Phone string Time time.Time } func TestMain(m *testing.M) { // 检查 Redis 是否可用,不可用则跳过测试 os.Setenv("REDIS_TEST", "redis://:@localhost:6379/2?timeout=100ms&logSlow=10us") conn, err := net.DialTimeout("tcp", "localhost:6379", 500*time.Millisecond) if err != nil { fmt.Println("Redis server is not running at localhost:6379, skipping redis tests.") os.Exit(0) } _ = conn.Close() _ = config.Load("redis", nil) os.Exit(m.Run()) } func TestBase(t *testing.T) { rd := redis.GetRedis("test", nil) if rd.Error != nil { t.Fatal("GetRedis error", rd.Error) } rd.DEL("redisName", "redisUser", "redisIDs") // 测试不存在的 Key r := rd.GET("redisNotExists") if r.Error != nil || r.String() != "" || r.Int() != 0 { t.Fatal("GET NotExists should return empty", r.Error, r.String(), r.Int()) } // 测试 EXISTS exists := rd.EXISTS("redisName") if exists { t.Fatal("EXISTS should be false") } // 测试 SET/GET rd.SET("redisName", "12345") if rd.GET("redisName").String() != "12345" { t.Fatal("GET mismatch") } // 测试 GETSET r = rd.GETSET("redisName", 12345) if r.String() != "12345" { t.Fatal("GETSET String mismatch", r.String()) } if rd.GET("redisName").Int() != 12345 { t.Fatal("Int conversion mismatch") } // 测试 Expire rd.SET("redisExpire", "val") rd.EXPIRE("redisExpire", 1) time.Sleep(1100 * time.Millisecond) if rd.EXISTS("redisExpire") { t.Fatal("Key should have expired") } // 测试 Struct 自动序列化 info := userInfo{ Name: "aaa", ID: 123, Time: time.Now().Truncate(time.Second), } rd.SET("redisUser", info) var ru userInfo rd.GET("redisUser").To(&ru) if ru.Name != info.Name || ru.ID != info.ID || !ru.Time.Equal(info.Time) { t.Fatalf("Struct mismatch: expected %+v, got %+v", info, ru) } // 测试 MSET/MGET rd.MSET("redisK1", "V1", "redisK2", "V2") results := rd.MGET("redisK1", "redisK2") if len(results) != 2 || results[0].String() != "V1" || results[1].String() != "V2" { t.Fatal("MGET mismatch") } // 清理 rd.DEL("redisName", "redisUser", "redisK1", "redisK2") } func TestIDMaker(t *testing.T) { rd := redis.GetRedis("test", nil) maker := redis.NewIDMaker(rd) // 测试生成唯一性 ids := make(map[string]bool) for i := 0; i < 200; i++ { id := maker.Get(10) if ids[id] { t.Fatalf("Duplicate ID generated: %s", id) } ids[id] = true } // 测试针对数据库优化的版本 idMysql := maker.GetForMysql(16) if len(idMysql) != 16 { t.Fatalf("Invalid MySQL ID length: %d", len(idMysql)) } idPg := maker.GetForPostgreSQL(16) if len(idPg) != 16 { t.Fatalf("Invalid PostgreSQL ID length: %d", len(idPg)) } } func TestGenerics(t *testing.T) { rd := redis.GetRedis("test", nil) rd.SET("gen_test", userInfo{Name: "Generics", ID: 888}) defer rd.DEL("gen_test") r := rd.GET("gen_test") user := redis.To[userInfo](r) if user.Name != "Generics" || user.ID != 888 { t.Fatal("Generics To[T] mismatch", user) } } func TestRetry(t *testing.T) { rd := redis.GetRedis("test", nil) rd.SET("retry_test", "ok") // 模拟连接失效(通过直接关闭底层连接池中的连接比较困难, // 我们这里通过执行一个非法的命令或直接调用 do 模拟) // 实际上 redis.Pool 已经处理了失效连接的弃用。 // 这里的 retry 主要是针对 Do 过程中的网络抖动。 // 为了真实测试 retry,我们可以获取连接后手动关闭它,然后再次调用 Do conn, _ := rd.GetConnection() _ = conn.Close() // 这里的 Close 只是归还连接池,不一定会导致报错,除非连接池已满或被标记为失效 // 验证重试逻辑:Do 方法内部会自动尝试 GetNewConnection r := rd.Do("GET", "retry_test") if r.Error != nil || r.String() != "ok" { t.Fatal("Retry failed", r.Error) } rd.DEL("retry_test") }