Files
traefikoidc/internal/cache/backends/review_r177_redis_test.go
T
lukaszraczylo 4d23d618f5 fix(cache): harden cache backends, resilience, recovery, and transports
- RESP -ERR no longer closes healthy conn / aborts batch; global cap replacement-aware
- global health-check pass serialized (latent data race); MemoryMonitor via ResourceManager
- token-exchange retry, open-circuit rejections counted as failures
- TLS-posture matching transport reuse (no silent InsecureSkipVerify handoff)
2026-08-14 16:16:51 +01:00

80 lines
2.5 KiB
Go

package backends
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
)
// TestR177_RedisCommandErrorDoesNotCloseConn regresses the RESP command-error
// conflation: a Redis '-' (command error) reply, e.g. WRONGTYPE or OOM, is
// a valid protocol response — it does not mean the connection is dead.
// Previously Do and Pipeline treated it as an IO error, marking the healthy
// pooled connection closed (and for pipelines aborting the whole batch),
// discarding a perfectly reusable connection.
func TestR177_RedisCommandErrorDoesNotCloseConn(t *testing.T) {
mr := NewMiniredisServer(t)
config := &PoolConfig{
Address: mr.GetAddr(),
MaxConnections: 5,
ConnectTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
}
ctx := context.Background()
pool, err := NewConnectionPool(config)
require.NoError(t, err)
defer pool.Close()
conn, err := pool.Get(ctx)
require.NoError(t, err)
defer pool.Put(conn)
t.Run("single command error leaves conn healthy", func(t *testing.T) {
mr.SetError("ERR artificial") // every command returns '-ERR artificial'
_, err := conn.Do("GET", "k")
require.Error(t, err)
require.True(t, errors.Is(err, ErrCommandReply),
"expected a Redis command-reply error, got %v", err)
mr.SetError("") // clear; the connection must still be reusable
// On the buggy path Do marked the conn closed above, so this
// PING fails with "connection closed"; on the fixed path the
// conn is untouched and PING succeeds.
resp, err := conn.Do("PING")
require.NoError(t, err)
require.Equal(t, "PONG", resp)
})
t.Run("pipeline command error reads full batch and keeps conn", func(t *testing.T) {
mr.SetError("ERR artificial")
p := conn.NewPipeline()
p.Queue("SET", "k1", "v1")
p.Queue("SET", "k2", "v2")
// On the buggy path Execute returned after the first error (1
// response) and closed the conn; on the fixed path all replies
// are read (healthy conn), the batch is not aborted.
responses, err := p.Execute()
require.NoError(t, err, "batch must complete with per-command errors")
require.Len(t, responses, 2)
for i, r := range responses {
er, ok := r.(error)
require.True(t, ok, "response %d should carry an error value", i)
require.True(t, errors.Is(er, ErrCommandReply), "response %d should be a command-reply error", i)
}
mr.SetError("")
resp, err := conn.Do("PING")
require.NoError(t, err)
require.Equal(t, "PONG", resp)
})
}