forked from baron/baron-sso
151 lines
4.0 KiB
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)
|
|
}
|