proxy-pool/internal/adapters/redisprovider/adapter_test.go

151 lines
4.9 KiB
Go

package redisprovider
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/redis/go-redis/v9"
controllerProvider "proxy-pool/internal/controller/provider"
)
func TestNewRejectsInvalidDependenciesAndOptions(t *testing.T) {
t.Parallel()
client := redis.NewClient(&redis.Options{Addr: "127.0.0.1:0"})
t.Cleanup(func() { _ = client.Close() })
valid := Options{
Namespace: "controller", HolderID: "controller-a", LeaseTTL: 3 * time.Second,
RenewEvery: time.Second, RetryInterval: 50 * time.Millisecond, PermitGrace: time.Second,
}
var typedNil *redis.Client
tests := []struct {
name string
client redis.Scripter
options Options
}{
{name: "nil client", options: valid},
{name: "typed nil client", client: typedNil, options: valid},
{name: "empty namespace", client: client, options: withNamespace(valid, "")},
{name: "unsafe namespace", client: client, options: withNamespace(valid, "bad:value")},
{name: "empty holder", client: client, options: withHolder(valid, "")},
{name: "zero lease", client: client, options: withLeaseTTL(valid, 0)},
{name: "renew exceeds third", client: client, options: withRenewEvery(valid, 2*time.Second)},
{name: "zero retry", client: client, options: withRetryInterval(valid, 0)},
{name: "negative grace", client: client, options: withPermitGrace(valid, -1)},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
adapter, err := New(test.client, test.options)
if err == nil || adapter != nil {
t.Fatalf("New() = (%v, %v), want nil adapter and error", adapter, err)
}
})
}
}
func TestNewBuildsPerUpstreamClusterSafeKeys(t *testing.T) {
t.Parallel()
client := redis.NewClient(&redis.Options{Addr: "127.0.0.1:0"})
t.Cleanup(func() { _ = client.Close() })
adapter, err := New(client, Options{
Namespace: " controller ", HolderID: "controller-a", LeaseTTL: 3 * time.Second,
RenewEvery: time.Second, RetryInterval: 50 * time.Millisecond, PermitGrace: time.Second,
})
if err != nil {
t.Fatalf("New(): %v", err)
}
keys, err := adapter.keys.forUpstream("provider:{unsafe}")
if err != nil {
t.Fatalf("forUpstream(): %v", err)
}
all := keys.all()
for _, key := range all {
if strings.Contains(key, "provider:{unsafe}") || strings.Count(key, "{") != 1 ||
strings.Count(key, "}") != 1 || !strings.Contains(key, "{provider:") {
t.Fatalf("unsafe provider coordination key %q", key)
}
}
if strings.Split(all[0], "}")[0] != strings.Split(all[len(all)-1], "}")[0] {
t.Fatalf("keys do not share one upstream hash tag: %v", all)
}
if _, err := adapter.keys.forUpstream(""); !errors.Is(err, controllerProvider.ErrInvalidCoordination) {
t.Fatalf("forUpstream(empty) error = %v", err)
}
}
func TestRunLeaderRejectsInvalidCalls(t *testing.T) {
t.Parallel()
var adapter *Adapter
limits := controllerProvider.CoordinationLimits{
RequestInterval: time.Second, MaxInFlight: 1, MaxAttemptDuration: time.Second,
}
work := func(context.Context, controllerProvider.LeaderSession) error { return nil }
if err := adapter.RunLeader(context.Background(), "provider-a", limits, work); !errors.Is(err, controllerProvider.ErrInvalidCoordination) {
t.Fatalf("nil adapter error = %v", err)
}
}
func TestRunLeaderRejectsNegativeFetchQuota(t *testing.T) {
t.Parallel()
client := redis.NewClient(&redis.Options{Addr: "127.0.0.1:0"})
t.Cleanup(func() { _ = client.Close() })
adapter, err := New(client, Options{
Namespace: "controller", HolderID: "controller-a", LeaseTTL: 3 * time.Second,
RenewEvery: time.Second, RetryInterval: 50 * time.Millisecond,
})
if err != nil {
t.Fatalf("New(): %v", err)
}
limits := controllerProvider.CoordinationLimits{
MaxInFlight: 1, MaxAttemptDuration: time.Second, MaxTotal: -1,
}
err = adapter.RunLeader(context.Background(), "provider-a", limits,
func(context.Context, controllerProvider.LeaderSession) error { return nil })
if !errors.Is(err, controllerProvider.ErrInvalidCoordination) {
t.Fatalf("RunLeader() error = %v, want ErrInvalidCoordination", err)
}
}
func TestLeaderWorkResultPrefersParentCancellation(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := leaderWorkResult(ctx, nil); !errors.Is(err, context.Canceled) {
t.Fatalf("leaderWorkResult() error = %v, want context cancellation", err)
}
}
func withNamespace(options Options, value string) Options {
options.Namespace = value
return options
}
func withHolder(options Options, value string) Options {
options.HolderID = value
return options
}
func withLeaseTTL(options Options, value time.Duration) Options {
options.LeaseTTL = value
return options
}
func withRenewEvery(options Options, value time.Duration) Options {
options.RenewEvery = value
return options
}
func withRetryInterval(options Options, value time.Duration) Options {
options.RetryInterval = value
return options
}
func withPermitGrace(options Options, value time.Duration) Options {
options.PermitGrace = value
return options
}