diff --git a/i18n/en.json b/i18n/en.json index 338eba46e69..41f0790b4b0 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -5103,6 +5103,10 @@ "id": "model.utils.decode_json.app_error", "translation": "could not decode" }, + { + "id": "plugin.rpcplugin.invocation.error", + "translation": "Error invoking plugin RPC" + }, { "id": "store.sql.alter_column_type.critical", "translation": "Failed to alter column type %v" diff --git a/plugin/api.go b/plugin/api.go index c62ae0f55b4..40a551e32b8 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -1,7 +1,23 @@ package plugin +import ( + "github.com/mattermost/platform/model" +) + type API interface { // LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a // struct that the configuration JSON can be unmarshalled to. LoadPluginConfiguration(dest interface{}) error + + // GetTeamByName gets a team by its name. + GetTeamByName(name string) (*model.Team, *model.AppError) + + // GetUserByUsername gets a user by their username. + GetUserByUsername(name string) (*model.User, *model.AppError) + + // GetChannelByName gets a channel by its name. + GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) + + // CreatePost creates a post. + CreatePost(post *model.Post) (*model.Post, *model.AppError) } diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 2f1db88cf41..6555a966c5d 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -3,6 +3,7 @@ package plugintest import ( "github.com/stretchr/testify/mock" + "github.com/mattermost/platform/model" "github.com/mattermost/platform/plugin" ) @@ -15,3 +16,43 @@ var _ plugin.API = (*API)(nil) func (m *API) LoadPluginConfiguration(dest interface{}) error { return m.Called(dest).Error(0) } + +func (m *API) GetTeamByName(name string) (*model.Team, *model.AppError) { + ret := m.Called(name) + if f, ok := ret.Get(0).(func(string) (*model.Team, *model.AppError)); ok { + return f(name) + } + team, _ := ret.Get(0).(*model.Team) + err, _ := ret.Get(1).(*model.AppError) + return team, err +} + +func (m *API) GetUserByUsername(name string) (*model.User, *model.AppError) { + ret := m.Called(name) + if f, ok := ret.Get(0).(func(string) (*model.User, *model.AppError)); ok { + return f(name) + } + user, _ := ret.Get(0).(*model.User) + err, _ := ret.Get(1).(*model.AppError) + return user, err +} + +func (m *API) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) { + ret := m.Called(name, teamId) + if f, ok := ret.Get(0).(func(_, _ string) (*model.Channel, *model.AppError)); ok { + return f(name, teamId) + } + channel, _ := ret.Get(0).(*model.Channel) + err, _ := ret.Get(1).(*model.AppError) + return channel, err +} + +func (m *API) CreatePost(post *model.Post) (*model.Post, *model.AppError) { + ret := m.Called(post) + if f, ok := ret.Get(0).(func(*model.Post) (*model.Post, *model.AppError)); ok { + return f(post) + } + postOut, _ := ret.Get(0).(*model.Post) + err, _ := ret.Get(1).(*model.AppError) + return postOut, err +} diff --git a/plugin/rpcplugin/api.go b/plugin/rpcplugin/api.go index 84eb3baae68..e1a297b0ac0 100644 --- a/plugin/rpcplugin/api.go +++ b/plugin/rpcplugin/api.go @@ -3,8 +3,10 @@ package rpcplugin import ( "encoding/json" "io" + "net/http" "net/rpc" + "github.com/mattermost/platform/model" "github.com/mattermost/platform/plugin" ) @@ -13,9 +15,9 @@ type LocalAPI struct { muxer *Muxer } -func (h *LocalAPI) LoadPluginConfiguration(args struct{}, reply *[]byte) error { +func (api *LocalAPI) LoadPluginConfiguration(args struct{}, reply *[]byte) error { var config interface{} - if err := h.api.LoadPluginConfiguration(&config); err != nil { + if err := api.api.LoadPluginConfiguration(&config); err != nil { return err } b, err := json.Marshal(config) @@ -26,6 +28,67 @@ func (h *LocalAPI) LoadPluginConfiguration(args struct{}, reply *[]byte) error { return nil } +type APITeamReply struct { + Team *model.Team + Error *model.AppError +} + +func (api *LocalAPI) GetTeamByName(args string, reply *APITeamReply) error { + team, err := api.api.GetTeamByName(args) + *reply = APITeamReply{ + Team: team, + Error: err, + } + return nil +} + +type APIUserReply struct { + User *model.User + Error *model.AppError +} + +func (api *LocalAPI) GetUserByUsername(args string, reply *APIUserReply) error { + user, err := api.api.GetUserByUsername(args) + *reply = APIUserReply{ + User: user, + Error: err, + } + return nil +} + +type APIGetChannelByNameArgs struct { + Name string + TeamId string +} + +type APIChannelReply struct { + Channel *model.Channel + Error *model.AppError +} + +func (api *LocalAPI) GetChannelByName(args *APIGetChannelByNameArgs, reply *APIChannelReply) error { + channel, err := api.api.GetChannelByName(args.Name, args.TeamId) + *reply = APIChannelReply{ + Channel: channel, + Error: err, + } + return nil +} + +type APIPostReply struct { + Post *model.Post + Error *model.AppError +} + +func (api *LocalAPI) CreatePost(args *model.Post, reply *APIPostReply) error { + post, err := api.api.CreatePost(args) + *reply = APIPostReply{ + Post: post, + Error: err, + } + return nil +} + func ServeAPI(api plugin.API, conn io.ReadWriteCloser, muxer *Muxer) { server := rpc.NewServer() server.Register(&LocalAPI{ @@ -42,14 +105,49 @@ type RemoteAPI struct { var _ plugin.API = (*RemoteAPI)(nil) -func (h *RemoteAPI) LoadPluginConfiguration(dest interface{}) error { +func (api *RemoteAPI) LoadPluginConfiguration(dest interface{}) error { var config []byte - if err := h.client.Call("LocalAPI.LoadPluginConfiguration", struct{}{}, &config); err != nil { + if err := api.client.Call("LocalAPI.LoadPluginConfiguration", struct{}{}, &config); err != nil { return err } return json.Unmarshal(config, dest) } +func (api *RemoteAPI) GetTeamByName(name string) (*model.Team, *model.AppError) { + var reply APITeamReply + if err := api.client.Call("LocalAPI.GetTeamByName", name, &reply); err != nil { + return nil, model.NewAppError("RemoteAPI.GetTeamByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Team, reply.Error +} + +func (api *RemoteAPI) GetUserByUsername(name string) (*model.User, *model.AppError) { + var reply APIUserReply + if err := api.client.Call("LocalAPI.GetUserByUsername", name, &reply); err != nil { + return nil, model.NewAppError("RemoteAPI.GetUserByUsername", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.User, reply.Error +} + +func (api *RemoteAPI) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) { + var reply APIChannelReply + if err := api.client.Call("LocalAPI.GetChannelByName", &APIGetChannelByNameArgs{ + Name: name, + TeamId: teamId, + }, &reply); err != nil { + return nil, model.NewAppError("RemoteAPI.GetChannelByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Channel, reply.Error +} + +func (api *RemoteAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) { + var reply APIPostReply + if err := api.client.Call("LocalAPI.CreatePost", post, &reply); err != nil { + return nil, model.NewAppError("RemoteAPI.CreatePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Post, reply.Error +} + func (h *RemoteAPI) Close() error { return h.client.Close() } diff --git a/plugin/rpcplugin/api_test.go b/plugin/rpcplugin/api_test.go index e5543355635..7ef9f51a800 100644 --- a/plugin/rpcplugin/api_test.go +++ b/plugin/rpcplugin/api_test.go @@ -3,11 +3,13 @@ package rpcplugin import ( "encoding/json" "io" + "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/mattermost/platform/model" "github.com/mattermost/platform/plugin" "github.com/mattermost/platform/plugin/plugintest" ) @@ -47,11 +49,57 @@ func TestAPI(t *testing.T) { json.Unmarshal([]byte(`{"Foo": "foo", "Bar": {"Baz": "baz"}}`), dest) }).Return(nil) + testChannel := &model.Channel{ + Id: "thechannelid", + } + + testTeam := &model.Team{ + Id: "theteamid", + } + teamNotFoundError := model.NewAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name=notateam", http.StatusNotFound) + + testUser := &model.User{ + Id: "theuserid", + } + + testPost := &model.Post{ + Message: "hello", + } + + api.On("GetChannelByName", "foo", "theteamid").Return(testChannel, nil) + api.On("GetTeamByName", "foo").Return(testTeam, nil) + api.On("GetTeamByName", "notateam").Return(nil, teamNotFoundError) + api.On("GetUserByUsername", "foo").Return(testUser, nil) + api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) (*model.Post, *model.AppError) { + p.Id = "thepostid" + return p, nil + }) + testAPIRPC(&api, func(remote plugin.API) { var config Config assert.NoError(t, remote.LoadPluginConfiguration(&config)) - assert.Equal(t, "foo", config.Foo) assert.Equal(t, "baz", config.Bar.Baz) + + channel, err := remote.GetChannelByName("foo", "theteamid") + assert.Equal(t, testChannel, channel) + assert.Nil(t, err) + + user, err := remote.GetUserByUsername("foo") + assert.Equal(t, testUser, user) + assert.Nil(t, err) + + team, err := remote.GetTeamByName("foo") + assert.Equal(t, testTeam, team) + assert.Nil(t, err) + + team, err = remote.GetTeamByName("notateam") + assert.Nil(t, team) + assert.Equal(t, teamNotFoundError, err) + + post, err := remote.CreatePost(testPost) + assert.NotEmpty(t, post.Id) + assert.Equal(t, testPost.Message, post.Message) + assert.Nil(t, err) }) }