shad-go/retryupdate/update_test.go

307 lines
6.9 KiB
Go
Raw Normal View History

2020-03-07 19:14:49 +00:00
package retryupdate_test
2023-10-02 19:32:41 +00:00
//go:generate mockgen -destination mock_test.go -package retryupdate_test gitlab.com/manytask/itmo-go/private/retryupdate/kvapi Client
2020-03-07 19:14:49 +00:00
import (
"errors"
"fmt"
"testing"
"github.com/gofrs/uuid"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
2023-10-02 19:32:41 +00:00
"gitlab.com/manytask/itmo-go/private/retryupdate"
"gitlab.com/manytask/itmo-go/private/retryupdate/kvapi"
2020-03-07 19:14:49 +00:00
)
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")
2020-03-07 19:15:30 +00:00
errGetAuth = &kvapi.APIError{Method: "get", Err: &kvapi.AuthError{Msg: "token expired"}}
errSetAuth = &kvapi.APIError{Method: "set", Err: &kvapi.AuthError{Msg: "token expired"}}
2020-03-07 19:14:49 +00:00
2020-03-11 13:17:13 +00:00
errGetNoKey = &kvapi.APIError{Method: "get", Err: kvapi.ErrKeyNotFound}
errSetNoKey = &kvapi.APIError{Method: "set", Err: kvapi.ErrKeyNotFound}
2020-03-07 19:14:49 +00:00
2020-03-07 19:15:30 +00:00
errGetTemporary = &kvapi.APIError{Method: "get", Err: errors.New("unavailable")}
errSetTemporary = &kvapi.APIError{Method: "set", Err: errors.New("unavailable")}
2020-03-07 19:14:49 +00:00
)
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))
}
2023-10-02 19:32:41 +00:00
2020-03-07 19:14:49 +00:00
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)).
2020-03-07 19:15:30 +00:00
Return(nil, &kvapi.APIError{Method: "set", Err: &kvapi.ConflictError{ExpectedVersion: UUID1, ProvidedVersion: UUID0}}),
2020-03-07 19:14:49 +00:00
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),
2021-02-28 14:39:28 +00:00
// second Set returns conflict.
2020-03-07 19:14:49 +00:00
c.EXPECT().
Set(SetRequest(K0, V1, UUID0)).
2020-03-07 19:15:30 +00:00
Return(nil, &kvapi.APIError{Method: "set", Err: conflictErr}),
2021-02-28 14:39:28 +00:00
// client recognizes that first Set was successful, by inspecting ExpectedVersion of returned error.
2020-03-07 19:14:49 +00:00
)
require.NoError(t, retryupdate.UpdateValue(c, K0, updateFn))
}