1
0
forked from baron/baron-sso
Files
baron-sso/backend/internal/service/redis_service_test.go

151 lines
4.0 KiB
Go

package service
import (
"context"
"encoding/json"
"os"
"testing"
"time"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/require"
)
type redisCommandStub struct {
scans map[string][]string
stateValue string
deleted []string
}
func (h *redisCommandStub) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
return ctx, nil
}
func (h *redisCommandStub) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
switch cmd.Name() {
case "ping":
if status, ok := cmd.(*redis.StatusCmd); ok {
status.SetVal("PONG")
}
case "scan":
if scan, ok := cmd.(*redis.ScanCmd); ok {
scan.SetVal(h.scans[scanPattern(cmd.Args())], 0)
}
case "get":
if str, ok := cmd.(*redis.StringCmd); ok {
if h.stateValue == "" {
str.SetErr(redis.Nil)
return nil
}
str.SetVal(h.stateValue)
}
case "del":
args := cmd.Args()
keys := make([]string, 0, len(args)-1)
for _, arg := range args[1:] {
keys = append(keys, arg.(string))
}
h.deleted = append(h.deleted, keys...)
if count, ok := cmd.(*redis.IntCmd); ok {
count.SetVal(int64(len(keys)))
}
}
cmd.SetErr(nil)
return nil
}
func (h *redisCommandStub) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
return ctx, nil
}
func (h *redisCommandStub) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
return nil
}
func scanPattern(args []interface{}) string {
for index := 0; index < len(args)-1; index++ {
value, ok := args[index].(string)
if ok && value == "match" {
if pattern, ok := args[index+1].(string); ok {
return pattern
}
}
}
return ""
}
func newStubbedRedisService(stub *redisCommandStub) *RedisService {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:1",
MaxRetries: -1,
})
client.AddHook(stub)
return &RedisService{Client: client}
}
func TestRedisServiceGetIdentityCacheStatusReadsStateAndCountsCacheKeys(t *testing.T) {
now := time.Date(2026, 6, 9, 3, 20, 0, 0, time.UTC)
state, err := json.Marshal(identityMirrorStateStore{
Status: "ready",
LastRefreshedAt: &now,
ObservedCount: 42,
UpdatedAt: &now,
})
require.NoError(t, err)
stub := &redisCommandStub{
stateValue: string(state),
scans: map[string][]string{
"identity:mirror:*": {"identity:mirror:state", "identity:mirror:user:1"},
"identity:index:*": {"identity:index:email:a", "identity:mirror:user:1"},
},
}
service := newStubbedRedisService(stub)
status, err := service.GetIdentityCacheStatus(context.Background())
require.NoError(t, err)
require.Equal(t, "ready", status.Status)
require.True(t, status.RedisReady)
require.Equal(t, int64(42), status.ObservedCount)
require.Equal(t, int64(3), status.KeyCount)
require.Equal(t, &now, status.LastRefreshedAt)
require.Equal(t, &now, status.UpdatedAt)
}
func TestRedisServiceFlushIdentityCacheDeletesOnlyIdentityMirrorAndIndexKeys(t *testing.T) {
stub := &redisCommandStub{
scans: map[string][]string{
"identity:mirror:*": {"identity:mirror:state", "identity:mirror:user:1"},
"identity:index:*": {"identity:index:email:a", "identity:mirror:user:1"},
},
}
service := newStubbedRedisService(stub)
result, err := service.FlushIdentityCache(context.Background())
require.NoError(t, err)
require.Equal(t, "success", result.Status)
require.Equal(t, int64(3), result.FlushedKeys)
require.ElementsMatch(t, []string{
"identity:mirror:state",
"identity:mirror:user:1",
"identity:index:email:a",
}, stub.deleted)
}
func TestRedisServiceGetIdentityCacheStatusReturnsUnavailableWithoutClient(t *testing.T) {
status, err := (*RedisService)(nil).GetIdentityCacheStatus(context.Background())
require.NoError(t, err)
require.Equal(t, "unavailable", status.Status)
require.False(t, status.RedisReady)
require.NotEmpty(t, status.LastError)
}
func TestRedisServiceFlushIdentityCacheFailsWithoutClient(t *testing.T) {
_, err := (*RedisService)(nil).FlushIdentityCache(context.Background())
require.ErrorIs(t, err, os.ErrInvalid)
}