445 lines
10 KiB
Go
445 lines
10 KiB
Go
|
|
package manager
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
"wallet-pay-api/common/global"
|
||
|
|
"wallet-pay-api/common/model"
|
||
|
|
"wallet-pay-api/pkg/loggerx"
|
||
|
|
|
||
|
|
"github.com/redis/go-redis/v9"
|
||
|
|
)
|
||
|
|
|
||
|
|
func newTestRedisCache() *CacheRedis {
|
||
|
|
// 支持通过环境变量覆盖 redis 地址
|
||
|
|
addr := os.Getenv("TEST_REDIS_ADDR")
|
||
|
|
if addr == "" {
|
||
|
|
addr = "10.40.92.54:30379"
|
||
|
|
}
|
||
|
|
client := redis.NewClient(&redis.Options{
|
||
|
|
Addr: addr,
|
||
|
|
Password: "123000",
|
||
|
|
DB: 0,
|
||
|
|
})
|
||
|
|
return NewCacheRedis(context.Background(), client, "test")
|
||
|
|
}
|
||
|
|
func TestBatchGetRedis(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
keys := []string{"k1", "k2", "k3"}
|
||
|
|
resp, err := BatchGetRedis[model.YuebaoUserAsset](cache, ctx, keys)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
t.Logf("%+v", resp)
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheRedis_Types(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
// int
|
||
|
|
err := cache.Set(ctx, "int", 123, time.Second*10, []int64{123})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var i int
|
||
|
|
err = cache.Get(ctx, "int", &i)
|
||
|
|
if err != nil || i != 123 {
|
||
|
|
t.Errorf("int get failed: %v, %d", err, i)
|
||
|
|
}
|
||
|
|
// string
|
||
|
|
err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{124})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "str", &s)
|
||
|
|
if err != nil || s != "abc" {
|
||
|
|
t.Errorf("string get failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
// struct
|
||
|
|
st := TestStruct{Name: "foo"}
|
||
|
|
err = cache.Set(ctx, "struct", st, time.Second*10, []int64{125})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var st2 TestStruct
|
||
|
|
err = cache.Get(ctx, "struct", &st2)
|
||
|
|
if err != nil || st2.Name != "foo" {
|
||
|
|
t.Errorf("struct get failed: %v, %+v", err, st2)
|
||
|
|
}
|
||
|
|
// map
|
||
|
|
m := map[string]int{"a": 1}
|
||
|
|
err = cache.Set(ctx, "map", m, time.Second*10, []int64{126})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var m2 map[string]int
|
||
|
|
err = cache.Get(ctx, "map", &m2)
|
||
|
|
if err != nil || m2["a"] != 1 {
|
||
|
|
t.Errorf("map get failed: %v, %+v", err, m2)
|
||
|
|
}
|
||
|
|
// slice
|
||
|
|
sl := []string{"x", "y"}
|
||
|
|
err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{127})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var sl2 []string
|
||
|
|
err = cache.Get(ctx, "slice", &sl2)
|
||
|
|
if err != nil || len(sl2) != 2 || sl2[0] != "x" {
|
||
|
|
t.Errorf("slice get failed: %v, %+v", err, sl2)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheRedis_Expire(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
err := cache.Set(ctx, "expire", "v", time.Millisecond*200, []int64{128})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "expire", &s)
|
||
|
|
if err != nil || s != "v" {
|
||
|
|
t.Errorf("expire get failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
time.Sleep(time.Millisecond * 250)
|
||
|
|
err = cache.Get(ctx, "expire", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should expire, got: %v", s)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheRedis_Del(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
err := cache.Set(ctx, "del", 1, time.Second*10, []int64{129})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
err = cache.Del(ctx, "del")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var i int
|
||
|
|
err = cache.Get(ctx, "del", &i)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should not get deleted key")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheRedis_TypeMismatch(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{123})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "mismatch", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should type mismatch")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheRedis_BatchGet(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
cache.Set(ctx, "ba", 1, time.Second*10, []int64{1})
|
||
|
|
cache.Set(ctx, "bb", 2, time.Second*10, []int64{2})
|
||
|
|
cache.Set(ctx, "bc", 3, time.Second*10, []int64{3})
|
||
|
|
keys := []string{"bj", "ba", "bb", "bx"}
|
||
|
|
m, err := BatchGetRedis[int](cache, ctx, keys)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
b, _ := json.Marshal(m)
|
||
|
|
fmt.Println(string(b))
|
||
|
|
if len(m) != 2 || *m["ba"] != 1 || *m["bb"] != 2 {
|
||
|
|
t.Errorf("batch get failed: %+v", m)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheManager_Redis_Generic(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := newTestRedisCache()
|
||
|
|
m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 })
|
||
|
|
data := TestStruct{Name: "bar"}
|
||
|
|
err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got, err := m.Get(ctx, "k1")
|
||
|
|
if err != nil || got.Name != "bar" {
|
||
|
|
t.Errorf("manager redis get failed: %v, %+v", err, got)
|
||
|
|
}
|
||
|
|
err = m.Del(ctx, "k1")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got, err = m.Get(ctx, "k1")
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("manager redis del failed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestICacheInterface(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
var cache ICache = NewCacheLocal(time.Second * 10)
|
||
|
|
err := cache.Set(ctx, "iface", "val", time.Second*10, []int64{130})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "iface", &s)
|
||
|
|
if err != nil || s != "val" {
|
||
|
|
t.Errorf("iface get failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
err = cache.Del(ctx, "iface")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
err = cache.Get(ctx, "iface", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("iface del failed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheManager_Generic(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
m := NewCacheManager[TestStruct](cache, "pref", func(t *TestStruct) int64 { return 0 })
|
||
|
|
data := TestStruct{Name: "bar"}
|
||
|
|
err := m.Set(ctx, "k1", &data, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got, err := m.Get(ctx, "k1")
|
||
|
|
if err != nil || got.Name != "bar" {
|
||
|
|
t.Errorf("manager get failed: %v, %+v", err, got)
|
||
|
|
}
|
||
|
|
err = m.Del(ctx, "k1")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
got, err = m.Get(ctx, "k1")
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("manager del failed")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_Boundary(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
|
||
|
|
// 空 key
|
||
|
|
err := cache.Set(ctx, "", "v", time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "", &s)
|
||
|
|
if err != nil || s != "v" {
|
||
|
|
t.Errorf("empty key failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
// nil value
|
||
|
|
err = cache.Set(ctx, "nil", nil, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var x any
|
||
|
|
err = cache.Get(ctx, "nil", &x)
|
||
|
|
if err != nil || x != nil {
|
||
|
|
t.Errorf("nil value failed: %v, %v", err, x)
|
||
|
|
}
|
||
|
|
// 极短 ttl
|
||
|
|
err = cache.Set(ctx, "short", "v", time.Millisecond*1, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
time.Sleep(time.Millisecond * 5)
|
||
|
|
err = cache.Get(ctx, "short", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("short ttl should expire")
|
||
|
|
}
|
||
|
|
// 重复 set
|
||
|
|
err = cache.Set(ctx, "dup", "v1", time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
err = cache.Set(ctx, "dup", "v2", time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
err = cache.Get(ctx, "dup", &s)
|
||
|
|
if err != nil || s != "v2" {
|
||
|
|
t.Errorf("dup set failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
// Stop 后行为
|
||
|
|
cache.Stop()
|
||
|
|
// Stop 后再次 Stop 不应 panic
|
||
|
|
cache.Stop()
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_Concurrent(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
|
||
|
|
n := 100
|
||
|
|
done := make(chan struct{}, n*2)
|
||
|
|
for i := 0; i < n; i++ {
|
||
|
|
go func(i int) {
|
||
|
|
key := "k" + string(rune(i))
|
||
|
|
cache.Set(ctx, key, i, time.Second*10, []int64{int64(i)})
|
||
|
|
done <- struct{}{}
|
||
|
|
}(i)
|
||
|
|
go func(i int) {
|
||
|
|
key := "k" + string(rune(i))
|
||
|
|
var v int
|
||
|
|
cache.Get(ctx, key, &v)
|
||
|
|
done <- struct{}{}
|
||
|
|
}(i)
|
||
|
|
}
|
||
|
|
for i := 0; i < n*2; i++ {
|
||
|
|
<-done
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_Types(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
// int
|
||
|
|
err := cache.Set(ctx, "int", 123, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var i int
|
||
|
|
err = cache.Get(ctx, "int", &i)
|
||
|
|
if err != nil || i != 123 {
|
||
|
|
t.Errorf("int get failed: %v, %d", err, i)
|
||
|
|
}
|
||
|
|
// string
|
||
|
|
err = cache.Set(ctx, "str", "abc", time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "str", &s)
|
||
|
|
if err != nil || s != "abc" {
|
||
|
|
t.Errorf("string get failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
// struct
|
||
|
|
st := TestStruct{Name: "foo"}
|
||
|
|
err = cache.Set(ctx, "struct", st, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var st2 TestStruct
|
||
|
|
err = cache.Get(ctx, "struct", &st2)
|
||
|
|
if err != nil || st2.Name != "foo" {
|
||
|
|
t.Errorf("struct get failed: %v, %+v", err, st2)
|
||
|
|
}
|
||
|
|
// map
|
||
|
|
m := map[string]int{"a": 1}
|
||
|
|
err = cache.Set(ctx, "map", m, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var m2 map[string]int
|
||
|
|
err = cache.Get(ctx, "map", &m2)
|
||
|
|
if err != nil || m2["a"] != 1 {
|
||
|
|
t.Errorf("map get failed: %v, %+v", err, m2)
|
||
|
|
}
|
||
|
|
// slice
|
||
|
|
sl := []string{"x", "y"}
|
||
|
|
err = cache.Set(ctx, "slice", sl, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var sl2 []string
|
||
|
|
err = cache.Get(ctx, "slice", &sl2)
|
||
|
|
if err != nil || len(sl2) != 2 || sl2[0] != "x" {
|
||
|
|
t.Errorf("slice get failed: %v, %+v", err, sl2)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_Expire(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Millisecond * 50)
|
||
|
|
|
||
|
|
err := cache.Set(ctx, "expire", "v", time.Millisecond*100, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "expire", &s)
|
||
|
|
if err != nil || s != "v" {
|
||
|
|
t.Errorf("expire get failed: %v, %s", err, s)
|
||
|
|
}
|
||
|
|
time.Sleep(time.Millisecond * 120)
|
||
|
|
err = cache.Get(ctx, "expire", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should expire, got: %v", s)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_Del(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
err := cache.Set(ctx, "del", 1, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
err = cache.Del(ctx, "del")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var i int
|
||
|
|
err = cache.Get(ctx, "del", &i)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should not get deleted key")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_TypeMismatch(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
err := cache.Set(ctx, "mismatch", 123, time.Second*10, []int64{0})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
var s string
|
||
|
|
err = cache.Get(ctx, "mismatch", &s)
|
||
|
|
if err == nil {
|
||
|
|
t.Errorf("should type mismatch")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCacheLocal_BatchGet(t *testing.T) {
|
||
|
|
ctx := context.Background()
|
||
|
|
cache := NewCacheLocal(time.Second * 10)
|
||
|
|
|
||
|
|
cache.Set(ctx, "a", 1, time.Second*10, []int64{0})
|
||
|
|
cache.Set(ctx, "b", 2, time.Second*10, []int64{0})
|
||
|
|
cache.Set(ctx, "c", 3, time.Second*10, []int64{0})
|
||
|
|
keys := []string{"a", "b", "x"}
|
||
|
|
m, err := BatchGetLocal[int](cache, ctx, keys)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if len(m) != 2 || *m["a"] != 1 || *m["b"] != 2 {
|
||
|
|
t.Errorf("batch get failed: %+v", m)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type TestStruct struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
}
|