2026-05-03 08:43:23 +08:00
|
|
|
|
package redis_test
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-05-03 12:01:59 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
|
"net"
|
2026-05-03 08:43:23 +08:00
|
|
|
|
"os"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"apigo.cc/go/config"
|
|
|
|
|
|
"apigo.cc/go/redis"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type userInfo struct {
|
2026-05-04 00:46:17 +08:00
|
|
|
|
ID int
|
2026-05-03 08:43:23 +08:00
|
|
|
|
Name string
|
|
|
|
|
|
Phone string
|
|
|
|
|
|
Time time.Time
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
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()
|
|
|
|
|
|
|
2026-05-03 08:43:23 +08:00
|
|
|
|
_ = config.Load("redis", nil)
|
2026-05-03 12:01:59 +08:00
|
|
|
|
os.Exit(m.Run())
|
|
|
|
|
|
}
|
2026-05-03 08:43:23 +08:00
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
func TestBase(t *testing.T) {
|
2026-05-03 08:43:23 +08:00
|
|
|
|
rd := redis.GetRedis("test", nil)
|
|
|
|
|
|
if rd.Error != nil {
|
|
|
|
|
|
t.Fatal("GetRedis error", rd.Error)
|
|
|
|
|
|
}
|
2026-05-04 00:46:17 +08:00
|
|
|
|
rd.DEL("redisName", "redisUser", "redisIDs")
|
2026-05-03 08:43:23 +08:00
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试不存在的 Key
|
2026-05-03 08:43:23 +08:00
|
|
|
|
r := rd.GET("redisNotExists")
|
2026-05-03 12:01:59 +08:00
|
|
|
|
if r.Error != nil || r.String() != "" || r.Int() != 0 {
|
|
|
|
|
|
t.Fatal("GET NotExists should return empty", r.Error, r.String(), r.Int())
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 EXISTS
|
2026-05-03 08:43:23 +08:00
|
|
|
|
exists := rd.EXISTS("redisName")
|
|
|
|
|
|
if exists {
|
|
|
|
|
|
t.Fatal("EXISTS should be false")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 SET/GET
|
2026-05-03 08:43:23 +08:00
|
|
|
|
rd.SET("redisName", "12345")
|
2026-05-03 12:01:59 +08:00
|
|
|
|
if rd.GET("redisName").String() != "12345" {
|
|
|
|
|
|
t.Fatal("GET mismatch")
|
|
|
|
|
|
}
|
2026-05-03 08:43:23 +08:00
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 GETSET
|
2026-05-03 08:43:23 +08:00
|
|
|
|
r = rd.GETSET("redisName", 12345)
|
|
|
|
|
|
if r.String() != "12345" {
|
|
|
|
|
|
t.Fatal("GETSET String mismatch", r.String())
|
|
|
|
|
|
}
|
2026-05-03 12:01:59 +08:00
|
|
|
|
if rd.GET("redisName").Int() != 12345 {
|
|
|
|
|
|
t.Fatal("Int conversion mismatch")
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 Expire
|
|
|
|
|
|
rd.SET("redisExpire", "val")
|
|
|
|
|
|
rd.EXPIRE("redisExpire", 1)
|
|
|
|
|
|
time.Sleep(1100 * time.Millisecond)
|
|
|
|
|
|
if rd.EXISTS("redisExpire") {
|
|
|
|
|
|
t.Fatal("Key should have expired")
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 Struct 自动序列化
|
2026-05-03 08:43:23 +08:00
|
|
|
|
info := userInfo{
|
|
|
|
|
|
Name: "aaa",
|
2026-05-04 00:46:17 +08:00
|
|
|
|
ID: 123,
|
2026-05-03 12:01:59 +08:00
|
|
|
|
Time: time.Now().Truncate(time.Second),
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
rd.SET("redisUser", info)
|
|
|
|
|
|
var ru userInfo
|
2026-05-03 12:01:59 +08:00
|
|
|
|
rd.GET("redisUser").To(&ru)
|
2026-05-04 00:46:17 +08:00
|
|
|
|
if ru.Name != info.Name || ru.ID != info.ID || !ru.Time.Equal(info.Time) {
|
2026-05-03 08:43:23 +08:00
|
|
|
|
t.Fatalf("Struct mismatch: expected %+v, got %+v", info, ru)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试 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")
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
2026-05-03 12:01:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 清理
|
|
|
|
|
|
rd.DEL("redisName", "redisUser", "redisK1", "redisK2")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-04 00:46:17 +08:00
|
|
|
|
func TestIDMaker(t *testing.T) {
|
2026-05-03 12:01:59 +08:00
|
|
|
|
rd := redis.GetRedis("test", nil)
|
2026-05-04 00:46:17 +08:00
|
|
|
|
maker := redis.NewIDMaker(rd)
|
2026-05-03 12:01:59 +08:00
|
|
|
|
|
|
|
|
|
|
// 测试生成唯一性
|
|
|
|
|
|
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
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-03 12:01:59 +08:00
|
|
|
|
// 测试针对数据库优化的版本
|
|
|
|
|
|
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))
|
2026-05-03 08:43:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestGenerics(t *testing.T) {
|
|
|
|
|
|
rd := redis.GetRedis("test", nil)
|
2026-05-04 00:46:17 +08:00
|
|
|
|
rd.SET("gen_test", userInfo{Name: "Generics", ID: 888})
|
2026-05-03 08:43:23 +08:00
|
|
|
|
defer rd.DEL("gen_test")
|
|
|
|
|
|
|
|
|
|
|
|
r := rd.GET("gen_test")
|
|
|
|
|
|
user := redis.To[userInfo](r)
|
2026-05-04 00:46:17 +08:00
|
|
|
|
if user.Name != "Generics" || user.ID != 888 {
|
2026-05-03 08:43:23 +08:00
|
|
|
|
t.Fatal("Generics To[T] mismatch", user)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-03 12:01:59 +08:00
|
|
|
|
|
2026-05-04 00:46:17 +08:00
|
|
|
|
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")
|
|
|
|
|
|
}
|
|
|
|
|
|
|