1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
package redistool
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/redis/rueidis"
rmock "github.com/redis/rueidis/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/api"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/testing/matcher"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/testing/testhelpers"
"go.uber.org/mock/gomock"
"go.uber.org/zap/zaptest"
clock_testing "k8s.io/utils/clock/testing"
)
const (
ctxKey = 23124
)
func BenchmarkBuildTokenLimiterKey(b *testing.B) {
b.ReportAllocs()
const prefix = "pref"
const currentMinute = 42
var sink string
requestKey := []byte{1, 2, 3, 4}
for i := 0; i < b.N; i++ {
sink = buildTokenLimiterKey(prefix, requestKey, currentMinute)
}
_ = sink
}
func TestBuildTokenLimiterKey(t *testing.T) {
const prefix = "pref"
const currentMinute = 42
requestKey := []byte{1, 2, 3, 4}
key := buildTokenLimiterKey(prefix, requestKey, currentMinute)
assert.Equal(t, fmt.Sprintf("%s:AQIDBA==:%c", prefix, currentMinute), key)
}
func TestTokenLimiterHappyPath(t *testing.T) {
ctx, _, client, limiter, key := setup(t)
client.EXPECT().
Do(gomock.Any(), rmock.Match("GET", key)).
Return(rmock.Result(rmock.RedisInt64(0)))
client.EXPECT().
DoMulti(gomock.Any(),
rmock.Match("MULTI"),
rmock.Match("INCR", key),
rmock.Match("EXPIRE", key, "59"),
rmock.Match("EXEC"),
)
require.True(t, limiter.Allow(ctx), "Allow when no token has been consumed")
}
func TestTokenLimiterOverLimit(t *testing.T) {
ctx, _, client, limiter, key := setup(t)
client.EXPECT().
Do(gomock.Any(), rmock.Match("GET", key)).
Return(rmock.Result(rmock.RedisInt64(1)))
require.False(t, limiter.Allow(ctx), "Do not allow when a token has been consumed")
}
func TestTokenLimiterNotAllowedWhenGetError(t *testing.T) {
ctx, rpcAPI, client, limiter, key := setup(t)
err := errors.New("test connection error")
client.EXPECT().
Do(gomock.Any(), rmock.Match("GET", key)).
Return(rmock.ErrorResult(err))
rpcAPI.EXPECT().
HandleProcessingError("redistool.TokenLimiter: error retrieving minute bucket count", err)
require.False(t, limiter.Allow(ctx), "Do not allow when there is a connection error")
}
func TestTokenLimiterNotAllowedWhenIncrError(t *testing.T) {
err := errors.New("test connection error")
ctx, rpcAPI, client, limiter, key := setup(t)
client.EXPECT().
Do(gomock.Any(), rmock.Match("GET", key)).
Return(rmock.Result(rmock.RedisInt64(0)))
client.EXPECT().
DoMulti(gomock.Any(),
rmock.Match("MULTI"),
rmock.Match("INCR", key),
rmock.Match("EXPIRE", key, "59"),
rmock.Match("EXEC"),
).
Return([]rueidis.RedisResult{rmock.ErrorResult(err)})
rpcAPI.EXPECT().
HandleProcessingError("redistool.TokenLimiter: error while incrementing token key count", matcher.ErrorIs(err))
require.False(t, limiter.Allow(ctx), "Do not allow when there is a connection error")
}
func setup(t *testing.T) (context.Context, *MockRPCAPI, *rmock.Client, *TokenLimiter, string) {
ctrl := gomock.NewController(t)
client := rmock.NewClient(ctrl)
rpcAPI := NewMockRPCAPI(ctrl)
rpcAPI.EXPECT().
Log().
Return(zaptest.NewLogger(t)).
AnyTimes()
limiter := NewTokenLimiter(client, "key_prefix", 1,
func(ctx context.Context) RPCAPI {
rpcAPI.EXPECT().
RequestKey().
Return(api.AgentToken2key(ctx.Value(ctxKey).(api.AgentToken)))
return rpcAPI
})
limiter.clock = clock_testing.NewFakePassiveClock(time.Unix(100, 0))
ctx := context.WithValue(context.Background(), ctxKey, testhelpers.AgentkToken) //nolint: staticcheck
key := buildTokenLimiterKey(limiter.keyPrefix, api.AgentToken2key(testhelpers.AgentkToken), byte(limiter.clock.Now().UTC().Minute()))
return ctx, rpcAPI, client, limiter, key
}
|