306 lines
6.9 KiB
Go
306 lines
6.9 KiB
Go
package retryupdate_test
|
|
|
|
//go:generate mockgen -destination mock_test.go -package retryupdate_test gitlab.com/manytask/itmo-go/private/retryupdate/kvapi Client
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"gitlab.com/manytask/itmo-go/private/retryupdate"
|
|
"gitlab.com/manytask/itmo-go/private/retryupdate/kvapi"
|
|
)
|
|
|
|
var (
|
|
K0 = "K0"
|
|
V0 = "V0"
|
|
V1 = "V1"
|
|
V2 = "V2"
|
|
V3 = "V3"
|
|
|
|
UUID0 = uuid.Must(uuid.NewV4())
|
|
UUID1 = uuid.Must(uuid.NewV4())
|
|
UUID2 = uuid.Must(uuid.NewV4())
|
|
|
|
errUpdate = errors.New("update error")
|
|
|
|
errGetAuth = &kvapi.APIError{Method: "get", Err: &kvapi.AuthError{Msg: "token expired"}}
|
|
errSetAuth = &kvapi.APIError{Method: "set", Err: &kvapi.AuthError{Msg: "token expired"}}
|
|
|
|
errGetNoKey = &kvapi.APIError{Method: "get", Err: kvapi.ErrKeyNotFound}
|
|
errSetNoKey = &kvapi.APIError{Method: "set", Err: kvapi.ErrKeyNotFound}
|
|
|
|
errGetTemporary = &kvapi.APIError{Method: "get", Err: errors.New("unavailable")}
|
|
errSetTemporary = &kvapi.APIError{Method: "set", Err: errors.New("unavailable")}
|
|
)
|
|
|
|
type setMatcher struct {
|
|
kvapi.SetRequest
|
|
|
|
save *uuid.UUID
|
|
}
|
|
|
|
func (m setMatcher) Matches(x interface{}) bool {
|
|
if arg, ok := x.(*kvapi.SetRequest); ok {
|
|
if m.save != nil {
|
|
*m.save = arg.NewVersion
|
|
}
|
|
|
|
return arg.Key == m.Key && arg.Value == m.Value && arg.OldVersion == m.OldVersion
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (m setMatcher) String() string {
|
|
return fmt.Sprintf("%v", m.SetRequest)
|
|
}
|
|
|
|
func SetRequest(k, v string, oldVersion uuid.UUID, saveUUID ...*uuid.UUID) gomock.Matcher {
|
|
m := setMatcher{
|
|
SetRequest: kvapi.SetRequest{
|
|
Key: k,
|
|
Value: v,
|
|
OldVersion: oldVersion,
|
|
},
|
|
}
|
|
|
|
if len(saveUUID) > 1 {
|
|
panic("error")
|
|
}
|
|
|
|
if len(saveUUID) == 1 {
|
|
m.save = saveUUID[0]
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func updateFn(oldValue *string) (string, error) {
|
|
switch {
|
|
case oldValue == nil:
|
|
return V0, nil
|
|
case *oldValue == V0:
|
|
return V1, nil
|
|
case *oldValue == V1:
|
|
return V2, nil
|
|
case *oldValue == V2:
|
|
return V3, nil
|
|
default:
|
|
return "", errUpdate
|
|
}
|
|
}
|
|
|
|
func TestSimpleUpdate(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestUpdateFnError(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V3, Version: UUID0}, nil),
|
|
)
|
|
|
|
require.Equal(t, errUpdate, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestCreateKey(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(nil, errGetNoKey),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V0, uuid.UUID{})).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestKeyVanished(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V2, Version: UUID0}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V3, UUID0)).
|
|
Return(nil, errSetNoKey),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V0, uuid.UUID{})).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestFailOnAuthErrorInGet(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(nil, errGetAuth),
|
|
)
|
|
|
|
require.Equal(t, errGetAuth, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestFailOnAuthErrorInSet(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(nil, errGetNoKey),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V0, uuid.UUID{})).
|
|
Return(nil, errSetAuth),
|
|
)
|
|
|
|
require.Equal(t, errSetAuth, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestRetryGetError(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(nil, errGetTemporary),
|
|
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(nil, errGetTemporary),
|
|
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestRetrySetError(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(nil, errSetTemporary),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestRetrySetConflict(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(nil, errSetTemporary),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(nil, &kvapi.APIError{Method: "set", Err: &kvapi.ConflictError{ExpectedVersion: UUID1, ProvidedVersion: UUID0}}),
|
|
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V2, Version: UUID1}, nil),
|
|
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V3, UUID1)).
|
|
Return(&kvapi.SetResponse{}, nil),
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|
|
|
|
func TestRetrySetFalseConflict(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
conflictErr := &kvapi.ConflictError{ProvidedVersion: UUID0}
|
|
|
|
c := NewMockClient(ctrl)
|
|
gomock.InOrder(
|
|
c.EXPECT().
|
|
Get(&kvapi.GetRequest{Key: K0}).
|
|
Return(&kvapi.GetResponse{Value: V0, Version: UUID0}, nil),
|
|
|
|
// first Set updates key state, but returns an error.
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0, &conflictErr.ExpectedVersion)).
|
|
Return(nil, errSetTemporary),
|
|
|
|
// second Set returns conflict.
|
|
c.EXPECT().
|
|
Set(SetRequest(K0, V1, UUID0)).
|
|
Return(nil, &kvapi.APIError{Method: "set", Err: conflictErr}),
|
|
|
|
// client recognizes that first Set was successful, by inspecting ExpectedVersion of returned error.
|
|
)
|
|
|
|
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
|
|
}
|