shad-go/retryupdate/update_test.go
2021-03-05 14:06:52 +03:00

305 lines
6.8 KiB
Go

package retryupdate_test
//go:generate mockgen -destination mock_test.go -package retryupdate_test gitlab.com/slon/shad-go/retryupdate/kvapi Client
import (
"errors"
"fmt"
"testing"
"github.com/gofrs/uuid"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/retryupdate"
"gitlab.com/slon/shad-go/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))
}