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 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 }