Merge branch 'master' into mark-as-unread
Этот коммит содержится в:
@@ -9,7 +9,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -138,16 +137,6 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel
|
||||
th.tempWorkspace = dir
|
||||
}
|
||||
|
||||
pluginDir := filepath.Join(th.tempWorkspace, "plugins")
|
||||
webappDir := filepath.Join(th.tempWorkspace, "webapp")
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Directory = pluginDir
|
||||
*cfg.PluginSettings.ClientDirectory = webappDir
|
||||
})
|
||||
|
||||
th.App.InitPlugins(pluginDir, webappDir)
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(updateChannel)).Methods("PUT")
|
||||
api.BaseRoutes.Channel.Handle("/patch", api.ApiSessionRequired(patchChannel)).Methods("PUT")
|
||||
api.BaseRoutes.Channel.Handle("/convert", api.ApiSessionRequired(convertChannelToPrivate)).Methods("POST")
|
||||
api.BaseRoutes.Channel.Handle("/privacy", api.ApiSessionRequired(updateChannelPrivacy)).Methods("PUT")
|
||||
api.BaseRoutes.Channel.Handle("/restore", api.ApiSessionRequired(restoreChannel)).Methods("POST")
|
||||
api.BaseRoutes.Channel.Handle("", api.ApiSessionRequired(deleteChannel)).Methods("DELETE")
|
||||
api.BaseRoutes.Channel.Handle("/stats", api.ApiSessionRequired(getChannelStats)).Methods("GET")
|
||||
@@ -229,6 +230,54 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
w.Write([]byte(rchannel.ToJson()))
|
||||
}
|
||||
|
||||
func updateChannelPrivacy(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
props := model.StringInterfaceFromJson(r.Body)
|
||||
privacy, ok := props["privacy"].(string)
|
||||
if !ok || (privacy != model.CHANNEL_OPEN && privacy != model.CHANNEL_PRIVATE) {
|
||||
c.SetInvalidParam("privacy")
|
||||
return
|
||||
}
|
||||
|
||||
channel, err := c.App.GetChannel(c.Params.ChannelId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionToTeam(c.App.Session, channel.TeamId, model.PERMISSION_MANAGE_TEAM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_TEAM)
|
||||
return
|
||||
}
|
||||
|
||||
if channel.Name == model.DEFAULT_CHANNEL && privacy == model.CHANNEL_PRIVATE {
|
||||
c.Err = model.NewAppError("updateChannelPrivacy", "api.channel.update_channel_privacy.default_channel_error", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := c.App.GetUser(c.App.Session.UserId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
channel.Type = privacy
|
||||
|
||||
updatedChannel, err := c.App.UpdateChannelPrivacy(channel, user)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
c.LogAudit("name=" + updatedChannel.Name)
|
||||
|
||||
w.Write([]byte(updatedChannel.ToJson()))
|
||||
}
|
||||
|
||||
func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
|
||||
@@ -1387,6 +1387,68 @@ func TestConvertChannelToPrivate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateChannelPrivacy(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
type testTable []struct {
|
||||
name string
|
||||
channel *model.Channel
|
||||
expectedPrivacy string
|
||||
}
|
||||
|
||||
defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false)
|
||||
privateChannel := th.CreatePrivateChannel()
|
||||
publicChannel := th.CreatePublicChannel()
|
||||
|
||||
tt := testTable{
|
||||
{"Updating default channel should fail with forbidden status if not logged in", defaultChannel, model.CHANNEL_OPEN},
|
||||
{"Updating private channel should fail with forbidden status if not logged in", privateChannel, model.CHANNEL_PRIVATE},
|
||||
{"Updating public channel should fail with forbidden status if not logged in", publicChannel, model.CHANNEL_OPEN},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
th.LoginTeamAdmin()
|
||||
|
||||
tt = testTable{
|
||||
{"Converting default channel to private should fail", defaultChannel, model.CHANNEL_PRIVATE},
|
||||
{"Updating privacy to an invalid setting should fail", publicChannel, "invalid"},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
tt = testTable{
|
||||
{"Default channel should stay public", defaultChannel, model.CHANNEL_OPEN},
|
||||
{"Public channel should stay public", publicChannel, model.CHANNEL_OPEN},
|
||||
{"Private channel should stay private", privateChannel, model.CHANNEL_PRIVATE},
|
||||
{"Public channel should convert to private", publicChannel, model.CHANNEL_PRIVATE},
|
||||
{"Private channel should convert to public", privateChannel, model.CHANNEL_OPEN},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
updatedChannel, resp := Client.UpdateChannelPrivacy(tc.channel.Id, tc.expectedPrivacy)
|
||||
CheckNoError(t, resp)
|
||||
assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy)
|
||||
updatedChannel, err := th.App.GetChannel(tc.channel.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, updatedChannel.Type, tc.expectedPrivacy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreChannel(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
25
api4/helpers.go
Обычный файл
25
api4/helpers.go
Обычный файл
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func parseInt(u *url.URL, name string, defaultValue int) (int, error) {
|
||||
valueStr := u.Query().Get(name)
|
||||
if valueStr == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(valueStr)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to parse %s as integer", name)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@@ -142,8 +142,20 @@ func TestOpenDialog(t *testing.T) {
|
||||
CheckBadRequestStatus(t, resp)
|
||||
assert.False(t, pass)
|
||||
|
||||
// Should pass with no elements
|
||||
// Should pass with markdown formatted introduction text
|
||||
request.URL = "http://localhost:8065"
|
||||
request.Dialog.IntroductionText = "**Some** _introduction text"
|
||||
pass, resp = Client.OpenInteractiveDialog(request)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, pass)
|
||||
|
||||
// Should pass with empty introduction text
|
||||
request.Dialog.IntroductionText = ""
|
||||
pass, resp = Client.OpenInteractiveDialog(request)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, pass)
|
||||
|
||||
// Should pass with no elements
|
||||
request.Dialog.Elements = nil
|
||||
pass, resp = Client.OpenInteractiveDialog(request)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
@@ -7,6 +7,7 @@ package api4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -36,6 +37,8 @@ func (api *API) InitPlugin() {
|
||||
api.BaseRoutes.Plugin.Handle("/disable", api.ApiSessionRequired(disablePlugin)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.Plugins.Handle("/webapp", api.ApiHandler(getWebappPlugins)).Methods("GET")
|
||||
|
||||
api.BaseRoutes.Plugins.Handle("/marketplace", api.ApiSessionRequired(getMarketplacePlugins)).Methods("GET")
|
||||
}
|
||||
|
||||
func uploadPlugin(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -239,6 +242,43 @@ func getWebappPlugins(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(model.ManifestListToJson(clientManifests)))
|
||||
}
|
||||
|
||||
func getMarketplacePlugins(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !*c.App.Config().PluginSettings.Enable {
|
||||
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !*c.App.Config().PluginSettings.EnableMarketplace {
|
||||
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marketplace_disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
filter, err := parseMarketplacePluginFilter(r.URL)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
plugins, appErr := c.App.GetMarketplacePlugins(filter)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(plugins)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getMarketplacePlugins", "app.plugin.marshal.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func enablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequirePluginId()
|
||||
if c.Err != nil {
|
||||
@@ -286,3 +326,25 @@ func disablePlugin(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func parseMarketplacePluginFilter(u *url.URL) (*model.MarketplacePluginFilter, error) {
|
||||
page, err := parseInt(u, "page", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
perPage, err := parseInt(u, "per_page", 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := u.Query().Get("filter")
|
||||
serverVersion := u.Query().Get("server_version")
|
||||
|
||||
return &model.MarketplacePluginFilter{
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
Filter: filter,
|
||||
ServerVersion: serverVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -36,9 +38,7 @@ func TestPlugin(t *testing.T) {
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Install from URL
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
@@ -270,9 +270,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
testCluster.ClearMessages()
|
||||
|
||||
@@ -331,9 +329,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
|
||||
func TestDisableOnRemove(t *testing.T) {
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
@@ -432,6 +428,324 @@ func TestDisableOnRemove(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarketplacePlugins(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.EnableMarketplace = false
|
||||
})
|
||||
|
||||
t.Run("marketplace disabled", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.EnableMarketplace = false
|
||||
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Nil(t, plugins)
|
||||
})
|
||||
|
||||
t.Run("no server", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckInternalErrorStatus(t, resp)
|
||||
require.Nil(t, plugins)
|
||||
})
|
||||
|
||||
t.Run("no permission", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = "invalid.com"
|
||||
})
|
||||
|
||||
plugins, resp := th.Client.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Nil(t, plugins)
|
||||
})
|
||||
|
||||
t.Run("empty response from server", func(t *testing.T) {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
json, err := json.Marshal([]*model.MarketplacePlugin{})
|
||||
require.NoError(t, err)
|
||||
res.Write(json)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Len(t, plugins, 0)
|
||||
})
|
||||
|
||||
t.Run("verify server version is passed through", func(t *testing.T) {
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
serverVersion, ok := req.URL.Query()["server_version"]
|
||||
require.True(t, ok)
|
||||
require.Len(t, serverVersion, 1)
|
||||
require.Equal(t, model.CurrentVersion, serverVersion[0])
|
||||
require.NotEqual(t, 0, len(serverVersion[0]))
|
||||
|
||||
res.WriteHeader(http.StatusOK)
|
||||
json, err := json.Marshal([]*model.MarketplacePlugin{})
|
||||
require.NoError(t, err)
|
||||
res.Write(json)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Len(t, plugins, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetInstalledMarketplacePlugins(t *testing.T) {
|
||||
samplePlugins := []*model.MarketplacePlugin{
|
||||
{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps",
|
||||
IconData: "http://example.com/icon.svg",
|
||||
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
|
||||
Manifest: &model.Manifest{
|
||||
Id: "com.mattermost.nps",
|
||||
Name: "User Satisfaction Surveys",
|
||||
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
|
||||
Version: "1.0.3",
|
||||
MinServerVersion: "5.14.0",
|
||||
},
|
||||
},
|
||||
InstalledVersion: "",
|
||||
},
|
||||
}
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("marketplace client returns not-installed plugin", func(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
json, err := json.Marshal(samplePlugins)
|
||||
require.NoError(t, err)
|
||||
res.Write(json)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, samplePlugins, plugins)
|
||||
|
||||
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
|
||||
expectedPlugins := append(samplePlugins, &model.MarketplacePlugin{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "",
|
||||
IconData: "",
|
||||
DownloadURL: "",
|
||||
Manifest: manifest,
|
||||
},
|
||||
InstalledVersion: manifest.Version,
|
||||
})
|
||||
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
||||
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
||||
})
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, expectedPlugins, plugins)
|
||||
|
||||
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, samplePlugins, plugins)
|
||||
})
|
||||
|
||||
t.Run("marketplace client returns installed plugin", func(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
})
|
||||
|
||||
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
|
||||
newPlugin := &model.MarketplacePlugin{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "HomepageURL",
|
||||
IconData: "IconData",
|
||||
DownloadURL: "DownloadURL",
|
||||
Manifest: manifest,
|
||||
},
|
||||
InstalledVersion: manifest.Version,
|
||||
}
|
||||
expectedPlugins := append(samplePlugins, newPlugin)
|
||||
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
||||
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
||||
})
|
||||
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
json, err := json.Marshal([]*model.MarketplacePlugin{samplePlugins[0], newPlugin})
|
||||
require.NoError(t, err)
|
||||
res.Write(json)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, expectedPlugins, plugins)
|
||||
|
||||
ok, resp := th.SystemAdminClient.RemovePlugin(manifest.Id)
|
||||
CheckNoError(t, resp)
|
||||
assert.True(t, ok)
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
newPlugin.InstalledVersion = manifest.Version
|
||||
require.Equal(t, expectedPlugins, plugins)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSearchGetMarketplacePlugins(t *testing.T) {
|
||||
samplePlugins := []*model.MarketplacePlugin{
|
||||
{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "https://github.com/mattermost/mattermost-plugin-nps",
|
||||
IconData: "Cjxzdmcgdmlld0JveD0nMCAwIDEwNSA5MycgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJz4KPHBhdGggZD0nTTY2LDBoMzl2OTN6TTM4LDBoLTM4djkzek01MiwzNWwyNSw1OGgtMTZsLTgtMThoLTE4eicgZmlsbD0nI0VEMUMyNCcvPgo8L3N2Zz4K",
|
||||
DownloadURL: "https://github.com/mattermost/mattermost-plugin-nps/releases/download/v1.0.3/com.mattermost.nps-1.0.3.tar.gz",
|
||||
Manifest: &model.Manifest{
|
||||
Id: "com.mattermost.nps",
|
||||
Name: "User Satisfaction Surveys",
|
||||
Description: "This plugin sends quarterly user satisfaction surveys to gather feedback and help improve Mattermost.",
|
||||
Version: "1.0.3",
|
||||
MinServerVersion: "5.14.0",
|
||||
},
|
||||
},
|
||||
InstalledVersion: "",
|
||||
},
|
||||
}
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
tarData, err := ioutil.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tarDataV2, err := ioutil.ReadFile(filepath.Join(path, "testpluginv2.tar.gz"))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("search installed plugin", func(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
json, err := json.Marshal(samplePlugins)
|
||||
require.NoError(t, err)
|
||||
res.Write(json)
|
||||
}))
|
||||
defer func() { testServer.Close() }()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.PluginSettings.Enable = true
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.EnableMarketplace = true
|
||||
*cfg.PluginSettings.MarketplaceUrl = testServer.URL
|
||||
})
|
||||
|
||||
plugins, resp := th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, samplePlugins, plugins)
|
||||
|
||||
manifest, resp := th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarData))
|
||||
CheckNoError(t, resp)
|
||||
|
||||
newPluginV1 := &model.MarketplacePlugin{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "",
|
||||
IconData: "",
|
||||
DownloadURL: "",
|
||||
Manifest: manifest,
|
||||
},
|
||||
InstalledVersion: manifest.Version,
|
||||
}
|
||||
expectedPlugins := append(samplePlugins, newPluginV1)
|
||||
|
||||
manifest, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader(tarDataV2))
|
||||
CheckNoError(t, resp)
|
||||
newPluginV2 := &model.MarketplacePlugin{
|
||||
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
||||
HomepageURL: "",
|
||||
IconData: "",
|
||||
DownloadURL: "",
|
||||
Manifest: manifest,
|
||||
},
|
||||
InstalledVersion: manifest.Version,
|
||||
}
|
||||
expectedPlugins = append(expectedPlugins, newPluginV2)
|
||||
sort.SliceStable(expectedPlugins, func(i, j int) bool {
|
||||
return strings.ToLower(expectedPlugins[i].Manifest.Name) < strings.ToLower(expectedPlugins[j].Manifest.Name)
|
||||
})
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, expectedPlugins, plugins)
|
||||
|
||||
// Search for plugins from the server
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "testplugin_v2"})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "dsgsdg_v2"})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, []*model.MarketplacePlugin{newPluginV2}, plugins)
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "User Satisfaction Surveys"})
|
||||
CheckNoError(t, resp)
|
||||
require.Equal(t, samplePlugins, plugins)
|
||||
|
||||
plugins, resp = th.SystemAdminClient.GetMarketplacePlugins(&model.MarketplacePluginFilter{Filter: "NOFILTER"})
|
||||
CheckNoError(t, resp)
|
||||
require.Nil(t, plugins)
|
||||
})
|
||||
}
|
||||
|
||||
func findClusterMessages(event string, msgs []*model.ClusterMessage) []*model.ClusterMessage {
|
||||
var result []*model.ClusterMessage
|
||||
for _, msg := range msgs {
|
||||
|
||||
24
api4/post.go
24
api4/post.go
@@ -134,6 +134,10 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
skipFetchThreads := false
|
||||
if r.URL.Query().Get("fetchThreads") == "false" {
|
||||
skipFetchThreads = true
|
||||
}
|
||||
|
||||
channelId := c.Params.ChannelId
|
||||
page := c.Params.Page
|
||||
@@ -149,7 +153,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
etag := ""
|
||||
|
||||
if since > 0 {
|
||||
list, err = c.App.GetPostsSince(channelId, since)
|
||||
list, err = c.App.GetPostsSince(model.GetPostsSinceOptions{ChannelId: channelId, Time: since, SkipFetchThreads: skipFetchThreads})
|
||||
} else if len(afterPost) > 0 {
|
||||
etag = c.App.GetPostsEtag(channelId)
|
||||
|
||||
@@ -157,7 +161,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err = c.App.GetPostsAfterPost(channelId, afterPost, page, perPage)
|
||||
list, err = c.App.GetPostsAfterPost(model.GetPostsOptions{ChannelId: channelId, PostId: afterPost, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
|
||||
} else if len(beforePost) > 0 {
|
||||
etag = c.App.GetPostsEtag(channelId)
|
||||
|
||||
@@ -165,7 +169,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err = c.App.GetPostsBeforePost(channelId, beforePost, page, perPage)
|
||||
list, err = c.App.GetPostsBeforePost(model.GetPostsOptions{ChannelId: channelId, PostId: beforePost, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
|
||||
} else {
|
||||
etag = c.App.GetPostsEtag(channelId)
|
||||
|
||||
@@ -173,7 +177,7 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err = c.App.GetPostsPage(channelId, page, perPage)
|
||||
list, err = c.App.GetPostsPage(model.GetPostsOptions{ChannelId: channelId, Page: page, PerPage: perPage, SkipFetchThreads: skipFetchThreads})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -209,7 +213,11 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter)
|
||||
skipFetchThreads := false
|
||||
if r.URL.Query().Get("fetchThreads") == "false" {
|
||||
skipFetchThreads = true
|
||||
}
|
||||
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter, skipFetchThreads)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
@@ -223,7 +231,11 @@ func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
postList, err = c.App.GetPostsPage(channelId, app.PAGE_DEFAULT, c.Params.LimitBefore)
|
||||
postList, err = c.App.GetPostsPage(model.GetPostsOptions{ChannelId: channelId, Page: app.PAGE_DEFAULT, PerPage: c.Params.LimitBefore, SkipFetchThreads: skipFetchThreads})
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
postList.NextPostId = c.App.GetNextPostIdFromPostList(postList)
|
||||
|
||||
@@ -56,7 +56,7 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
actualGoroutines := runtime.NumGoroutine()
|
||||
if *c.App.Config().ServiceSettings.GoroutineHealthThreshold > 0 && actualGoroutines >= *c.App.Config().ServiceSettings.GoroutineHealthThreshold {
|
||||
mlog.Warn(fmt.Sprintf("The number of running goroutines (%v) is over the health threshold (%v)", actualGoroutines, *c.App.Config().ServiceSettings.GoroutineHealthThreshold))
|
||||
mlog.Warn("The number of running goroutines is over the health threshold", mlog.Int("goroutines", actualGoroutines), mlog.Int("health_threshold", *c.App.Config().ServiceSettings.GoroutineHealthThreshold))
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
}
|
||||
|
||||
@@ -76,17 +76,17 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
Value: currentTime,
|
||||
})
|
||||
if writeErr != nil {
|
||||
mlog.Debug(fmt.Sprintf("Unable to write to database: %s", writeErr.Error()))
|
||||
mlog.Debug("Unable to write to database.", mlog.Err(writeErr))
|
||||
s[dbStatusKey] = model.STATUS_UNHEALTHY
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
} else {
|
||||
healthCheck, readErr := c.App.Srv.Store.System().GetByName(healthCheckKey)
|
||||
if readErr != nil {
|
||||
mlog.Debug(fmt.Sprintf("Unable to read from database: %s", readErr.Error()))
|
||||
mlog.Debug("Unable to read from database.", mlog.Err(readErr))
|
||||
s[dbStatusKey] = model.STATUS_UNHEALTHY
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
} else if healthCheck.Value != currentTime {
|
||||
mlog.Debug(fmt.Sprintf("Incorrect healthcheck value, expected %s, got %s", currentTime, healthCheck.Value))
|
||||
mlog.Debug("Incorrect healthcheck value", mlog.String("expected", currentTime), mlog.String("got", healthCheck.Value))
|
||||
s[dbStatusKey] = model.STATUS_UNHEALTHY
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
} else {
|
||||
@@ -105,7 +105,7 @@ func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
}
|
||||
} else {
|
||||
mlog.Debug(fmt.Sprintf("Unable to get filestore for ping status: %s", appErr.Error()))
|
||||
mlog.Debug("Unable to get filestore for ping status.", mlog.Err(appErr))
|
||||
s[filestoreStatusKey] = model.STATUS_UNHEALTHY
|
||||
s[model.STATUS] = model.STATUS_UNHEALTHY
|
||||
}
|
||||
@@ -269,7 +269,7 @@ func postLog(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
err.Where = "client"
|
||||
c.LogError(err)
|
||||
} else {
|
||||
mlog.Debug(fmt.Sprint(msg))
|
||||
mlog.Debug("message", mlog.String("message", msg))
|
||||
}
|
||||
|
||||
m["message"] = msg
|
||||
|
||||
@@ -557,7 +557,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
profiles, err = c.App.GetUsersWithoutTeamPage(c.Params.Page, c.Params.PerPage, c.IsSystemAdmin(), restrictions)
|
||||
profiles, err = c.App.GetUsersWithoutTeamPage(userGetOptions, c.IsSystemAdmin())
|
||||
} else if len(notInChannelId) > 0 {
|
||||
if !c.App.SessionHasPermissionToChannel(c.App.Session, notInChannelId, model.PERMISSION_READ_CHANNEL) {
|
||||
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -25,7 +24,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
mlog.Error(fmt.Sprintf("websocket connect err: %v", err))
|
||||
mlog.Error("websocket connect err.", mlog.Err(err))
|
||||
c.Err = model.NewAppError("connect", "api.web_socket.connect.upgrade.app_error", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user