thread-master/apps/backend/internal/lib/redislock/redislock_test.go

106 lines
2.7 KiB
Go

package redislock
import (
"context"
"fmt"
"strings"
"testing"
"time"
)
type fakeClient struct {
value string
ttlMS int64
}
func (f *fakeClient) SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error) {
if err := ctx.Err(); err != nil {
return false, err
}
if f.value != "" {
return false, nil
}
f.value = value
f.ttlMS = int64(seconds) * 1000
return true, nil
}
func (f *fakeClient) EvalCtx(ctx context.Context, script string, keys []string, args ...any) (any, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
owner := fmt.Sprint(args[0])
if f.value != owner {
return int64(0), nil
}
if script == renewScript {
f.ttlMS = args[1].(int64)
return int64(1), nil
}
if script == releaseScript {
f.value = ""
return int64(1), nil
}
return nil, fmt.Errorf("unexpected script")
}
func TestLockUsesUniqueTokenPerAcquisition(t *testing.T) {
client := &fakeClient{}
lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)
acquired, err := lock.Acquire(context.Background())
if err != nil || !acquired {
t.Fatalf("Acquire() = %v, %v", acquired, err)
}
first := client.value
if !strings.HasPrefix(first, "worker-7:") {
t.Fatalf("token %q has no worker prefix", first)
}
if err := lock.Release(context.Background()); err != nil {
t.Fatal(err)
}
acquired, err = lock.Acquire(context.Background())
if err != nil || !acquired {
t.Fatalf("second Acquire() = %v, %v", acquired, err)
}
if client.value == first {
t.Fatal("successful acquisitions reused a token")
}
}
func TestRenewAndReleaseRequireCurrentOwnership(t *testing.T) {
client := &fakeClient{}
lock := NewWithClient(client, "locks:outbox", "worker-7", 3*time.Minute)
if acquired, err := lock.Acquire(context.Background()); err != nil || !acquired {
t.Fatalf("Acquire() = %v, %v", acquired, err)
}
client.ttlMS = 1
renewed, err := lock.Renew(context.Background())
if err != nil || !renewed || client.ttlMS != int64(3*time.Minute/time.Millisecond) {
t.Fatalf("Renew() = %v, %v, ttl=%d", renewed, err, client.ttlMS)
}
client.value = "worker-8:successor"
renewed, err = lock.Renew(context.Background())
if err != nil || renewed {
t.Fatalf("lost-owner Renew() = %v, %v", renewed, err)
}
if err := lock.Release(context.Background()); err != nil {
t.Fatal(err)
}
if client.value != "worker-8:successor" {
t.Fatalf("Release removed successor lease: %q", client.value)
}
}
func TestLockMethodsHonorCanceledContext(t *testing.T) {
client := &fakeClient{}
lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, err := lock.Acquire(ctx); err == nil {
t.Fatal("Acquire succeeded with canceled context")
}
}