diff --git a/plugin/helpers.go b/plugin/helpers.go index 726741ad68..7b62fffa80 100644 --- a/plugin/helpers.go +++ b/plugin/helpers.go @@ -3,7 +3,11 @@ package plugin -import "github.com/mattermost/mattermost-server/model" +import ( + "github.com/blang/semver" + "github.com/mattermost/mattermost-server/model" + "github.com/pkg/errors" +) type Helpers interface { // EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot. @@ -54,3 +58,14 @@ type Helpers interface { type HelpersImpl struct { API API } + +func (p *HelpersImpl) ensureServerVersion(required string) error { + serverVersion := p.API.GetServerVersion() + currentVersion := semver.MustParse(serverVersion) + requiredVersion := semver.MustParse(required) + + if currentVersion.LT(requiredVersion) { + return errors.Errorf("incompatible server version for plugin, minimum required version: %s, current version: %s", required, serverVersion) + } + return nil +} diff --git a/plugin/helpers_bots.go b/plugin/helpers_bots.go index 28ed4df3f0..d7ab57ce64 100644 --- a/plugin/helpers_bots.go +++ b/plugin/helpers_bots.go @@ -10,6 +10,11 @@ import ( ) func (p *HelpersImpl) EnsureBot(bot *model.Bot) (retBotId string, retErr error) { + err := p.ensureServerVersion("5.10.0") + if err != nil { + return "", errors.Wrap(err, "failed to ensure bot") + } + // Must provide a bot with a username if bot == nil || len(bot.Username) < 1 { return "", errors.New("passed a bad bot, nil or no username") diff --git a/plugin/helpers_bots_test.go b/plugin/helpers_bots_test.go index 792f67e177..f3b985951a 100644 --- a/plugin/helpers_bots_test.go +++ b/plugin/helpers_bots_test.go @@ -24,15 +24,37 @@ func TestEnsureBot(t *testing.T) { Description: "testbotdescription", } + t.Run("server version incompatible", func(t *testing.T) { + api := setupAPI() + api.On("GetServerVersion").Return("5.9.0") + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{} + p.API = api + + _, retErr := p.EnsureBot(nil) + + assert.NotNil(t, retErr) + assert.Equal(t, "failed to ensure bot: incompatible server version for plugin, minimum required version: 5.10.0, current version: 5.9.0", retErr.Error()) + }) + t.Run("bad parameters", func(t *testing.T) { t.Run("no bot", func(t *testing.T) { + api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") + p := &plugin.HelpersImpl{} + p.API = api botId, err := p.EnsureBot(nil) assert.Equal(t, "", botId) assert.NotNil(t, err) }) t.Run("bad username", func(t *testing.T) { + api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") + p := &plugin.HelpersImpl{} + p.API = api botId, err := p.EnsureBot(&model.Bot{ Username: "", }) @@ -46,6 +68,7 @@ func TestEnsureBot(t *testing.T) { expectedBotId := model.NewId() api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return([]byte(expectedBotId), nil) defer api.AssertExpectations(t) @@ -60,6 +83,7 @@ func TestEnsureBot(t *testing.T) { t.Run("should return an error if unable to get bot", func(t *testing.T) { api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, &model.AppError{}) defer api.AssertExpectations(t) @@ -78,6 +102,7 @@ func TestEnsureBot(t *testing.T) { expectedBotId := model.NewId() api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil) api.On("GetUserByUsername", testbot.Username).Return(nil, nil) api.On("CreateBot", testbot).Return(&model.Bot{ @@ -99,6 +124,7 @@ func TestEnsureBot(t *testing.T) { expectedBotId := model.NewId() api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil) api.On("GetUserByUsername", testbot.Username).Return(&model.User{ Id: expectedBotId, @@ -119,6 +145,7 @@ func TestEnsureBot(t *testing.T) { t.Run("should return the non-bot account but log a message if user exists with the same name and is not a bot", func(t *testing.T) { expectedBotId := model.NewId() api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil) api.On("GetUserByUsername", testbot.Username).Return(&model.User{ Id: expectedBotId, @@ -138,6 +165,7 @@ func TestEnsureBot(t *testing.T) { t.Run("should fail if create bot fails", func(t *testing.T) { api := setupAPI() + api.On("GetServerVersion").Return("5.10.0") api.On("KVGet", plugin.BOT_USER_KEY).Return(nil, nil) api.On("GetUserByUsername", testbot.Username).Return(nil, nil) api.On("CreateBot", testbot).Return(nil, &model.AppError{}) diff --git a/plugin/helpers_kv.go b/plugin/helpers_kv.go index 9b054502e2..3d421f47c5 100644 --- a/plugin/helpers_kv.go +++ b/plugin/helpers_kv.go @@ -11,6 +11,11 @@ import ( // KVSetJSON implements Helpers.KVSetJSON. func (p *HelpersImpl) KVSetJSON(key string, value interface{}) error { + err := p.ensureServerVersion("5.2.0") + if err != nil { + return err + } + data, err := json.Marshal(value) if err != nil { return err @@ -26,9 +31,14 @@ func (p *HelpersImpl) KVSetJSON(key string, value interface{}) error { // KVCompareAndSetJSON implements Helpers.KVCompareAndSetJSON. func (p *HelpersImpl) KVCompareAndSetJSON(key string, oldValue interface{}, newValue interface{}) (bool, error) { - var oldData, newData []byte var err error + err = p.ensureServerVersion("5.12.0") + if err != nil { + return false, err + } + var oldData, newData []byte + if oldValue != nil { oldData, err = json.Marshal(oldValue) if err != nil { @@ -53,9 +63,15 @@ func (p *HelpersImpl) KVCompareAndSetJSON(key string, oldValue interface{}, newV // KVCompareAndDeleteJSON implements Helpers.KVCompareAndDeleteJSON. func (p *HelpersImpl) KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error) { - var oldData []byte var err error + err = p.ensureServerVersion("5.16.0") + if err != nil { + return false, err + } + + var oldData []byte + if oldValue != nil { oldData, err = json.Marshal(oldValue) if err != nil { @@ -73,6 +89,11 @@ func (p *HelpersImpl) KVCompareAndDeleteJSON(key string, oldValue interface{}) ( // KVGetJSON implements Helpers.KVGetJSON. func (p *HelpersImpl) KVGetJSON(key string, value interface{}) (bool, error) { + err := p.ensureServerVersion("5.2.0") + if err != nil { + return false, err + } + data, appErr := p.API.KVGet(key) if appErr != nil { return false, appErr @@ -81,7 +102,7 @@ func (p *HelpersImpl) KVGetJSON(key string, value interface{}) (bool, error) { return false, nil } - err := json.Unmarshal(data, value) + err = json.Unmarshal(data, value) if err != nil { return false, err } @@ -89,8 +110,13 @@ func (p *HelpersImpl) KVGetJSON(key string, value interface{}) (bool, error) { return true, nil } -// KVSetWithExpiryJSON implements Helpers.KVSetWithExpiryJSON. +// 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 { + err := p.ensureServerVersion("5.6.0") + if err != nil { + return err + } + data, err := json.Marshal(value) if err != nil { return err diff --git a/plugin/helpers_kv_test.go b/plugin/helpers_kv_test.go index 3c63137401..2f29b587ce 100644 --- a/plugin/helpers_kv_test.go +++ b/plugin/helpers_kv_test.go @@ -10,10 +10,26 @@ import ( ) func TestKVGetJSON(t *testing.T) { + t.Run("incompatible server version", func(t *testing.T) { + api := &plugintest.API{} + api.On("GetServerVersion").Return("5.1.0") + + p := &plugin.HelpersImpl{API: api} + var dat map[string]interface{} + + ok, err := p.KVGetJSON("test-key", dat) + + api.AssertExpectations(t) + assert.False(t, ok) + assert.Error(t, err) + assert.Equal(t, "incompatible server version for plugin, minimum required version: 5.2.0, current version: 5.1.0", err.Error()) + }) + t.Run("KVGet error", func(t *testing.T) { p := &plugin.HelpersImpl{} api := &plugintest.API{} + api.On("GetServerVersion").Return("5.2.0") api.On("KVGet", "test-key").Return(nil, &model.AppError{}) p.API = api @@ -30,6 +46,7 @@ func TestKVGetJSON(t *testing.T) { p := &plugin.HelpersImpl{} api := &plugintest.API{} + api.On("GetServerVersion").Return("5.2.0") api.On("KVGet", "test-key").Return(nil, nil) p.API = api @@ -46,6 +63,7 @@ func TestKVGetJSON(t *testing.T) { p := &plugin.HelpersImpl{} api := &plugintest.API{} + api.On("GetServerVersion").Return("5.2.0") api.On("KVGet", "test-key").Return([]byte(`{{:}"val-a": 10}`), nil) p.API = api @@ -62,6 +80,7 @@ func TestKVGetJSON(t *testing.T) { p := &plugin.HelpersImpl{} api := &plugintest.API{} + api.On("GetServerVersion").Return("5.2.0") api.On("KVGet", "test-key").Return([]byte(`{"val-a": 10}`), nil) p.API = api @@ -78,9 +97,25 @@ func TestKVGetJSON(t *testing.T) { } func TestKVSetJSON(t *testing.T) { + t.Run("incompatible server version", func(t *testing.T) { + api := &plugintest.API{} + api.On("GetServerVersion").Return("5.1.0") + + p := &plugin.HelpersImpl{API: api} + + err := p.KVSetJSON("test-key", map[string]interface{}{ + "val-a": float64(10), + }) + + api.AssertExpectations(t) + assert.Error(t, err) + assert.Equal(t, "incompatible server version for plugin, minimum required version: 5.2.0, current version: 5.1.0", err.Error()) + }) + t.Run("JSON marshal error", func(t *testing.T) { api := &plugintest.API{} api.AssertNotCalled(t, "KVSet") + api.On("GetServerVersion").Return("5.2.0") p := &plugin.HelpersImpl{API: api} @@ -92,6 +127,7 @@ func TestKVSetJSON(t *testing.T) { t.Run("KVSet error", func(t *testing.T) { api := &plugintest.API{} api.On("KVSet", "test-key", []byte(`{"val-a":10}`)).Return(&model.AppError{}) + api.On("GetServerVersion").Return("5.2.0") p := &plugin.HelpersImpl{API: api} @@ -106,6 +142,7 @@ func TestKVSetJSON(t *testing.T) { t.Run("marshallable struct", func(t *testing.T) { api := &plugintest.API{} api.On("KVSet", "test-key", []byte(`{"val-a":10}`)).Return(nil) + api.On("GetServerVersion").Return("5.2.0") p := &plugin.HelpersImpl{API: api} @@ -119,9 +156,24 @@ func TestKVSetJSON(t *testing.T) { } func TestKVCompareAndSetJSON(t *testing.T) { + t.Run("incompatible server version", func(t *testing.T) { + api := &plugintest.API{} + api.On("GetServerVersion").Return("5.10.0") + p := &plugin.HelpersImpl{API: api} + + ok, err := p.KVCompareAndSetJSON("test-key", nil, map[string]interface{}{ + "val-b": 20, + }) + + assert.Equal(t, false, ok) + assert.Error(t, err) + assert.Equal(t, "incompatible server version for plugin, minimum required version: 5.12.0, current version: 5.10.0", err.Error()) + }) + t.Run("old value JSON marshal error", func(t *testing.T) { api := &plugintest.API{} api.AssertNotCalled(t, "KVCompareAndSet") + api.On("GetServerVersion").Return("5.12.0") p := &plugin.HelpersImpl{API: api} ok, err := p.KVCompareAndSetJSON("test-key", func() {}, map[string]interface{}{}) @@ -133,6 +185,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { t.Run("new value JSON marshal error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.12.0") api.AssertNotCalled(t, "KVCompareAndSet") p := &plugin.HelpersImpl{API: api} @@ -146,6 +199,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { t.Run("KVCompareAndSet error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.12.0") api.On("KVCompareAndSet", "test-key", []byte(`{"val-a":10}`), []byte(`{"val-b":20}`)).Return(false, &model.AppError{}) p := &plugin.HelpersImpl{API: api} @@ -162,6 +216,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { t.Run("old value nil", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.12.0") api.On("KVCompareAndSet", "test-key", []byte(nil), []byte(`{"val-b":20}`)).Return(true, nil) p := &plugin.HelpersImpl{API: api} @@ -176,6 +231,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { t.Run("old value non-nil", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.12.0") api.On("KVCompareAndSet", "test-key", []byte(`{"val-a":10}`), []byte(`{"val-b":20}`)).Return(true, nil) p := &plugin.HelpersImpl{API: api} @@ -192,6 +248,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { t.Run("new value nil", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.12.0") api.On("KVCompareAndSet", "test-key", []byte(`{"val-a":10}`), []byte(nil)).Return(true, nil) p := &plugin.HelpersImpl{API: api} @@ -206,8 +263,23 @@ func TestKVCompareAndSetJSON(t *testing.T) { } func TestKVCompareAndDeleteJSON(t *testing.T) { + t.Run("incompatible server version", func(t *testing.T) { + api := &plugintest.API{} + api.On("GetServerVersion").Return("5.10.0") + p := &plugin.HelpersImpl{API: api} + + ok, err := p.KVCompareAndDeleteJSON("test-key", map[string]interface{}{ + "val-a": 10, + }) + + assert.Equal(t, false, ok) + assert.Error(t, err) + assert.Equal(t, "incompatible server version for plugin, minimum required version: 5.16.0, current version: 5.10.0", err.Error()) + }) + t.Run("old value JSON marshal error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.16.0") api.AssertNotCalled(t, "KVCompareAndDelete") p := &plugin.HelpersImpl{API: api} @@ -220,6 +292,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { t.Run("KVCompareAndDelete error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.16.0") api.On("KVCompareAndDelete", "test-key", []byte(`{"val-a":10}`)).Return(false, &model.AppError{}) p := &plugin.HelpersImpl{API: api} @@ -234,6 +307,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { t.Run("old value nil", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.16.0") api.On("KVCompareAndDelete", "test-key", []byte(nil)).Return(true, nil) p := &plugin.HelpersImpl{API: api} @@ -246,6 +320,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { t.Run("old value non-nil", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.16.0") api.On("KVCompareAndDelete", "test-key", []byte(`{"val-a":10}`)).Return(true, nil) p := &plugin.HelpersImpl{API: api} @@ -260,8 +335,24 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { } func TestKVSetWithExpiryJSON(t *testing.T) { + t.Run("incompatible server version", func(t *testing.T) { + api := &plugintest.API{} + api.On("GetServerVersion").Return("5.4.0") + + p := &plugin.HelpersImpl{API: api} + + err := p.KVSetWithExpiryJSON("test-key", map[string]interface{}{ + "val-a": float64(10), + }, 100) + + api.AssertExpectations(t) + assert.Error(t, err) + assert.Equal(t, "incompatible server version for plugin, minimum required version: 5.6.0, current version: 5.4.0", err.Error()) + }) + t.Run("JSON marshal error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.6.0") api.AssertNotCalled(t, "KVSetWithExpiry") p := &plugin.HelpersImpl{API: api} @@ -274,6 +365,7 @@ func TestKVSetWithExpiryJSON(t *testing.T) { t.Run("KVSetWithExpiry error", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.6.0") api.On("KVSetWithExpiry", "test-key", []byte(`{"val-a":10}`), int64(100)).Return(&model.AppError{}) p := &plugin.HelpersImpl{API: api} @@ -287,6 +379,7 @@ func TestKVSetWithExpiryJSON(t *testing.T) { t.Run("wellformed JSON", func(t *testing.T) { api := &plugintest.API{} + api.On("GetServerVersion").Return("5.6.0") api.On("KVSetWithExpiry", "test-key", []byte(`{"val-a":10}`), int64(100)).Return(nil) p := &plugin.HelpersImpl{API: api}