MM-16821 - Add a KVCompareAndDelete to the plugin API (#11804)

* Implement KVCompareAndDelete and KVCompareAndDeleteJSON

* Add tests for KVCompareAndDelete

* Update minimum server version

* Handle nil value on CompareAndSet so that it deletes it

* Fix comments

* Tweaks from PR comments

* Go back to deleted, err
Этот коммит содержится в:
Gervasio Marchand
2019-08-21 23:25:38 -03:00
коммит произвёл Jesse Hallam
родитель f8ad9f3b8f
Коммит 11b0a20d7d
13 изменённых файлов: 281 добавлений и 0 удалений

Просмотреть файл

@@ -469,6 +469,14 @@ type API interface {
// Minimum server version: 5.12
KVCompareAndSet(key string, oldValue, newValue []byte) (bool, *model.AppError)
// KVCompareAndDelete deletes a key-value pair, unique per plugin, but only if the current value matches the given oldValue.
// Returns (false, err) if DB error occurred
// Returns (false, nil) if current value != oldValue or key does not exist when deleting
// Returns (true, nil) if current value == oldValue and the key was deleted
//
// Minimum server version: 5.16
KVCompareAndDelete(key string, oldValue []byte) (bool, *model.AppError)
// KVSet stores a key-value pair with an expiry time, unique per plugin.
//
// Minimum server version: 5.6

Просмотреть файл

@@ -3521,6 +3521,36 @@ func (s *apiRPCServer) KVCompareAndSet(args *Z_KVCompareAndSetArgs, returns *Z_K
return nil
}
type Z_KVCompareAndDeleteArgs struct {
A string
B []byte
}
type Z_KVCompareAndDeleteReturns struct {
A bool
B *model.AppError
}
func (g *apiRPCClient) KVCompareAndDelete(key string, oldValue []byte) (bool, *model.AppError) {
_args := &Z_KVCompareAndDeleteArgs{key, oldValue}
_returns := &Z_KVCompareAndDeleteReturns{}
if err := g.client.Call("Plugin.KVCompareAndDelete", _args, _returns); err != nil {
log.Printf("RPC call to KVCompareAndDelete API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) KVCompareAndDelete(args *Z_KVCompareAndDeleteArgs, returns *Z_KVCompareAndDeleteReturns) error {
if hook, ok := s.impl.(interface {
KVCompareAndDelete(key string, oldValue []byte) (bool, *model.AppError)
}); ok {
returns.A, returns.B = hook.KVCompareAndDelete(args.A, args.B)
} else {
return encodableError(fmt.Errorf("API KVCompareAndDelete called but not implemented."))
}
return nil
}
type Z_KVSetWithExpiryArgs struct {
A string
B []byte

Просмотреть файл

@@ -22,6 +22,14 @@ type Helpers interface {
// Minimum server version: 5.12
KVCompareAndSetJSON(key string, oldValue interface{}, newValue interface{}) (bool, error)
// KVCompareAndDeleteJSON deletes a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string.
// Returns (false, err) if DB error occurred
// Returns (false, nil) if current value != oldValue or the key was already deleted
// Returns (true, nil) if current value == oldValue
//
// Minimum server version: 5.16
KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error)
// KVGetJSON retrieves a value based on the key, unique per plugin, unmarshalling the previously set JSON string into the given value. Returns true if the key exists.
KVGetJSON(key string, value interface{}) (bool, error)

Просмотреть файл

@@ -59,6 +59,21 @@ func (p *HelpersImpl) KVCompareAndSetJSON(key string, oldValue interface{}, newV
return p.API.KVCompareAndSet(key, oldData, newData)
}
// KVCompareAndDeleteJSON is a wrapper around KVCompareAndDelete to simplify atomically deleting a JSON object from the key value store.
func (p *HelpersImpl) KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error) {
var oldData []byte
var err error
if oldValue != nil {
oldData, err = json.Marshal(oldValue)
if err != nil {
return false, errors.Wrap(err, "unable to marshal old value")
}
}
return p.API.KVCompareAndDelete(key, oldData)
}
// KVSetWithExpiryJSON is a wrapper around KVSetWithExpiry to simplify atomically writing a JSON object with expiry to the key value store.
func (p *HelpersImpl) KVSetWithExpiryJSON(key string, value interface{}, expireInSeconds int64) error {
data, err := json.Marshal(value)

Просмотреть файл

@@ -175,6 +175,46 @@ func TestKVCompareAndSetJSON(t *testing.T) {
})
}
func TestKVCompareAndDeleteJSON(t *testing.T) {
t.Run("old value JSON marshal error", func(t *testing.T) {
api := &plugintest.API{}
api.AssertNotCalled(t, "KVCompareAndDelete")
p := &plugin.HelpersImpl{API: api}
ok, err := p.KVCompareAndDeleteJSON("test-key", func() { return })
api.AssertExpectations(t)
assert.Equal(t, false, ok)
assert.NotNil(t, err)
})
t.Run("old value nil", func(t *testing.T) {
api := &plugintest.API{}
api.On("KVCompareAndDelete", "test-key", []byte(nil)).Return(true, nil)
p := &plugin.HelpersImpl{API: api}
ok, err := p.KVCompareAndDeleteJSON("test-key", nil)
api.AssertExpectations(t)
assert.True(t, ok)
assert.Nil(t, err)
})
t.Run("old value non-nil", func(t *testing.T) {
api := &plugintest.API{}
api.On("KVCompareAndDelete", "test-key", []byte(`{"val-a":10}`)).Return(true, nil)
p := &plugin.HelpersImpl{API: api}
ok, err := p.KVCompareAndDeleteJSON("test-key", map[string]interface{}{
"val-a": 10,
})
api.AssertExpectations(t)
assert.True(t, ok)
assert.Nil(t, err)
})
}
func TestKVSetWithExpiryJSON(t *testing.T) {
t.Run("JSON marshal error", func(t *testing.T) {
api := &plugintest.API{}

Просмотреть файл

@@ -1903,6 +1903,29 @@ func (_m *API) HasPermissionToTeam(userId string, teamId string, permission *mod
return r0
}
// KVCompareAndDelete provides a mock function with given fields: key, oldValue
func (_m *API) KVCompareAndDelete(key string, oldValue []byte) (bool, *model.AppError) {
ret := _m.Called(key, oldValue)
var r0 bool
if rf, ok := ret.Get(0).(func(string, []byte) bool); ok {
r0 = rf(key, oldValue)
} else {
r0 = ret.Get(0).(bool)
}
var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string, []byte) *model.AppError); ok {
r1 = rf(key, oldValue)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
}
return r0, r1
}
// KVCompareAndSet provides a mock function with given fields: key, oldValue, newValue
func (_m *API) KVCompareAndSet(key string, oldValue []byte, newValue []byte) (bool, *model.AppError) {
ret := _m.Called(key, oldValue, newValue)

Просмотреть файл

@@ -33,6 +33,27 @@ func (_m *Helpers) EnsureBot(bot *model.Bot) (string, error) {
return r0, r1
}
// KVCompareAndDeleteJSON provides a mock function with given fields: key, oldValue
func (_m *Helpers) KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error) {
ret := _m.Called(key, oldValue)
var r0 bool
if rf, ok := ret.Get(0).(func(string, interface{}) bool); ok {
r0 = rf(key, oldValue)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, interface{}) error); ok {
r1 = rf(key, oldValue)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KVCompareAndSetJSON provides a mock function with given fields: key, oldValue, newValue
func (_m *Helpers) KVCompareAndSetJSON(key string, oldValue interface{}, newValue interface{}) (bool, error) {
ret := _m.Called(key, oldValue, newValue)