summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Makefile6
-rw-r--r--README.md1
-rw-r--r--api4/channel.go5
-rw-r--r--api4/channel_test.go24
-rw-r--r--api4/system_test.go23
-rw-r--r--app/diagnostics.go164
-rw-r--r--app/import.go15
-rw-r--r--app/plugin_api.go32
-rw-r--r--app/post.go1
-rw-r--r--cmd/mattermost/commands/roles_test.go4
-rw-r--r--cmd/mattermost/commands/sampledata.go29
-rw-r--r--cmd/mattermost/commands/user_test.go8
-rw-r--r--config/default.json3
-rw-r--r--i18n/en.json72
-rw-r--r--model/access_test.go18
-rw-r--r--model/authorize_test.go30
-rw-r--r--model/cluster_message_test.go6
-rw-r--r--model/compliance_post_test.go15
-rw-r--r--model/emoji_test.go46
-rw-r--r--model/oauth_test.go42
-rw-r--r--model/post.go24
-rw-r--r--model/post_test.go44
-rw-r--r--model/preference_test.go52
-rw-r--r--model/user_test.go66
-rw-r--r--model/utils.go2
-rw-r--r--model/utils_test.go12
-rw-r--r--model/version_test.go105
-rw-r--r--plugin/api.go24
-rw-r--r--plugin/client_rpc_generated.go233
-rw-r--r--plugin/plugintest/api.go158
-rw-r--r--store/sqlstore/channel_store.go8
-rw-r--r--store/sqlstore/team_store.go11
-rw-r--r--store/sqlstore/upgrade.go10
-rw-r--r--store/store.go2
-rw-r--r--store/storetest/compliance_store.go19
-rw-r--r--store/storetest/mocks/ChannelStore.go16
-rw-r--r--store/storetest/mocks/TeamStore.go16
-rw-r--r--store/storetest/status_store.go5
-rw-r--r--store/storetest/system_store.go13
-rw-r--r--store/storetest/team_store.go109
-rw-r--r--store/storetest/user_store.go4
-rw-r--r--utils/markdown/autolink.go44
-rw-r--r--utils/markdown/autolink_test.go150
-rw-r--r--utils/markdown/commonmark_test.go2
-rw-r--r--utils/markdown/html.go2
-rw-r--r--utils/markdown/inlines.go53
47 files changed, 1221 insertions, 509 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index af8fcfc8f..fb640da3d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Code Contribution Guidelines
-Thank you for your interest in contributing! Please see the [Mattermost Contribution Guide](http://docs.mattermost.com/developer/contribution-guide.html) which describes the process for making code contributions across Mattermost projects and [join our "Contributors" community channel](https://pre-release.mattermost.com/core/channels/tickets) to ask questions from community members and the Mattermost core team.
+Thank you for your interest in contributing! Please see the [Mattermost Contribution Guide](https://developers.mattermost.com/contribute/getting-started/) which describes the process for making code contributions across Mattermost projects and [join our "Contributors" community channel](https://pre-release.mattermost.com/core/channels/tickets) to ask questions from community members and the Mattermost core team.
### Review Process for this Repo
diff --git a/Makefile b/Makefile
index 2782fbc9c..05478cb68 100644
--- a/Makefile
+++ b/Makefile
@@ -275,15 +275,15 @@ gofmt: ## Runs gofmt against all packages.
@echo "gofmt success"; \
store-mocks: ## Creates mock files.
- go get github.com/vektra/mockery/...
+ go get -u github.com/vektra/mockery/...
$(GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.'
ldap-mocks: ## Creates mock files for ldap.
- go get github.com/vektra/mockery/...
+ go get -u github.com/vektra/mockery/...
$(GOPATH)/bin/mockery -dir enterprise/ldap -all -output enterprise/ldap/mocks -note 'Regenerate this file using `make ldap-mocks`.'
plugin-mocks: ## Creates mock files for plugins.
- go get github.com/vektra/mockery/...
+ go get -u github.com/vektra/mockery/...
$(GOPATH)/bin/mockery -dir plugin -name API -output plugin/plugintest -outpkg plugintest -case underscore -note 'Regenerate this file using `make plugin-mocks`.'
$(GOPATH)/bin/mockery -dir plugin -name Hooks -output plugin/plugintest -outpkg plugintest -case underscore -note 'Regenerate this file using `make plugin-mocks`.'
diff --git a/README.md b/README.md
index ed95223a6..d0ce48725 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@ Mattermost is an open source, private cloud, Slack-alternative from [https://mat
It's written in Golang and React and runs as a single Linux binary with MySQL or PostgreSQL. Every month on the 16th [a new compiled version is released under an MIT license](https://www.mattermost.org/download/).
- [Review product documentation](http://docs.mattermost.com/).
+- [Review developer documentation](http://developers.mattermost.com/).
- [Download compiled version](https://mattermost.org/download).
<img width="1006" alt="screenshot at nov 29 14-11-32" src="https://user-images.githubusercontent.com/29708087/33394101-404e23e4-d50f-11e7-8fe5-99d4802a9768.png">
diff --git a/api4/channel.go b/api4/channel.go
index f21b45d56..1599b6e70 100644
--- a/api4/channel.go
+++ b/api4/channel.go
@@ -1069,6 +1069,11 @@ func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if !(channel.Type == model.CHANNEL_OPEN || channel.Type == model.CHANNEL_PRIVATE) {
+ c.Err = model.NewAppError("removeChannelMember", "api.channel.remove_channel_member.type.app_error", nil, "", http.StatusBadRequest)
+ return
+ }
+
if c.Params.UserId != c.Session.UserId {
if channel.Type == model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) {
c.SetPermissionError(model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS)
diff --git a/api4/channel_test.go b/api4/channel_test.go
index 5ca4dee6e..8593ea831 100644
--- a/api4/channel_test.go
+++ b/api4/channel_test.go
@@ -2038,6 +2038,30 @@ func TestRemoveChannelMember(t *testing.T) {
_, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id)
CheckNoError(t, resp)
+
+ // Test on preventing removal of user from a direct channel
+ directChannel, resp := Client.CreateDirectChannel(user1.Id, user2.Id)
+ CheckNoError(t, resp)
+
+ _, resp = Client.RemoveUserFromChannel(directChannel.Id, user1.Id)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = Client.RemoveUserFromChannel(directChannel.Id, user2.Id)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = th.SystemAdminClient.RemoveUserFromChannel(directChannel.Id, user1.Id)
+ CheckBadRequestStatus(t, resp)
+
+ // Test on preventing removal of user from a group channel
+ user3 := th.CreateUser()
+ groupChannel, resp := Client.CreateGroupChannel([]string{user1.Id, user2.Id, user3.Id})
+ CheckNoError(t, resp)
+
+ _, resp = Client.RemoveUserFromChannel(groupChannel.Id, user1.Id)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = th.SystemAdminClient.RemoveUserFromChannel(groupChannel.Id, user1.Id)
+ CheckBadRequestStatus(t, resp)
}
func TestAutocompleteChannels(t *testing.T) {
diff --git a/api4/system_test.go b/api4/system_test.go
index 099c193d0..205572cf6 100644
--- a/api4/system_test.go
+++ b/api4/system_test.go
@@ -10,6 +10,7 @@ import (
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestGetPing(t *testing.T) {
@@ -47,9 +48,7 @@ func TestGetConfig(t *testing.T) {
cfg, resp := th.SystemAdminClient.GetConfig()
CheckNoError(t, resp)
- if len(cfg.TeamSettings.SiteName) == 0 {
- t.Fatal()
- }
+ require.NotEqual(t, "", cfg.TeamSettings.SiteName)
if *cfg.LdapSettings.BindPassword != model.FAKE_SETTING && len(*cfg.LdapSettings.BindPassword) != 0 {
t.Fatal("did not sanitize properly")
@@ -121,28 +120,14 @@ func TestUpdateConfig(t *testing.T) {
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
CheckNoError(t, resp)
- if len(cfg.TeamSettings.SiteName) == 0 {
- t.Fatal()
- } else {
- if cfg.TeamSettings.SiteName != "MyFancyName" {
- t.Log("It should update the SiteName")
- t.Fatal()
- }
- }
+ require.Equal(t, "MyFancyName", cfg.TeamSettings.SiteName, "It should update the SiteName")
//Revert the change
cfg.TeamSettings.SiteName = SiteName
cfg, resp = th.SystemAdminClient.UpdateConfig(cfg)
CheckNoError(t, resp)
- if len(cfg.TeamSettings.SiteName) == 0 {
- t.Fatal()
- } else {
- if cfg.TeamSettings.SiteName != SiteName {
- t.Log("It should update the SiteName")
- t.Fatal()
- }
- }
+ require.Equal(t, SiteName, cfg.TeamSettings.SiteName, "It should update the SiteName")
t.Run("Should not be able to modify PluginSettings.EnableUploads", func(t *testing.T) {
oldEnableUploads := *th.App.GetConfig().PluginSettings.EnableUploads
diff --git a/app/diagnostics.go b/app/diagnostics.go
index 7034fc1a3..63bc506c3 100644
--- a/app/diagnostics.go
+++ b/app/diagnostics.go
@@ -6,46 +6,51 @@ package app
import (
"path/filepath"
"runtime"
+ "strings"
+
+ "github.com/segmentio/analytics-go"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
- "github.com/segmentio/analytics-go"
)
const (
SEGMENT_KEY = "fwb7VPbFeQ7SKp3wHm1RzFUuXZudqVok"
- TRACK_CONFIG_SERVICE = "config_service"
- TRACK_CONFIG_TEAM = "config_team"
- TRACK_CONFIG_CLIENT_REQ = "config_client_requirements"
- TRACK_CONFIG_SQL = "config_sql"
- TRACK_CONFIG_LOG = "config_log"
- TRACK_CONFIG_FILE = "config_file"
- TRACK_CONFIG_RATE = "config_rate"
- TRACK_CONFIG_EXTENSION = "config_extension"
- TRACK_CONFIG_EMAIL = "config_email"
- TRACK_CONFIG_PRIVACY = "config_privacy"
- TRACK_CONFIG_THEME = "config_theme"
- TRACK_CONFIG_OAUTH = "config_oauth"
- TRACK_CONFIG_LDAP = "config_ldap"
- TRACK_CONFIG_COMPLIANCE = "config_compliance"
- TRACK_CONFIG_LOCALIZATION = "config_localization"
- TRACK_CONFIG_SAML = "config_saml"
- TRACK_CONFIG_PASSWORD = "config_password"
- TRACK_CONFIG_CLUSTER = "config_cluster"
- TRACK_CONFIG_METRICS = "config_metrics"
- TRACK_CONFIG_WEBRTC = "config_webrtc"
- TRACK_CONFIG_SUPPORT = "config_support"
- TRACK_CONFIG_NATIVEAPP = "config_nativeapp"
- TRACK_CONFIG_EXPERIMENTAL = "config_experimental"
- TRACK_CONFIG_ANALYTICS = "config_analytics"
- TRACK_CONFIG_ANNOUNCEMENT = "config_announcement"
- TRACK_CONFIG_ELASTICSEARCH = "config_elasticsearch"
- TRACK_CONFIG_PLUGIN = "config_plugin"
- TRACK_CONFIG_DATA_RETENTION = "config_data_retention"
- TRACK_CONFIG_MESSAGE_EXPORT = "config_message_export"
- TRACK_CONFIG_DISPLAY = "config_display"
- TRACK_CONFIG_TIMEZONE = "config_timezone"
+ TRACK_CONFIG_SERVICE = "config_service"
+ TRACK_CONFIG_TEAM = "config_team"
+ TRACK_CONFIG_CLIENT_REQ = "config_client_requirements"
+ TRACK_CONFIG_SQL = "config_sql"
+ TRACK_CONFIG_LOG = "config_log"
+ TRACK_CONFIG_FILE = "config_file"
+ TRACK_CONFIG_RATE = "config_rate"
+ TRACK_CONFIG_EXTENSION = "config_extension"
+ TRACK_CONFIG_EMAIL = "config_email"
+ TRACK_CONFIG_PRIVACY = "config_privacy"
+ TRACK_CONFIG_THEME = "config_theme"
+ TRACK_CONFIG_OAUTH = "config_oauth"
+ TRACK_CONFIG_LDAP = "config_ldap"
+ TRACK_CONFIG_COMPLIANCE = "config_compliance"
+ TRACK_CONFIG_LOCALIZATION = "config_localization"
+ TRACK_CONFIG_SAML = "config_saml"
+ TRACK_CONFIG_PASSWORD = "config_password"
+ TRACK_CONFIG_CLUSTER = "config_cluster"
+ TRACK_CONFIG_METRICS = "config_metrics"
+ TRACK_CONFIG_WEBRTC = "config_webrtc"
+ TRACK_CONFIG_SUPPORT = "config_support"
+ TRACK_CONFIG_NATIVEAPP = "config_nativeapp"
+ TRACK_CONFIG_EXPERIMENTAL = "config_experimental"
+ TRACK_CONFIG_ANALYTICS = "config_analytics"
+ TRACK_CONFIG_ANNOUNCEMENT = "config_announcement"
+ TRACK_CONFIG_ELASTICSEARCH = "config_elasticsearch"
+ TRACK_CONFIG_PLUGIN = "config_plugin"
+ TRACK_CONFIG_DATA_RETENTION = "config_data_retention"
+ TRACK_CONFIG_MESSAGE_EXPORT = "config_message_export"
+ TRACK_CONFIG_DISPLAY = "config_display"
+ TRACK_CONFIG_TIMEZONE = "config_timezone"
+ TRACK_PERMISSIONS_GENERAL = "permissions_general"
+ TRACK_PERMISSIONS_SYSTEM_SCHEME = "permissions_system_scheme"
+ TRACK_PERMISSIONS_TEAM_SCHEMES = "permissions_team_schemes"
TRACK_ACTIVITY = "activity"
TRACK_LICENSE = "license"
@@ -63,6 +68,7 @@ func (a *App) SendDailyDiagnostics() {
a.trackLicense()
a.trackPlugins()
a.trackServer()
+ a.trackPermissions()
}
}
@@ -650,3 +656,97 @@ func (a *App) trackServer() {
a.SendDiagnostic(TRACK_SERVER, data)
}
+
+func (a *App) trackPermissions() {
+ phase1Complete := false
+ if ph1res := <-a.Srv.Store.System().GetByName(ADVANCED_PERMISSIONS_MIGRATION_KEY); ph1res.Err == nil {
+ phase1Complete = true
+ }
+
+ phase2Complete := false
+ if ph2res := <-a.Srv.Store.System().GetByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2); ph2res.Err == nil {
+ phase2Complete = true
+ }
+
+ a.SendDiagnostic(TRACK_PERMISSIONS_GENERAL, map[string]interface{}{
+ "phase_1_migration_complete": phase1Complete,
+ "phase_2_migration_complete": phase2Complete,
+ })
+
+ systemAdminPermissions := ""
+ if role, err := a.GetRoleByName(model.SYSTEM_ADMIN_ROLE_ID); err == nil {
+ systemAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ systemUserPermissions := ""
+ if role, err := a.GetRoleByName(model.SYSTEM_USER_ROLE_ID); err == nil {
+ systemUserPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ teamAdminPermissions := ""
+ if role, err := a.GetRoleByName(model.TEAM_ADMIN_ROLE_ID); err == nil {
+ teamAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ teamUserPermissions := ""
+ if role, err := a.GetRoleByName(model.TEAM_USER_ROLE_ID); err == nil {
+ teamUserPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ channelAdminPermissions := ""
+ if role, err := a.GetRoleByName(model.CHANNEL_ADMIN_ROLE_ID); err == nil {
+ channelAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ channelUserPermissions := ""
+ if role, err := a.GetRoleByName(model.CHANNEL_USER_ROLE_ID); err == nil {
+ systemAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ a.SendDiagnostic(TRACK_PERMISSIONS_SYSTEM_SCHEME, map[string]interface{}{
+ "system_admin_permissions": systemAdminPermissions,
+ "system_user_permissions": systemUserPermissions,
+ "team_admin_permissions": teamAdminPermissions,
+ "team_user_permissions": teamUserPermissions,
+ "channel_admin_permissions": channelAdminPermissions,
+ "channel_user_permissions": channelUserPermissions,
+ })
+
+ if schemes, err := a.GetSchemes(model.SCHEME_SCOPE_TEAM, 0, 100); err == nil {
+ for _, scheme := range schemes {
+ teamAdminPermissions := ""
+ if role, err := a.GetRoleByName(scheme.DefaultTeamAdminRole); err == nil {
+ teamAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ teamUserPermissions := ""
+ if role, err := a.GetRoleByName(scheme.DefaultTeamUserRole); err == nil {
+ teamUserPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ channelAdminPermissions := ""
+ if role, err := a.GetRoleByName(scheme.DefaultChannelAdminRole); err == nil {
+ channelAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ channelUserPermissions := ""
+ if role, err := a.GetRoleByName(scheme.DefaultChannelUserRole); err == nil {
+ systemAdminPermissions = strings.Join(role.Permissions, " ")
+ }
+
+ var count int64 = 0
+ if res := <-a.Srv.Store.Team().AnalyticsGetTeamCountForScheme(scheme.Id); res.Err == nil {
+ count = res.Data.(int64)
+ }
+
+ a.SendDiagnostic(TRACK_PERMISSIONS_TEAM_SCHEMES, map[string]interface{}{
+ "scheme_id": scheme.Id,
+ "team_admin_permissions": teamAdminPermissions,
+ "team_user_permissions": teamUserPermissions,
+ "channel_admin_permissions": channelAdminPermissions,
+ "channel_user_permissions": channelUserPermissions,
+ "team_count": count,
+ })
+ }
+ }
+}
diff --git a/app/import.go b/app/import.go
index 078198dd4..496c6b7fc 100644
--- a/app/import.go
+++ b/app/import.go
@@ -100,6 +100,10 @@ func (a *App) BulkImport(fileReader io.Reader, dryRun bool, workers int) (*model
return model.NewAppError("BulkImport", "app.import.bulk_import.file_scan.error", nil, err.Error(), http.StatusInternalServerError), 0
}
+ if err := a.finalizeImport(dryRun); err != nil {
+ return err, 0
+ }
+
return nil, 0
}
@@ -165,3 +169,14 @@ func (a *App) ImportLine(line LineImportData, dryRun bool) *model.AppError {
return model.NewAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]interface{}{"Type": line.Type}, "", http.StatusBadRequest)
}
}
+
+func (a *App) finalizeImport(dryRun bool) *model.AppError {
+ if dryRun {
+ return nil
+ }
+ result := <-a.Srv.Store.Channel().ResetLastPostAt()
+ if result.Err != nil {
+ return result.Err
+ }
+ return nil
+}
diff --git a/app/plugin_api.go b/app/plugin_api.go
index c3ab8fab2..503feabee 100644
--- a/app/plugin_api.go
+++ b/app/plugin_api.go
@@ -258,6 +258,18 @@ func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError
return api.app.CreatePostMissingChannel(post, true)
}
+func (api *PluginAPI) AddReaction(reaction *model.Reaction) (*model.Reaction, *model.AppError) {
+ return api.app.SaveReactionForPost(reaction)
+}
+
+func (api *PluginAPI) RemoveReaction(reaction *model.Reaction) *model.AppError {
+ return api.app.DeleteReactionForPost(reaction)
+}
+
+func (api *PluginAPI) GetReactions(postId string) ([]*model.Reaction, *model.AppError) {
+ return api.app.GetReactionsForPost(postId)
+}
+
func (api *PluginAPI) SendEphemeralPost(userId string, post *model.Post) *model.Post {
return api.app.SendEphemeralPost(userId, post)
}
@@ -279,6 +291,14 @@ func (api *PluginAPI) CopyFileInfos(userId string, fileIds []string) ([]string,
return api.app.CopyFileInfos(userId, fileIds)
}
+func (api *PluginAPI) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) {
+ return api.app.GetFileInfo(fileId)
+}
+
+func (api *PluginAPI) ReadFile(path string) ([]byte, *model.AppError) {
+ return api.app.ReadFile(path)
+}
+
func (api *PluginAPI) KVSet(key string, value []byte) *model.AppError {
return api.app.SetPluginKey(api.id, key, value)
}
@@ -299,6 +319,18 @@ func (api *PluginAPI) PublishWebSocketEvent(event string, payload map[string]int
})
}
+func (api *PluginAPI) HasPermissionTo(userId string, permission *model.Permission) bool {
+ return api.app.HasPermissionTo(userId, permission)
+}
+
+func (api *PluginAPI) HasPermissionToTeam(userId, teamId string, permission *model.Permission) bool {
+ return api.app.HasPermissionToTeam(userId, teamId, permission)
+}
+
+func (api *PluginAPI) HasPermissionToChannel(userId, channelId string, permission *model.Permission) bool {
+ return api.app.HasPermissionToChannel(userId, channelId, permission)
+}
+
func (api *PluginAPI) LogDebug(msg string, keyValuePairs ...interface{}) {
api.logger.Debug(msg, keyValuePairs...)
}
diff --git a/app/post.go b/app/post.go
index 0ce1bbe2d..0eed87280 100644
--- a/app/post.go
+++ b/app/post.go
@@ -869,6 +869,7 @@ func (a *App) DoPostAction(postId string, actionId string, userId string) *model
request := &model.PostActionIntegrationRequest{
UserId: userId,
+ PostId: postId,
Context: action.Integration.Context,
}
diff --git a/cmd/mattermost/commands/roles_test.go b/cmd/mattermost/commands/roles_test.go
index 4f11ce7ed..da33a73cc 100644
--- a/cmd/mattermost/commands/roles_test.go
+++ b/cmd/mattermost/commands/roles_test.go
@@ -17,7 +17,7 @@ func TestAssignRole(t *testing.T) {
CheckCommand(t, "roles", "system_admin", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
- t.Fatal()
+ t.Fatal(result.Err)
} else {
user := result.Data.(*model.User)
if user.Roles != "system_user system_admin" {
@@ -28,7 +28,7 @@ func TestAssignRole(t *testing.T) {
CheckCommand(t, "roles", "member", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
- t.Fatal()
+ t.Fatal(result.Err)
} else {
user := result.Data.(*model.User)
if user.Roles != "system_user" {
diff --git a/cmd/mattermost/commands/sampledata.go b/cmd/mattermost/commands/sampledata.go
index 0983ab0df..ed550bf6b 100644
--- a/cmd/mattermost/commands/sampledata.go
+++ b/cmd/mattermost/commands/sampledata.go
@@ -59,6 +59,15 @@ func randomPastTime(seconds int) int64 {
return (today.Unix() * 1000) - int64(rand.Intn(seconds*1000))
}
+func sortedRandomDates(size int) []int64 {
+ dates := make([]int64, size)
+ for i := 0; i < size; i++ {
+ dates[i] = randomPastTime(50000)
+ }
+ sort.Slice(dates, func(a, b int) bool { return dates[a] < dates[b] })
+ return dates
+}
+
func randomEmoji() string {
emojis := []string{"+1", "-1", "heart", "blush"}
return emojis[rand.Intn(len(emojis))]
@@ -274,8 +283,10 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
for team, channels := range teamsAndChannels {
for _, channel := range channels {
+ dates := sortedRandomDates(postsPerChannel)
+
for i := 0; i < postsPerChannel; i++ {
- postLine := createPost(team, channel, allUsers)
+ postLine := createPost(team, channel, allUsers, dates[i])
encoder.Encode(postLine)
}
}
@@ -286,8 +297,10 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
user2 := allUsers[rand.Intn(len(allUsers))]
channelLine := createDirectChannel([]string{user1, user2})
encoder.Encode(channelLine)
+
+ dates := sortedRandomDates(postsPerDirectChannel)
for j := 0; j < postsPerDirectChannel; j++ {
- postLine := createDirectPost([]string{user1, user2})
+ postLine := createDirectPost([]string{user1, user2}, dates[j])
encoder.Encode(postLine)
}
}
@@ -303,8 +316,10 @@ func sampleDataCmdF(command *cobra.Command, args []string) error {
}
channelLine := createDirectChannel(users)
encoder.Encode(channelLine)
+
+ dates := sortedRandomDates(postsPerGroupChannel)
for j := 0; j < postsPerGroupChannel; j++ {
- postLine := createDirectPost(users)
+ postLine := createDirectPost(users, dates[j])
encoder.Encode(postLine)
}
}
@@ -529,9 +544,9 @@ func createChannel(idx int, teamName string) app.LineImportData {
}
}
-func createPost(team string, channel string, allUsers []string) app.LineImportData {
+func createPost(team string, channel string, allUsers []string, createAt int64) app.LineImportData {
message := randomMessage(allUsers)
- create_at := randomPastTime(50000)
+ create_at := createAt
user := allUsers[rand.Intn(len(allUsers))]
// Some messages are flagged by an user
@@ -589,9 +604,9 @@ func createDirectChannel(members []string) app.LineImportData {
}
}
-func createDirectPost(members []string) app.LineImportData {
+func createDirectPost(members []string, createAt int64) app.LineImportData {
message := randomMessage(members)
- create_at := randomPastTime(50000)
+ create_at := createAt
user := members[rand.Intn(len(members))]
// Some messages are flagged by an user
diff --git a/cmd/mattermost/commands/user_test.go b/cmd/mattermost/commands/user_test.go
index 69ca9ecb8..088893602 100644
--- a/cmd/mattermost/commands/user_test.go
+++ b/cmd/mattermost/commands/user_test.go
@@ -50,12 +50,10 @@ func TestCreateUserWithoutTeam(t *testing.T) {
CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil {
- t.Fatal()
+ t.Fatal(result.Err)
} else {
user := result.Data.(*model.User)
- if user.Email != email {
- t.Fatal()
- }
+ require.Equal(t, email, user.Email)
}
}
@@ -92,7 +90,7 @@ func TestChangeUserEmail(t *testing.T) {
t.Fatal("should've updated to the new email")
}
if result := <-th.App.Srv.Store.User().GetByEmail(newEmail); result.Err != nil {
- t.Fatal()
+ t.Fatal(result.Err)
} else {
user := result.Data.(*model.User)
if user.Email != newEmail {
diff --git a/config/default.json b/config/default.json
index 985d3f63a..884eceb98 100644
--- a/config/default.json
+++ b/config/default.json
@@ -105,7 +105,8 @@
"ExperimentalEnableAutomaticReplies": false,
"ExperimentalHideTownSquareinLHS": false,
"ExperimentalTownSquareIsReadOnly": false,
- "ExperimentalPrimaryTeam": ""
+ "ExperimentalPrimaryTeam": "",
+ "ExperimentalDefaultChannels": ""
},
"DisplaySettings": {
"CustomUrlSchemes": [],
diff --git a/i18n/en.json b/i18n/en.json
index 19c92a0b4..b379e08d1 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -156,6 +156,10 @@
"translation": "The channel has been archived or deleted"
},
{
+ "id": "store.sql_team.analytics_get_team_count_for_scheme.app_error",
+ "translation": "We couldn't get the channel count for the scheme."
+ },
+ {
"id": "api.channel.delete_channel.type.invalid",
"translation": "Cannot delete direct or group message channels"
},
@@ -232,12 +236,12 @@
"translation": "Cannot remove user from the default channel {{.Channel}}"
},
{
- "id": "api.channel.remove_member.removed",
- "translation": "%v removed from the channel."
+ "id": "api.channel.remove_channel_member.type.app_error",
+ "translation": "Cannot remove user from a channel."
},
{
- "id": "api.channel.remove_user_from_channel.deleted.app_error",
- "translation": "The channel has been archived or deleted"
+ "id": "api.channel.remove_member.removed",
+ "translation": "%v removed from the channel."
},
{
"id": "api.channel.update_channel.deleted.app_error",
@@ -1033,6 +1037,14 @@
"translation": "File attachments have been disabled on this server."
},
{
+ "id": "api.file.file_exists.exists_local.app_error",
+ "translation": "Unable to know if the file exists. An error ocurred when trying to check file existency."
+ },
+ {
+ "id": "api.file.file_exists.s3.app_error",
+ "translation": "Unable to know if the file exists. An error ocurred when trying to check file existency."
+ },
+ {
"id": "api.file.get_file.public_invalid.app_error",
"translation": "The public link does not appear to be valid"
},
@@ -2507,6 +2519,10 @@
"translation": "Import data line has type \"direct_post\" but the direct_post object is null."
},
{
+ "id": "app.import.import_line.null_emoji.error",
+ "translation": "Import data line has type \"emoji\" but the emoji object is null."
+ },
+ {
"id": "app.import.import_line.null_post.error",
"translation": "Import data line has type \"post\" but the post object is null."
},
@@ -3019,10 +3035,6 @@
"translation": "[{{ .SiteName }}] Notification in {{ .TeamName}} on {{.Month}} {{.Day}}, {{.Year}}"
},
{
- "id": "app.plugin.activate.app_error",
- "translation": "Unable to activate extracted plugin."
- },
- {
"id": "app.plugin.cluster.save_config.app_error",
"translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled."
},
@@ -3087,18 +3099,10 @@
"translation": "Plugin is not installed"
},
{
- "id": "app.plugin.prepackaged.app_error",
- "translation": "Cannot install prepackaged plugin"
- },
- {
"id": "app.plugin.remove.app_error",
"translation": "Unable to delete plugin"
},
{
- "id": "app.plugin.set_plugin_status_state.app_error",
- "translation": "Unable to set plugin status state."
- },
- {
"id": "app.plugin.upload_disabled.app_error",
"translation": "Plugins and/or plugin uploads have been disabled."
},
@@ -3219,6 +3223,10 @@
"translation": "Unable to open the temporary export file."
},
{
+ "id": "ent.compliance.global_relay.rewind_temporary_file.appError",
+ "translation": "Unable to re-read the Global Relay temporary export file."
+ },
+ {
"id": "ent.compliance.licence_disable.app_error",
"translation": "Compliance functionality disabled by current license. Please contact your system administrator about upgrading your enterprise license."
},
@@ -3399,10 +3407,22 @@
"translation": "Invalid AD/LDAP Filter"
},
{
+ "id": "ent.message_export.global_relay.attach_file.app_error",
+ "translation": "Unable to add attachment to the Global Relay export."
+ },
+ {
+ "id": "ent.message_export.global_relay.close_zip_file.app_error",
+ "translation": "Unable to close properly the zip file."
+ },
+ {
"id": "ent.message_export.global_relay.create_file_in_zip.app_error",
"translation": "Unable to create the eml file."
},
{
+ "id": "ent.message_export.global_relay.generate_email.app_error",
+ "translation": "Unable to generate eml file data."
+ },
+ {
"id": "ent.message_export.global_relay_export.deliver.close.app_error",
"translation": "Unable to deliver the email to Global Relay."
},
@@ -4675,8 +4695,8 @@
"translation": "GitLab's Terms of Service have updated. Please go to gitlab.com to accept them and then try logging into Mattermost again."
},
{
- "id": "plugin.rpcplugin.invocation.error",
- "translation": "Error invoking plugin RPC"
+ "id": "plugin.api.update_user_status.bad_status",
+ "translation": "Unable to set the user statys. Unknown user status."
},
{
"id": "store.sql.convert_string_array",
@@ -4891,6 +4911,14 @@
"translation": "We could not reset the channel schemes"
},
{
+ "id": "store.sql_channel.reset_last_post_at.app_error",
+ "translation": "We could not reset the channel last post at date"
+ },
+ {
+ "id": "store.sql_channel.save.archived_channel.app_error",
+ "translation": "You can not modify an archived channel"
+ },
+ {
"id": "store.sql_channel.save.commit_transaction.app_error",
"translation": "Unable to commit transaction"
},
@@ -5119,6 +5147,10 @@
"translation": "We couldn't save the emoji"
},
{
+ "id": "store.sql_file_info.PermanentDeleteByUser.app_error",
+ "translation": "We couldn't delete attachments of the user"
+ },
+ {
"id": "store.sql_file_info.attach_to_post.app_error",
"translation": "We couldn't attach the file info to the post"
},
@@ -5139,6 +5171,10 @@
"translation": "We couldn't get the file info for the post"
},
{
+ "id": "store.sql_file_info.get_for_user_id.app_error",
+ "translation": "We couldn't get the file info for the user"
+ },
+ {
"id": "store.sql_file_info.permanent_delete.app_error",
"translation": "We couldn't permanently delete the file info"
},
diff --git a/model/access_test.go b/model/access_test.go
index f0ed2da77..0f124a107 100644
--- a/model/access_test.go
+++ b/model/access_test.go
@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestAccessJson(t *testing.T) {
@@ -26,9 +28,7 @@ func TestAccessJson(t *testing.T) {
func TestAccessIsValid(t *testing.T) {
ad := AccessData{}
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.ClientId = NewRandomString(28)
if err := ad.IsValid(); err == nil {
@@ -41,9 +41,7 @@ func TestAccessIsValid(t *testing.T) {
}
ad.ClientId = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.UserId = NewRandomString(28)
if err := ad.IsValid(); err == nil {
@@ -66,9 +64,7 @@ func TestAccessIsValid(t *testing.T) {
}
ad.Token = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.RefreshToken = NewRandomString(28)
if err := ad.IsValid(); err == nil {
@@ -76,9 +72,7 @@ func TestAccessIsValid(t *testing.T) {
}
ad.RefreshToken = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.RedirectUri = ""
if err := ad.IsValid(); err == nil {
diff --git a/model/authorize_test.go b/model/authorize_test.go
index 81e059305..0775b06c1 100644
--- a/model/authorize_test.go
+++ b/model/authorize_test.go
@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestAuthJson(t *testing.T) {
@@ -46,9 +48,7 @@ func TestAuthIsValid(t *testing.T) {
ad := AuthData{}
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.ClientId = NewRandomString(28)
if err := ad.IsValid(); err == nil {
@@ -56,9 +56,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.ClientId = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.UserId = NewRandomString(28)
if err := ad.IsValid(); err == nil {
@@ -66,9 +64,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.UserId = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.Code = NewRandomString(129)
if err := ad.IsValid(); err == nil {
@@ -81,9 +77,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.Code = NewId()
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.ExpiresIn = 0
if err := ad.IsValid(); err == nil {
@@ -91,9 +85,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.ExpiresIn = 1
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.CreateAt = 0
if err := ad.IsValid(); err == nil {
@@ -101,9 +93,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.CreateAt = 1
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.State = NewRandomString(129)
if err := ad.IsValid(); err == nil {
@@ -121,9 +111,7 @@ func TestAuthIsValid(t *testing.T) {
}
ad.Scope = NewRandomString(128)
- if err := ad.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, ad.IsValid())
ad.RedirectUri = ""
if err := ad.IsValid(); err == nil {
diff --git a/model/cluster_message_test.go b/model/cluster_message_test.go
index 38603e577..e9225a5c5 100644
--- a/model/cluster_message_test.go
+++ b/model/cluster_message_test.go
@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestClusterMessage(t *testing.T) {
@@ -17,9 +19,7 @@ func TestClusterMessage(t *testing.T) {
json := m.ToJson()
result := ClusterMessageFromJson(strings.NewReader(json))
- if result.Data != "hello" {
- t.Fatal()
- }
+ require.Equal(t, "hello", result.Data)
badresult := ClusterMessageFromJson(strings.NewReader("junk"))
if badresult != nil {
diff --git a/model/compliance_post_test.go b/model/compliance_post_test.go
index ff159ef1b..21044c128 100644
--- a/model/compliance_post_test.go
+++ b/model/compliance_post_test.go
@@ -5,25 +5,20 @@ package model
import (
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestCompliancePostHeader(t *testing.T) {
- if CompliancePostHeader()[0] != "TeamName" {
- t.Fatal()
- }
+ require.Equal(t, "TeamName", CompliancePostHeader()[0])
}
func TestCompliancePost(t *testing.T) {
o := CompliancePost{TeamName: "test", PostFileIds: "files", PostCreateAt: GetMillis()}
r := o.Row()
- if r[0] != "test" {
- t.Fatal()
- }
-
- if r[len(r)-1] != "files" {
- t.Fatal()
- }
+ require.Equal(t, "test", r[0])
+ require.Equal(t, "files", r[len(r)-1])
}
var cleanTests = []struct {
diff --git a/model/emoji_test.go b/model/emoji_test.go
index 50d741214..4539db873 100644
--- a/model/emoji_test.go
+++ b/model/emoji_test.go
@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestEmojiIsValid(t *testing.T) {
@@ -23,61 +25,39 @@ func TestEmojiIsValid(t *testing.T) {
}
emoji.Id = "1234"
- if err := emoji.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, emoji.IsValid())
emoji.Id = NewId()
emoji.CreateAt = 0
- if err := emoji.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, emoji.IsValid())
emoji.CreateAt = 1234
emoji.UpdateAt = 0
- if err := emoji.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, emoji.IsValid())
emoji.UpdateAt = 1234
emoji.CreatorId = strings.Repeat("1", 27)
- if err := emoji.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, emoji.IsValid())
emoji.CreatorId = NewId()
emoji.Name = strings.Repeat("1", 65)
- if err := emoji.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, emoji.IsValid())
emoji.Name = ""
- if err := emoji.IsValid(); err == nil {
- t.Fatal(err)
- }
+ require.NotNil(t, emoji.IsValid())
emoji.Name = strings.Repeat("1", 64)
- if err := emoji.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, emoji.IsValid())
emoji.Name = "name-"
- if err := emoji.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, emoji.IsValid())
emoji.Name = "name_"
- if err := emoji.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, emoji.IsValid())
emoji.Name = "name:"
- if err := emoji.IsValid(); err == nil {
- t.Fatal(err)
- }
+ require.NotNil(t, emoji.IsValid())
emoji.Name = "croissant"
- if err := emoji.IsValid(); err == nil {
- t.Fatal(err)
- }
+ require.NotNil(t, emoji.IsValid())
}
diff --git a/model/oauth_test.go b/model/oauth_test.go
index 5c0547717..cbed8a633 100644
--- a/model/oauth_test.go
+++ b/model/oauth_test.go
@@ -6,6 +6,8 @@ package model
import (
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestOAuthAppJson(t *testing.T) {
@@ -52,52 +54,32 @@ func TestOAuthAppPreUpdate(t *testing.T) {
func TestOAuthAppIsValid(t *testing.T) {
app := OAuthApp{}
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.Id = NewId()
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.CreateAt = 1
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.UpdateAt = 1
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.CreatorId = NewId()
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.ClientSecret = NewId()
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.Name = "TestOAuthApp"
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.CallbackUrls = []string{"https://nowhere.com"}
- if err := app.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, app.IsValid())
app.Homepage = "https://nowhere.com"
- if err := app.IsValid(); err != nil {
- t.Fatal()
- }
+ require.Nil(t, app.IsValid())
app.IconURL = "https://nowhere.com/icon_image.png"
- if err := app.IsValid(); err != nil {
- t.Fatal()
- }
+ require.Nil(t, app.IsValid())
}
diff --git a/model/post.go b/model/post.go
index 1dd0a4db6..635493c9d 100644
--- a/model/post.go
+++ b/model/post.go
@@ -121,6 +121,7 @@ type PostActionIntegration struct {
type PostActionIntegrationRequest struct {
UserId string `json:"user_id"`
+ PostId string `json:"post_id"`
Context StringInterface `json:"context,omitempty"`
}
@@ -351,6 +352,29 @@ func (r *PostActionIntegrationRequest) ToJson() string {
return string(b)
}
+func PostActionIntegrationRequesteFromJson(data io.Reader) *PostActionIntegrationRequest {
+ var o *PostActionIntegrationRequest
+ err := json.NewDecoder(data).Decode(&o)
+ if err != nil {
+ return nil
+ }
+ return o
+}
+
+func (r *PostActionIntegrationResponse) ToJson() string {
+ b, _ := json.Marshal(r)
+ return string(b)
+}
+
+func PostActionIntegrationResponseFromJson(data io.Reader) *PostActionIntegrationResponse {
+ var o *PostActionIntegrationResponse
+ err := json.NewDecoder(data).Decode(&o)
+ if err != nil {
+ return nil
+ }
+ return o
+}
+
func (o *Post) Attachments() []*SlackAttachment {
if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
return attachments
diff --git a/model/post_test.go b/model/post_test.go
index af350d76e..b15134c89 100644
--- a/model/post_test.go
+++ b/model/post_test.go
@@ -11,14 +11,46 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestPostJson(t *testing.T) {
+func TestPostToJson(t *testing.T) {
o := Post{Id: NewId(), Message: NewId()}
- json := o.ToJson()
- ro := PostFromJson(strings.NewReader(json))
+ j := o.ToJson()
+ ro := PostFromJson(strings.NewReader(j))
- if o.Id != ro.Id {
- t.Fatal("Ids do not match")
- }
+ assert.NotNil(t, ro)
+ assert.Equal(t, o, *ro)
+}
+
+func TestPostFromJsonError(t *testing.T) {
+ ro := PostFromJson(strings.NewReader(""))
+ assert.Nil(t, ro)
+}
+
+func TestPostActionIntegrationRequestToJson(t *testing.T) {
+ o := PostActionIntegrationRequest{UserId: NewId(), Context: StringInterface{"a": "abc"}}
+ j := o.ToJson()
+ ro := PostActionIntegrationRequesteFromJson(strings.NewReader(j))
+
+ assert.NotNil(t, ro)
+ assert.Equal(t, o, *ro)
+}
+
+func TestPostActionIntegrationRequestFromJsonError(t *testing.T) {
+ ro := PostActionIntegrationRequesteFromJson(strings.NewReader(""))
+ assert.Nil(t, ro)
+}
+
+func TestPostActionIntegrationResponseToJson(t *testing.T) {
+ o := PostActionIntegrationResponse{Update: &Post{Id: NewId(), Message: NewId()}, EphemeralText: NewId()}
+ j := o.ToJson()
+ ro := PostActionIntegrationResponseFromJson(strings.NewReader(j))
+
+ assert.NotNil(t, ro)
+ assert.Equal(t, o, *ro)
+}
+
+func TestPostActionIntegrationResponseFromJsonError(t *testing.T) {
+ ro := PostActionIntegrationResponseFromJson(strings.NewReader(""))
+ assert.Nil(t, ro)
}
func TestPostIsValid(t *testing.T) {
diff --git a/model/preference_test.go b/model/preference_test.go
index c56d46e2c..d4035125a 100644
--- a/model/preference_test.go
+++ b/model/preference_test.go
@@ -7,6 +7,8 @@ import (
"encoding/json"
"strings"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestPreferenceIsValid(t *testing.T) {
@@ -16,54 +18,34 @@ func TestPreferenceIsValid(t *testing.T) {
Name: NewId(),
}
- if err := preference.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, preference.IsValid())
preference.UserId = NewId()
- if err := preference.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, preference.IsValid())
preference.Category = strings.Repeat("01234567890", 20)
- if err := preference.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, preference.IsValid())
preference.Category = PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW
- if err := preference.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, preference.IsValid())
preference.Name = strings.Repeat("01234567890", 20)
- if err := preference.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, preference.IsValid())
preference.Name = NewId()
- if err := preference.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, preference.IsValid())
preference.Value = strings.Repeat("01234567890", 201)
- if err := preference.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, preference.IsValid())
preference.Value = "1234garbage"
- if err := preference.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, preference.IsValid())
preference.Category = PREFERENCE_CATEGORY_THEME
- if err := preference.IsValid(); err == nil {
- t.Fatal()
- }
+ require.NotNil(t, preference.IsValid())
preference.Value = `{"color": "#ff0000", "color2": "#faf"}`
- if err := preference.IsValid(); err != nil {
- t.Fatal(err)
- }
+ require.Nil(t, preference.IsValid())
}
func TestPreferencePreUpdate(t *testing.T) {
@@ -79,11 +61,9 @@ func TestPreferencePreUpdate(t *testing.T) {
t.Fatal(err)
}
- if props["color"] != "#ff0000" || props["color2"] != "#faf" || props["codeTheme"] != "github" {
- t.Fatal("shouldn't have changed valid props")
- }
+ require.Equal(t, "#ff0000", props["color"], "shouldn't have changed valid props")
+ require.Equal(t, "#faf", props["color2"], "shouldn't have changed valid props")
+ require.Equal(t, "github", props["codeTheme"], "shouldn't have changed valid props")
- if props["invalid"] == "invalid" {
- t.Fatal("should have changed invalid prop")
- }
+ require.NotEqual(t, "invalid", props["invalid"], "should have changed invalid prop")
}
diff --git a/model/user_test.go b/model/user_test.go
index b3aaad522..f86b52919 100644
--- a/model/user_test.go
+++ b/model/user_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPasswordHash(t *testing.T) {
@@ -117,41 +118,34 @@ func TestUserIsValid(t *testing.T) {
}
user.Id = NewId()
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "create_at", user.Id) {
- t.Fatal(err)
- }
+ err := user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "create_at", user.Id), "expected user is valid error: %s", err.Error())
user.CreateAt = GetMillis()
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "update_at", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "update_at", user.Id), "expected user is valid error: %s", err.Error())
user.UpdateAt = GetMillis()
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "username", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "username", user.Id), "expected user is valid error: %s", err.Error())
user.Username = NewId() + "^hello#"
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "username", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "username", user.Id), "expected user is valid error: %s", err.Error())
user.Username = NewId()
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "email", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "email", user.Id), "expected user is valid error: %s", err.Error())
user.Email = strings.Repeat("01234567890", 20)
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "email", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "email", user.Id), "expected user is valid error: %s", err.Error())
user.Email = "user@example.com"
user.Nickname = strings.Repeat("a", 65)
- if err := user.IsValid(); !HasExpectedUserIsValidError(err, "nickname", user.Id) {
- t.Fatal(err)
- }
+ err = user.IsValid()
+ require.True(t, HasExpectedUserIsValidError(err, "nickname", user.Id), "expected user is valid error: %s", err.Error())
user.Nickname = strings.Repeat("a", 64)
if err := user.IsValid(); err != nil {
@@ -331,28 +325,10 @@ func TestCleanUsername(t *testing.T) {
}
func TestRoles(t *testing.T) {
-
- if !IsValidUserRoles("team_user") {
- t.Fatal()
- }
-
- if IsValidUserRoles("system_admin") {
- t.Fatal()
- }
-
- if !IsValidUserRoles("system_user system_admin") {
- t.Fatal()
- }
-
- if IsInRole("system_admin junk", "admin") {
- t.Fatal()
- }
-
- if !IsInRole("system_admin junk", "system_admin") {
- t.Fatal()
- }
-
- if IsInRole("admin", "system_admin") {
- t.Fatal()
- }
+ require.True(t, IsValidUserRoles("team_user"))
+ require.False(t, IsValidUserRoles("system_admin"))
+ require.True(t, IsValidUserRoles("system_user system_admin"))
+ require.False(t, IsInRole("system_admin junk", "admin"))
+ require.True(t, IsInRole("system_admin junk", "system_admin"))
+ require.False(t, IsInRole("admin", "system_admin"))
}
diff --git a/model/utils.go b/model/utils.go
index 0d8d359a6..574f43e06 100644
--- a/model/utils.go
+++ b/model/utils.go
@@ -263,7 +263,7 @@ func GetServerIpAddress() string {
} else {
for _, addr := range addrs {
- if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
+ if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() {
if ip.IP.To4() != nil {
return ip.IP.String()
}
diff --git a/model/utils_test.go b/model/utils_test.go
index 9797c7090..d35146b30 100644
--- a/model/utils_test.go
+++ b/model/utils_test.go
@@ -34,18 +34,14 @@ func TestAppError(t *testing.T) {
err := NewAppError("TestAppError", "message", nil, "", http.StatusInternalServerError)
json := err.ToJson()
rerr := AppErrorFromJson(strings.NewReader(json))
- if err.Message != rerr.Message {
- t.Fatal()
- }
+ require.Equal(t, err.Message, rerr.Message)
t.Log(err.Error())
}
func TestAppErrorJunk(t *testing.T) {
rerr := AppErrorFromJson(strings.NewReader("<html><body>This is a broken test</body></html>"))
- if "body: <html><body>This is a broken test</body></html>" != rerr.DetailedError {
- t.Fatal()
- }
+ require.Equal(t, "body: <html><body>This is a broken test</body></html>", rerr.DetailedError)
}
func TestCopyStringMap(t *testing.T) {
@@ -173,9 +169,7 @@ func TestValidLower(t *testing.T) {
func TestEtag(t *testing.T) {
etag := Etag("hello", 24)
- if len(etag) <= 0 {
- t.Fatal()
- }
+ require.NotEqual(t, "", etag)
}
var hashtags = map[string]string{
diff --git a/model/version_test.go b/model/version_test.go
index 869ed8ad0..a06db326c 100644
--- a/model/version_test.go
+++ b/model/version_test.go
@@ -6,100 +6,59 @@ package model
import (
"fmt"
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestSplitVersion(t *testing.T) {
major1, minor1, patch1 := SplitVersion("junk")
- if major1 != 0 || minor1 != 0 || patch1 != 0 {
- t.Fatal()
- }
+ require.EqualValues(t, 0, major1)
+ require.EqualValues(t, 0, minor1)
+ require.EqualValues(t, 0, patch1)
major2, minor2, patch2 := SplitVersion("1.2.3")
- if major2 != 1 || minor2 != 2 || patch2 != 3 {
- t.Fatal()
- }
+ require.EqualValues(t, 1, major2)
+ require.EqualValues(t, 2, minor2)
+ require.EqualValues(t, 3, patch2)
major3, minor3, patch3 := SplitVersion("1.2")
- if major3 != 1 || minor3 != 2 || patch3 != 0 {
- t.Fatal()
- }
+ require.EqualValues(t, 1, major3)
+ require.EqualValues(t, 2, minor3)
+ require.EqualValues(t, 0, patch3)
major4, minor4, patch4 := SplitVersion("1")
- if major4 != 1 || minor4 != 0 || patch4 != 0 {
- t.Fatal()
- }
+ require.EqualValues(t, 1, major4)
+ require.EqualValues(t, 0, minor4)
+ require.EqualValues(t, 0, patch4)
major5, minor5, patch5 := SplitVersion("1.2.3.junkgoeswhere")
- if major5 != 1 || minor5 != 2 || patch5 != 3 {
- t.Fatal()
- }
+ require.EqualValues(t, 1, major5)
+ require.EqualValues(t, 2, minor5)
+ require.EqualValues(t, 3, patch5)
}
func TestGetPreviousVersion(t *testing.T) {
- if GetPreviousVersion("1.3.0") != "1.2.0" {
- t.Fatal()
- }
-
- if GetPreviousVersion("1.2.1") != "1.1.0" {
- t.Fatal()
- }
-
- if GetPreviousVersion("1.1.0") != "1.0.0" {
- t.Fatal()
- }
-
- if GetPreviousVersion("1.0.0") != "0.7.0" {
- t.Fatal()
- }
-
- if GetPreviousVersion("0.7.1") != "0.6.0" {
- t.Fatal()
- }
-
- if GetPreviousVersion("0.5.0") != "" {
- t.Fatal()
- }
+ require.Equal(t, "1.2.0", GetPreviousVersion("1.3.0"))
+ require.Equal(t, "1.1.0", GetPreviousVersion("1.2.1"))
+ require.Equal(t, "1.0.0", GetPreviousVersion("1.1.0"))
+ require.Equal(t, "0.7.0", GetPreviousVersion("1.0.0"))
+ require.Equal(t, "0.6.0", GetPreviousVersion("0.7.1"))
+ require.Equal(t, "", GetPreviousVersion("0.5.0"))
}
func TestIsCurrentVersion(t *testing.T) {
major, minor, patch := SplitVersion(CurrentVersion)
- if !IsCurrentVersion(CurrentVersion) {
- t.Fatal()
- }
-
- if !IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major, minor, patch+100)) {
- t.Fatal()
- }
-
- if IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major, minor+1, patch)) {
- t.Fatal()
- }
-
- if IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major+1, minor, patch)) {
- t.Fatal()
- }
+ require.True(t, IsCurrentVersion(CurrentVersion))
+ require.True(t, IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major, minor, patch+100)))
+ require.False(t, IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major, minor+1, patch)))
+ require.False(t, IsCurrentVersion(fmt.Sprintf("%v.%v.%v", major+1, minor, patch)))
}
func TestIsPreviousVersionsSupported(t *testing.T) {
-
- if !IsPreviousVersionsSupported(versionsWithoutHotFixes[0]) {
- t.Fatal()
- }
-
- if !IsPreviousVersionsSupported(versionsWithoutHotFixes[1]) {
- t.Fatal()
- }
-
- if !IsPreviousVersionsSupported(versionsWithoutHotFixes[2]) {
- t.Fatal()
- }
-
- if IsPreviousVersionsSupported(versionsWithoutHotFixes[4]) {
- t.Fatal()
- }
-
- if IsPreviousVersionsSupported(versionsWithoutHotFixes[5]) {
- t.Fatal()
- }
+ require.True(t, IsPreviousVersionsSupported(versionsWithoutHotFixes[0]))
+ require.True(t, IsPreviousVersionsSupported(versionsWithoutHotFixes[1]))
+ require.True(t, IsPreviousVersionsSupported(versionsWithoutHotFixes[2]))
+ require.False(t, IsPreviousVersionsSupported(versionsWithoutHotFixes[4]))
+ require.False(t, IsPreviousVersionsSupported(versionsWithoutHotFixes[5]))
}
diff --git a/plugin/api.go b/plugin/api.go
index 841a2c702..370c28268 100644
--- a/plugin/api.go
+++ b/plugin/api.go
@@ -143,6 +143,15 @@ type API interface {
// CreatePost creates a post.
CreatePost(post *model.Post) (*model.Post, *model.AppError)
+ // AddReaction add a reaction to a post.
+ AddReaction(reaction *model.Reaction) (*model.Reaction, *model.AppError)
+
+ // RemoveReaction remove a reaction from a post.
+ RemoveReaction(reaction *model.Reaction) *model.AppError
+
+ // GetReaction get the reactions of a post.
+ GetReactions(postId string) ([]*model.Reaction, *model.AppError)
+
// SendEphemeralPost creates an ephemeral post.
SendEphemeralPost(userId string, post *model.Post) *model.Post
@@ -163,6 +172,12 @@ type API interface {
// actually duplicating the uploaded files.
CopyFileInfos(userId string, fileIds []string) ([]string, *model.AppError)
+ // GetFileInfo gets a File Info for a specific fileId
+ GetFileInfo(fileId string) (*model.FileInfo, *model.AppError)
+
+ // ReadFileAtPath reads the file from the backend for a specific path
+ ReadFile(path string) ([]byte, *model.AppError)
+
// KVSet will store a key-value pair, unique per plugin.
KVSet(key string, value []byte) *model.AppError
@@ -178,6 +193,15 @@ type API interface {
// broadcast determines to which users to send the event
PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast)
+ // HasPermissionTo check if the user has the permission at system scope.
+ HasPermissionTo(userId string, permission *model.Permission) bool
+
+ // HasPermissionToTeam check if the user has the permission at team scope.
+ HasPermissionToTeam(userId, teamId string, permission *model.Permission) bool
+
+ // HasPermissionToChannel check if the user has the permission at channel scope.
+ HasPermissionToChannel(userId, channelId string, permission *model.Permission) bool
+
// LogDebug writes a log message to the Mattermost server log file.
// Appropriate context such as the plugin name will already be added as fields so plugins
// do not need to add that info.
diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go
index 9106f2fad..ab41c66d9 100644
--- a/plugin/client_rpc_generated.go
+++ b/plugin/client_rpc_generated.go
@@ -1705,6 +1705,92 @@ func (s *apiRPCServer) CreatePost(args *Z_CreatePostArgs, returns *Z_CreatePostR
return nil
}
+type Z_AddReactionArgs struct {
+ A *model.Reaction
+}
+
+type Z_AddReactionReturns struct {
+ A *model.Reaction
+ B *model.AppError
+}
+
+func (g *apiRPCClient) AddReaction(reaction *model.Reaction) (*model.Reaction, *model.AppError) {
+ _args := &Z_AddReactionArgs{reaction}
+ _returns := &Z_AddReactionReturns{}
+ if err := g.client.Call("Plugin.AddReaction", _args, _returns); err != nil {
+ log.Printf("RPC call to AddReaction API failed: %s", err.Error())
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) AddReaction(args *Z_AddReactionArgs, returns *Z_AddReactionReturns) error {
+ if hook, ok := s.impl.(interface {
+ AddReaction(reaction *model.Reaction) (*model.Reaction, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.AddReaction(args.A)
+ } else {
+ return fmt.Errorf("API AddReaction called but not implemented.")
+ }
+ return nil
+}
+
+type Z_RemoveReactionArgs struct {
+ A *model.Reaction
+}
+
+type Z_RemoveReactionReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) RemoveReaction(reaction *model.Reaction) *model.AppError {
+ _args := &Z_RemoveReactionArgs{reaction}
+ _returns := &Z_RemoveReactionReturns{}
+ if err := g.client.Call("Plugin.RemoveReaction", _args, _returns); err != nil {
+ log.Printf("RPC call to RemoveReaction API failed: %s", err.Error())
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) RemoveReaction(args *Z_RemoveReactionArgs, returns *Z_RemoveReactionReturns) error {
+ if hook, ok := s.impl.(interface {
+ RemoveReaction(reaction *model.Reaction) *model.AppError
+ }); ok {
+ returns.A = hook.RemoveReaction(args.A)
+ } else {
+ return fmt.Errorf("API RemoveReaction called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetReactionsArgs struct {
+ A string
+}
+
+type Z_GetReactionsReturns struct {
+ A []*model.Reaction
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetReactions(postId string) ([]*model.Reaction, *model.AppError) {
+ _args := &Z_GetReactionsArgs{postId}
+ _returns := &Z_GetReactionsReturns{}
+ if err := g.client.Call("Plugin.GetReactions", _args, _returns); err != nil {
+ log.Printf("RPC call to GetReactions API failed: %s", err.Error())
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetReactions(args *Z_GetReactionsArgs, returns *Z_GetReactionsReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetReactions(postId string) ([]*model.Reaction, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetReactions(args.A)
+ } else {
+ return fmt.Errorf("API GetReactions called but not implemented.")
+ }
+ return nil
+}
+
type Z_SendEphemeralPostArgs struct {
A string
B *model.Post
@@ -1850,6 +1936,64 @@ func (s *apiRPCServer) CopyFileInfos(args *Z_CopyFileInfosArgs, returns *Z_CopyF
return nil
}
+type Z_GetFileInfoArgs struct {
+ A string
+}
+
+type Z_GetFileInfoReturns struct {
+ A *model.FileInfo
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) {
+ _args := &Z_GetFileInfoArgs{fileId}
+ _returns := &Z_GetFileInfoReturns{}
+ if err := g.client.Call("Plugin.GetFileInfo", _args, _returns); err != nil {
+ log.Printf("RPC call to GetFileInfo API failed: %s", err.Error())
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetFileInfo(args *Z_GetFileInfoArgs, returns *Z_GetFileInfoReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetFileInfo(fileId string) (*model.FileInfo, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetFileInfo(args.A)
+ } else {
+ return fmt.Errorf("API GetFileInfo called but not implemented.")
+ }
+ return nil
+}
+
+type Z_ReadFileArgs struct {
+ A string
+}
+
+type Z_ReadFileReturns struct {
+ A []byte
+ B *model.AppError
+}
+
+func (g *apiRPCClient) ReadFile(path string) ([]byte, *model.AppError) {
+ _args := &Z_ReadFileArgs{path}
+ _returns := &Z_ReadFileReturns{}
+ if err := g.client.Call("Plugin.ReadFile", _args, _returns); err != nil {
+ log.Printf("RPC call to ReadFile API failed: %s", err.Error())
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) ReadFile(args *Z_ReadFileArgs, returns *Z_ReadFileReturns) error {
+ if hook, ok := s.impl.(interface {
+ ReadFile(path string) ([]byte, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.ReadFile(args.A)
+ } else {
+ return fmt.Errorf("API ReadFile called but not implemented.")
+ }
+ return nil
+}
+
type Z_KVSetArgs struct {
A string
B []byte
@@ -1965,6 +2109,95 @@ func (s *apiRPCServer) PublishWebSocketEvent(args *Z_PublishWebSocketEventArgs,
return nil
}
+type Z_HasPermissionToArgs struct {
+ A string
+ B *model.Permission
+}
+
+type Z_HasPermissionToReturns struct {
+ A bool
+}
+
+func (g *apiRPCClient) HasPermissionTo(userId string, permission *model.Permission) bool {
+ _args := &Z_HasPermissionToArgs{userId, permission}
+ _returns := &Z_HasPermissionToReturns{}
+ if err := g.client.Call("Plugin.HasPermissionTo", _args, _returns); err != nil {
+ log.Printf("RPC call to HasPermissionTo API failed: %s", err.Error())
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) HasPermissionTo(args *Z_HasPermissionToArgs, returns *Z_HasPermissionToReturns) error {
+ if hook, ok := s.impl.(interface {
+ HasPermissionTo(userId string, permission *model.Permission) bool
+ }); ok {
+ returns.A = hook.HasPermissionTo(args.A, args.B)
+ } else {
+ return fmt.Errorf("API HasPermissionTo called but not implemented.")
+ }
+ return nil
+}
+
+type Z_HasPermissionToTeamArgs struct {
+ A string
+ B string
+ C *model.Permission
+}
+
+type Z_HasPermissionToTeamReturns struct {
+ A bool
+}
+
+func (g *apiRPCClient) HasPermissionToTeam(userId, teamId string, permission *model.Permission) bool {
+ _args := &Z_HasPermissionToTeamArgs{userId, teamId, permission}
+ _returns := &Z_HasPermissionToTeamReturns{}
+ if err := g.client.Call("Plugin.HasPermissionToTeam", _args, _returns); err != nil {
+ log.Printf("RPC call to HasPermissionToTeam API failed: %s", err.Error())
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) HasPermissionToTeam(args *Z_HasPermissionToTeamArgs, returns *Z_HasPermissionToTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ HasPermissionToTeam(userId, teamId string, permission *model.Permission) bool
+ }); ok {
+ returns.A = hook.HasPermissionToTeam(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API HasPermissionToTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_HasPermissionToChannelArgs struct {
+ A string
+ B string
+ C *model.Permission
+}
+
+type Z_HasPermissionToChannelReturns struct {
+ A bool
+}
+
+func (g *apiRPCClient) HasPermissionToChannel(userId, channelId string, permission *model.Permission) bool {
+ _args := &Z_HasPermissionToChannelArgs{userId, channelId, permission}
+ _returns := &Z_HasPermissionToChannelReturns{}
+ if err := g.client.Call("Plugin.HasPermissionToChannel", _args, _returns); err != nil {
+ log.Printf("RPC call to HasPermissionToChannel API failed: %s", err.Error())
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) HasPermissionToChannel(args *Z_HasPermissionToChannelArgs, returns *Z_HasPermissionToChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ HasPermissionToChannel(userId, channelId string, permission *model.Permission) bool
+ }); ok {
+ returns.A = hook.HasPermissionToChannel(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API HasPermissionToChannel called but not implemented.")
+ }
+ return nil
+}
+
type Z_LogDebugArgs struct {
A string
B []interface{}
diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go
index cf9ffa6a8..e84ceffd8 100644
--- a/plugin/plugintest/api.go
+++ b/plugin/plugintest/api.go
@@ -37,6 +37,31 @@ func (_m *API) AddChannelMember(channelId string, userId string) (*model.Channel
return r0, r1
}
+// AddReaction provides a mock function with given fields: reaction
+func (_m *API) AddReaction(reaction *model.Reaction) (*model.Reaction, *model.AppError) {
+ ret := _m.Called(reaction)
+
+ var r0 *model.Reaction
+ if rf, ok := ret.Get(0).(func(*model.Reaction) *model.Reaction); ok {
+ r0 = rf(reaction)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Reaction)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Reaction) *model.AppError); ok {
+ r1 = rf(reaction)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// CopyFileInfos provides a mock function with given fields: userId, fileIds
func (_m *API) CopyFileInfos(userId string, fileIds []string) ([]string, *model.AppError) {
ret := _m.Called(userId, fileIds)
@@ -449,6 +474,31 @@ func (_m *API) GetDirectChannel(userId1 string, userId2 string) (*model.Channel,
return r0, r1
}
+// GetFileInfo provides a mock function with given fields: fileId
+func (_m *API) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) {
+ ret := _m.Called(fileId)
+
+ var r0 *model.FileInfo
+ if rf, ok := ret.Get(0).(func(string) *model.FileInfo); ok {
+ r0 = rf(fileId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.FileInfo)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(fileId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// GetGroupChannel provides a mock function with given fields: userIds
func (_m *API) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
ret := _m.Called(userIds)
@@ -524,6 +574,31 @@ func (_m *API) GetPublicChannelsForTeam(teamId string, offset int, limit int) (*
return r0, r1
}
+// GetReactions provides a mock function with given fields: postId
+func (_m *API) GetReactions(postId string) ([]*model.Reaction, *model.AppError) {
+ ret := _m.Called(postId)
+
+ var r0 []*model.Reaction
+ if rf, ok := ret.Get(0).(func(string) []*model.Reaction); ok {
+ r0 = rf(postId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.Reaction)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(postId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// GetSession provides a mock function with given fields: sessionId
func (_m *API) GetSession(sessionId string) (*model.Session, *model.AppError) {
ret := _m.Called(sessionId)
@@ -799,6 +874,48 @@ func (_m *API) GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.A
return r0, r1
}
+// HasPermissionTo provides a mock function with given fields: userId, permission
+func (_m *API) HasPermissionTo(userId string, permission *model.Permission) bool {
+ ret := _m.Called(userId, permission)
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func(string, *model.Permission) bool); ok {
+ r0 = rf(userId, permission)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
+// HasPermissionToChannel provides a mock function with given fields: userId, channelId, permission
+func (_m *API) HasPermissionToChannel(userId string, channelId string, permission *model.Permission) bool {
+ ret := _m.Called(userId, channelId, permission)
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func(string, string, *model.Permission) bool); ok {
+ r0 = rf(userId, channelId, permission)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
+// HasPermissionToTeam provides a mock function with given fields: userId, teamId, permission
+func (_m *API) HasPermissionToTeam(userId string, teamId string, permission *model.Permission) bool {
+ ret := _m.Called(userId, teamId, permission)
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func(string, string, *model.Permission) bool); ok {
+ r0 = rf(userId, teamId, permission)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
// KVDelete provides a mock function with given fields: key
func (_m *API) KVDelete(key string) *model.AppError {
ret := _m.Called(key)
@@ -907,6 +1024,31 @@ func (_m *API) PublishWebSocketEvent(event string, payload map[string]interface{
_m.Called(event, payload, broadcast)
}
+// ReadFile provides a mock function with given fields: path
+func (_m *API) ReadFile(path string) ([]byte, *model.AppError) {
+ ret := _m.Called(path)
+
+ var r0 []byte
+ if rf, ok := ret.Get(0).(func(string) []byte); ok {
+ r0 = rf(path)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]byte)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(path)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// RegisterCommand provides a mock function with given fields: command
func (_m *API) RegisterCommand(command *model.Command) error {
ret := _m.Called(command)
@@ -921,6 +1063,22 @@ func (_m *API) RegisterCommand(command *model.Command) error {
return r0
}
+// RemoveReaction provides a mock function with given fields: reaction
+func (_m *API) RemoveReaction(reaction *model.Reaction) *model.AppError {
+ ret := _m.Called(reaction)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(*model.Reaction) *model.AppError); ok {
+ r0 = rf(reaction)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
// SaveConfig provides a mock function with given fields: config
func (_m *API) SaveConfig(config *model.Config) *model.AppError {
ret := _m.Called(config)
diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go
index e158ba5ea..97f60dda0 100644
--- a/store/sqlstore/channel_store.go
+++ b/store/sqlstore/channel_store.go
@@ -1921,3 +1921,11 @@ func (s SqlChannelStore) ClearAllCustomRoleAssignments() store.StoreChannel {
}
})
}
+
+func (s SqlChannelStore) ResetLastPostAt() store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ if _, err := s.GetMaster().Exec("UPDATE Channels SET LastPostAt = (SELECT UpdateAt FROM Posts WHERE ChannelId = Channels.Id ORDER BY UpdateAt DESC LIMIT 1);"); err != nil {
+ result.Err = model.NewAppError("SqlChannelStore.ResetLastPostAt", "store.sql_channel.reset_last_post_at.app_error", nil, err.Error(), http.StatusInternalServerError)
+ }
+ })
+}
diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go
index 4277a0ba2..d9e33df76 100644
--- a/store/sqlstore/team_store.go
+++ b/store/sqlstore/team_store.go
@@ -913,3 +913,14 @@ func (s SqlTeamStore) ClearAllCustomRoleAssignments() store.StoreChannel {
}
})
}
+
+func (s SqlTeamStore) AnalyticsGetTeamCountForScheme(schemeId string) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ count, err := s.GetReplica().SelectInt("SELECT count(*) FROM Teams WHERE SchemeId = :SchemeId AND DeleteAt = 0", map[string]interface{}{"SchemeId": schemeId})
+ if err != nil {
+ result.Err = model.NewAppError("SqlTeamStore.AnalyticsGetTeamCountForScheme", "store.sql_team.analytics_get_team_count_for_scheme.app_error", nil, "schemeId="+schemeId+" "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ result.Data = count
+ })
+}
diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go
index cc4e5a0cc..ab3bd202b 100644
--- a/store/sqlstore/upgrade.go
+++ b/store/sqlstore/upgrade.go
@@ -15,6 +15,7 @@ import (
)
const (
+ VERSION_5_3_0 = "5.3.0"
VERSION_5_2_0 = "5.2.0"
VERSION_5_1_0 = "5.1.0"
VERSION_5_0_0 = "5.0.0"
@@ -82,6 +83,7 @@ func UpgradeDatabase(sqlStore SqlStore) {
UpgradeDatabaseToVersion50(sqlStore)
UpgradeDatabaseToVersion51(sqlStore)
UpgradeDatabaseToVersion52(sqlStore)
+ UpgradeDatabaseToVersion53(sqlStore)
// If the SchemaVersion is empty this this is the first time it has ran
// so lets set it to the current version.
@@ -480,3 +482,11 @@ func UpgradeDatabaseToVersion52(sqlStore SqlStore) {
saveSchemaVersion(sqlStore, VERSION_5_2_0)
}
}
+
+func UpgradeDatabaseToVersion53(sqlStore SqlStore) {
+ // TODO: Uncomment following condition when version 5.3.0 is released
+ // if shouldPerformUpgrade(sqlStore, VERSION_5_2_0, VERSION_5_3_0) {
+
+ // saveSchemaVersion(sqlStore, VERSION_5_3_0)
+ // }
+}
diff --git a/store/store.go b/store/store.go
index 89adce188..0c89a0a91 100644
--- a/store/store.go
+++ b/store/store.go
@@ -110,6 +110,7 @@ type TeamStore interface {
MigrateTeamMembers(fromTeamId string, fromUserId string) StoreChannel
ResetAllTeamSchemes() StoreChannel
ClearAllCustomRoleAssignments() StoreChannel
+ AnalyticsGetTeamCountForScheme(schemeId string) StoreChannel
}
type ChannelStore interface {
@@ -171,6 +172,7 @@ type ChannelStore interface {
MigrateChannelMembers(fromChannelId string, fromUserId string) StoreChannel
ResetAllChannelSchemes() StoreChannel
ClearAllCustomRoleAssignments() StoreChannel
+ ResetLastPostAt() StoreChannel
}
type ChannelMemberHistoryStore interface {
diff --git a/store/storetest/compliance_store.go b/store/storetest/compliance_store.go
index a772f6e44..f7f095a00 100644
--- a/store/storetest/compliance_store.go
+++ b/store/storetest/compliance_store.go
@@ -10,6 +10,7 @@ import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestComplianceStore(t *testing.T, ss store.Store) {
@@ -35,9 +36,8 @@ func testComplianceStore(t *testing.T, ss store.Store) {
result := <-c
compliances := result.Data.(model.Compliances)
- if compliances[0].Status != model.COMPLIANCE_STATUS_RUNNING && compliance2.Id != compliances[0].Id {
- t.Fatal()
- }
+ require.Equal(t, model.COMPLIANCE_STATUS_RUNNING, compliances[0].Status)
+ require.Equal(t, compliance2.Id, compliances[0].Id)
compliance2.Status = model.COMPLIANCE_STATUS_FAILED
store.Must(ss.Compliance().Update(compliance2))
@@ -46,17 +46,14 @@ func testComplianceStore(t *testing.T, ss store.Store) {
result = <-c
compliances = result.Data.(model.Compliances)
- if compliances[0].Status != model.COMPLIANCE_STATUS_FAILED && compliance2.Id != compliances[0].Id {
- t.Fatal()
- }
+ require.Equal(t, model.COMPLIANCE_STATUS_FAILED, compliances[0].Status)
+ require.Equal(t, compliance2.Id, compliances[0].Id)
c = ss.Compliance().GetAll(0, 1)
result = <-c
compliances = result.Data.(model.Compliances)
- if len(compliances) != 1 {
- t.Fatal("should only have returned 1")
- }
+ require.Len(t, compliances, 1)
c = ss.Compliance().GetAll(1, 1)
result = <-c
@@ -67,9 +64,7 @@ func testComplianceStore(t *testing.T, ss store.Store) {
}
rc2 := (<-ss.Compliance().Get(compliance2.Id)).Data.(*model.Compliance)
- if rc2.Status != compliance2.Status {
- t.Fatal()
- }
+ require.Equal(t, compliance2.Status, rc2.Status)
}
func testComplianceExport(t *testing.T, ss store.Store) {
diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go
index 8adc98e10..747a844ec 100644
--- a/store/storetest/mocks/ChannelStore.go
+++ b/store/storetest/mocks/ChannelStore.go
@@ -711,6 +711,22 @@ func (_m *ChannelStore) ResetAllChannelSchemes() store.StoreChannel {
return r0
}
+// ResetLastPostAt provides a mock function with given fields:
+func (_m *ChannelStore) ResetLastPostAt() store.StoreChannel {
+ ret := _m.Called()
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func() store.StoreChannel); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
// Restore provides a mock function with given fields: channelId, time
func (_m *ChannelStore) Restore(channelId string, time int64) store.StoreChannel {
ret := _m.Called(channelId, time)
diff --git a/store/storetest/mocks/TeamStore.go b/store/storetest/mocks/TeamStore.go
index db5cb658f..8e27e3c05 100644
--- a/store/storetest/mocks/TeamStore.go
+++ b/store/storetest/mocks/TeamStore.go
@@ -13,6 +13,22 @@ type TeamStore struct {
mock.Mock
}
+// AnalyticsGetTeamCountForScheme provides a mock function with given fields: schemeId
+func (_m *TeamStore) AnalyticsGetTeamCountForScheme(schemeId string) store.StoreChannel {
+ ret := _m.Called(schemeId)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok {
+ r0 = rf(schemeId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
// AnalyticsTeamCount provides a mock function with given fields:
func (_m *TeamStore) AnalyticsTeamCount() store.StoreChannel {
ret := _m.Called()
diff --git a/store/storetest/status_store.go b/store/storetest/status_store.go
index b26be4c19..5231bc29a 100644
--- a/store/storetest/status_store.go
+++ b/store/storetest/status_store.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
@@ -103,9 +104,7 @@ func testActiveUserCount(t *testing.T, ss store.Store) {
t.Fatal(result.Err)
} else {
count := result.Data.(int64)
- if count <= 0 {
- t.Fatal()
- }
+ require.True(t, count > 0, "expected count > 0, got %d", count)
}
}
diff --git a/store/storetest/system_store.go b/store/storetest/system_store.go
index a06b72a83..6dc1efe41 100644
--- a/store/storetest/system_store.go
+++ b/store/storetest/system_store.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
@@ -25,9 +26,7 @@ func testSystemStore(t *testing.T, ss store.Store) {
result := <-ss.System().Get()
systems := result.Data.(model.StringMap)
- if systems[system.Name] != system.Value {
- t.Fatal()
- }
+ require.Equal(t, system.Value, systems[system.Name])
system.Value = "value2"
store.Must(ss.System().Update(system))
@@ -35,15 +34,11 @@ func testSystemStore(t *testing.T, ss store.Store) {
result2 := <-ss.System().Get()
systems2 := result2.Data.(model.StringMap)
- if systems2[system.Name] != system.Value {
- t.Fatal()
- }
+ require.Equal(t, system.Value, systems2[system.Name])
result3 := <-ss.System().GetByName(system.Name)
rsystem := result3.Data.(*model.System)
- if rsystem.Value != system.Value {
- t.Fatal()
- }
+ require.Equal(t, system.Value, rsystem.Value)
}
func testSystemStoreSaveOrUpdate(t *testing.T, ss store.Store) {
diff --git a/store/storetest/team_store.go b/store/storetest/team_store.go
index 40d69a2f2..ede1a91d3 100644
--- a/store/storetest/team_store.go
+++ b/store/storetest/team_store.go
@@ -44,6 +44,7 @@ func TestTeamStore(t *testing.T, ss store.Store) {
t.Run("MigrateTeamMembers", func(t *testing.T) { testTeamStoreMigrateTeamMembers(t, ss) })
t.Run("ResetAllTeamSchemes", func(t *testing.T) { testResetAllTeamSchemes(t, ss) })
t.Run("ClearAllCustomRoleAssignments", func(t *testing.T) { testTeamStoreClearAllCustomRoleAssignments(t, ss) })
+ t.Run("AnalyticsGetTeamCountForScheme", func(t *testing.T) { testTeamStoreAnalyticsGetTeamCountForScheme(t, ss) })
}
func testTeamStoreSave(t *testing.T, ss store.Store) {
@@ -590,10 +591,7 @@ func testTeamMembers(t *testing.T, ss store.Store) {
t.Fatal(r1.Err)
} else {
ms := r1.Data.([]*model.TeamMember)
-
- if len(ms) != 2 {
- t.Fatal()
- }
+ require.Len(t, ms, 2)
}
if r1 := <-ss.Team().GetMembers(teamId2, 0, 100); r1.Err != nil {
@@ -601,14 +599,8 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 1 {
- t.Fatal()
- }
-
- if ms[0].UserId != m3.UserId {
- t.Fatal()
-
- }
+ require.Len(t, ms, 1)
+ require.Equal(t, m3.UserId, ms[0].UserId)
}
if r1 := <-ss.Team().GetTeamsForUser(m1.UserId); r1.Err != nil {
@@ -616,14 +608,8 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 1 {
- t.Fatal()
- }
-
- if ms[0].TeamId != m1.TeamId {
- t.Fatal()
-
- }
+ require.Len(t, ms, 1)
+ require.Equal(t, m1.TeamId, ms[0].TeamId)
}
if r1 := <-ss.Team().RemoveMember(teamId1, m1.UserId); r1.Err != nil {
@@ -635,14 +621,8 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 1 {
- t.Fatal()
- }
-
- if ms[0].UserId != m2.UserId {
- t.Fatal()
-
- }
+ require.Len(t, ms, 1)
+ require.Equal(t, m2.UserId, ms[0].UserId)
}
store.Must(ss.Team().SaveMember(m1, -1))
@@ -656,9 +636,7 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 0 {
- t.Fatal()
- }
+ require.Len(t, ms, 0)
}
uid := model.NewId()
@@ -672,9 +650,7 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 2 {
- t.Fatal()
- }
+ require.Len(t, ms, 2)
}
if r1 := <-ss.Team().RemoveAllMembersByUser(uid); r1.Err != nil {
@@ -686,9 +662,7 @@ func testTeamMembers(t *testing.T, ss store.Store) {
} else {
ms := r1.Data.([]*model.TeamMember)
- if len(ms) != 0 {
- t.Fatal()
- }
+ require.Len(t, ms, 0)
}
}
@@ -1292,3 +1266,64 @@ func testTeamStoreClearAllCustomRoleAssignments(t *testing.T, ss store.Store) {
require.Nil(t, r4.Err)
assert.Equal(t, "", r4.Data.(*model.TeamMember).Roles)
}
+
+func testTeamStoreAnalyticsGetTeamCountForScheme(t *testing.T, ss store.Store) {
+ s1 := &model.Scheme{
+ DisplayName: model.NewId(),
+ Name: model.NewId(),
+ Description: model.NewId(),
+ Scope: model.SCHEME_SCOPE_TEAM,
+ }
+ s1 = (<-ss.Scheme().Save(s1)).Data.(*model.Scheme)
+
+ count1 := (<-ss.Team().AnalyticsGetTeamCountForScheme(s1.Id)).Data.(int64)
+ assert.Equal(t, int64(0), count1)
+
+ t1 := &model.Team{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Email: MakeEmail(),
+ Type: model.TEAM_OPEN,
+ SchemeId: &s1.Id,
+ }
+ t1 = (<-ss.Team().Save(t1)).Data.(*model.Team)
+
+ count2 := (<-ss.Team().AnalyticsGetTeamCountForScheme(s1.Id)).Data.(int64)
+ assert.Equal(t, int64(1), count2)
+
+ t2 := &model.Team{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Email: MakeEmail(),
+ Type: model.TEAM_OPEN,
+ SchemeId: &s1.Id,
+ }
+ t2 = (<-ss.Team().Save(t2)).Data.(*model.Team)
+
+ count3 := (<-ss.Team().AnalyticsGetTeamCountForScheme(s1.Id)).Data.(int64)
+ assert.Equal(t, int64(2), count3)
+
+ t3 := &model.Team{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Email: MakeEmail(),
+ Type: model.TEAM_OPEN,
+ }
+ t3 = (<-ss.Team().Save(t3)).Data.(*model.Team)
+
+ count4 := (<-ss.Team().AnalyticsGetTeamCountForScheme(s1.Id)).Data.(int64)
+ assert.Equal(t, int64(2), count4)
+
+ t4 := &model.Team{
+ Name: model.NewId(),
+ DisplayName: model.NewId(),
+ Email: MakeEmail(),
+ Type: model.TEAM_OPEN,
+ SchemeId: &s1.Id,
+ DeleteAt: model.GetMillis(),
+ }
+ t4 = (<-ss.Team().Save(t4)).Data.(*model.Team)
+
+ count5 := (<-ss.Team().AnalyticsGetTeamCountForScheme(s1.Id)).Data.(int64)
+ assert.Equal(t, int64(2), count5)
+}
diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go
index 10fb6a4d9..d1a373f9b 100644
--- a/store/storetest/user_store.go
+++ b/store/storetest/user_store.go
@@ -246,9 +246,7 @@ func testUserCount(t *testing.T, ss store.Store) {
t.Fatal(result.Err)
} else {
count := result.Data.(int64)
- if count <= 0 {
- t.Fatal()
- }
+ require.False(t, count <= 0, "expected count > 0, got %d", count)
}
}
diff --git a/utils/markdown/autolink.go b/utils/markdown/autolink.go
index 16c40e609..7f7d1117f 100644
--- a/utils/markdown/autolink.go
+++ b/utils/markdown/autolink.go
@@ -16,27 +16,27 @@ var (
DefaultUrlSchemes = []string{"http", "https", "ftp", "mailto", "tel"}
)
-// Given a string with a w at the given position, tries to parse and return a link starting with "www."
+// Given a string with a w at the given position, tries to parse and return a range containing a www link.
// if one exists. If the text at the given position isn't a link, returns an empty string. Equivalent to
// www_match from the reference code.
-func parseWWWAutolink(data string, position int) string {
+func parseWWWAutolink(data string, position int) (Range, bool) {
// Check that this isn't part of another word
if position > 1 {
prevChar := data[position-1]
if !isWhitespaceByte(prevChar) && !isAllowedBeforeWWWLink(prevChar) {
- return ""
+ return Range{}, false
}
}
// Check that this starts with www
if len(data)-position < 4 || !regexp.MustCompile(`^www\d{0,3}\.`).MatchString(data[position:]) {
- return ""
+ return Range{}, false
}
end := checkDomain(data[position:], false)
if end == 0 {
- return ""
+ return Range{}, false
}
end += position
@@ -47,12 +47,12 @@ func parseWWWAutolink(data string, position int) string {
}
// Trim trailing punctuation
- link := trimTrailingCharactersFromLink(data[position:end])
- if link == "" {
- return ""
+ end = trimTrailingCharactersFromLink(data, position, end)
+ if position == end {
+ return Range{}, false
}
- return link
+ return Range{position, end}, true
}
func isAllowedBeforeWWWLink(c byte) bool {
@@ -64,13 +64,13 @@ func isAllowedBeforeWWWLink(c byte) bool {
}
}
-// Given a string with a : at the given position, tried to parse and return a link starting with a URL scheme
+// Given a string with a : at the given position, tried to parse and return a range containing a URL scheme
// if one exists. If the text around the given position isn't a link, returns an empty string. Equivalent to
// url_match from the reference code.
-func parseURLAutolink(data string, position int) string {
+func parseURLAutolink(data string, position int) (Range, bool) {
// Check that a :// exists. This doesn't match the clients that treat the slashes as optional.
if len(data)-position < 4 || data[position+1] != '/' || data[position+2] != '/' {
- return ""
+ return Range{}, false
}
start := position - 1
@@ -81,12 +81,12 @@ func parseURLAutolink(data string, position int) string {
// Ensure that the URL scheme is allowed and that at least one character after the scheme is valid.
scheme := data[start:position]
if !isSchemeAllowed(scheme) || !isValidHostCharacter(data[position+3:]) {
- return ""
+ return Range{}, false
}
end := checkDomain(data[position+3:], true)
if end == 0 {
- return ""
+ return Range{}, false
}
end += position
@@ -97,12 +97,12 @@ func parseURLAutolink(data string, position int) string {
}
// Trim trailing punctuation
- link := trimTrailingCharactersFromLink(data[start:end])
- if link == "" {
- return ""
+ end = trimTrailingCharactersFromLink(data, start, end)
+ if start == end {
+ return Range{}, false
}
- return link
+ return Range{start, end}, true
}
func isSchemeAllowed(scheme string) bool {
@@ -166,9 +166,9 @@ func isValidHostCharacter(link string) bool {
}
// Removes any trailing characters such as punctuation or stray brackets that shouldn't be part of the link.
-// Equivalent to autolink_delim from the reference code.
-func trimTrailingCharactersFromLink(link string) string {
- runes := []rune(link)
+// Returns a new end position for the link. Equivalent to autolink_delim from the reference code.
+func trimTrailingCharactersFromLink(markdown string, start int, end int) int {
+ runes := []rune(markdown[start:end])
linkEnd := len(runes)
// Cut off the link before an open angle bracket if it contains one
@@ -240,7 +240,7 @@ func trimTrailingCharactersFromLink(link string) string {
}
}
- return string(runes[:linkEnd])
+ return start + len(string(runes[:linkEnd]))
}
func canEndAutolink(c rune) bool {
diff --git a/utils/markdown/autolink_test.go b/utils/markdown/autolink_test.go
index d0ea53fa4..997124338 100644
--- a/utils/markdown/autolink_test.go
+++ b/utils/markdown/autolink_test.go
@@ -134,7 +134,15 @@ func TestParseURLAutolink(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
- assert.Equal(t, testCase.Expected, parseURLAutolink(testCase.Input, testCase.Position))
+ rawRange, ok := parseURLAutolink(testCase.Input, testCase.Position)
+
+ if testCase.Expected == "" {
+ assert.False(t, ok)
+ assert.Equal(t, Range{0, 0}, rawRange)
+ } else {
+ assert.True(t, ok)
+ assert.Equal(t, testCase.Expected, testCase.Input[rawRange.Position:rawRange.End])
+ }
})
}
}
@@ -264,89 +272,153 @@ func TestParseWWWAutolink(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
- assert.Equal(t, testCase.Expected, parseWWWAutolink(testCase.Input, testCase.Position))
+ rawRange, ok := parseWWWAutolink(testCase.Input, testCase.Position)
+
+ if testCase.Expected == "" {
+ assert.False(t, ok)
+ assert.Equal(t, Range{0, 0}, rawRange)
+ } else {
+ assert.True(t, ok)
+ assert.Equal(t, testCase.Expected, testCase.Input[rawRange.Position:rawRange.End])
+ }
})
}
}
func TestTrimTrailingCharactersFromLink(t *testing.T) {
testCases := []struct {
- Input string
- Expected string
+ Input string
+ Start int
+ End int
+ ExpectedEnd int
}{
{
- Input: "http://www.example.com",
- Expected: "http://www.example.com",
+ Input: "http://www.example.com",
+ ExpectedEnd: 22,
+ },
+ {
+ Input: "http://www.example.com/abcd",
+ ExpectedEnd: 27,
+ },
+ {
+ Input: "http://www.example.com/abcd/",
+ ExpectedEnd: 28,
+ },
+ {
+ Input: "http://www.example.com/1234",
+ ExpectedEnd: 27,
+ },
+ {
+ Input: "http://www.example.com/abcd?foo=bar",
+ ExpectedEnd: 35,
},
{
- Input: "http://www.example.com/abcd",
- Expected: "http://www.example.com/abcd",
+ Input: "http://www.example.com/abcd#heading",
+ ExpectedEnd: 35,
},
{
- Input: "http://www.example.com/abcd/",
- Expected: "http://www.example.com/abcd/",
+ Input: "http://www.example.com.",
+ ExpectedEnd: 22,
},
{
- Input: "http://www.example.com/1234",
- Expected: "http://www.example.com/1234",
+ Input: "http://www.example.com,",
+ ExpectedEnd: 22,
},
{
- Input: "http://www.example.com/abcd?foo=bar",
- Expected: "http://www.example.com/abcd?foo=bar",
+ Input: "http://www.example.com?",
+ ExpectedEnd: 22,
},
{
- Input: "http://www.example.com/abcd#heading",
- Expected: "http://www.example.com/abcd#heading",
+ Input: "http://www.example.com)",
+ ExpectedEnd: 22,
},
{
- Input: "http://www.example.com.",
- Expected: "http://www.example.com",
+ Input: "http://www.example.com",
+ ExpectedEnd: 22,
},
{
- Input: "http://www.example.com,",
- Expected: "http://www.example.com",
+ Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)",
+ ExpectedEnd: 54,
},
{
- Input: "http://www.example.com?",
- Expected: "http://www.example.com",
+ Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation",
+ ExpectedEnd: 53,
},
{
- Input: "http://www.example.com)",
- Expected: "http://www.example.com",
+ Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation))",
+ ExpectedEnd: 54,
},
{
- Input: "http://www.example.com",
- Expected: "http://www.example.com",
+ Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)_(disambiguation)",
+ ExpectedEnd: 71,
},
{
- Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)",
- Expected: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)",
+ Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation_(disambiguation))",
+ ExpectedEnd: 71,
},
{
- Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation",
- Expected: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation",
+ Input: "http://www.example.com&quot;",
+ ExpectedEnd: 22,
},
{
- Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation))",
- Expected: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)",
+ Input: "this is a sentence containing http://www.example.com in it",
+ Start: 30,
+ End: 52,
+ ExpectedEnd: 52,
},
{
- Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)_(disambiguation)",
- Expected: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation)_(disambiguation)",
+ Input: "this is a sentence containing http://www.example.com???",
+ Start: 30,
+ End: 55,
+ ExpectedEnd: 52,
},
{
- Input: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation_(disambiguation))",
- Expected: "https://en.wikipedia.org/wiki/Dolphin_(disambiguation_(disambiguation))",
+ Input: "http://google.com/å",
+ ExpectedEnd: len("http://google.com/å"),
},
{
- Input: "http://www.example.com&quot;",
- Expected: "http://www.example.com",
+ Input: "http://google.com/å...",
+ ExpectedEnd: len("http://google.com/å"),
+ },
+ {
+ Input: "This is http://google.com/å, a link, and http://google.com/å",
+ Start: 8,
+ End: len("This is http://google.com/å,"),
+ ExpectedEnd: len("This is http://google.com/å"),
+ },
+ {
+ Input: "This is http://google.com/å, a link, and http://google.com/å",
+ Start: 41,
+ End: len("This is http://google.com/å, a link, and http://google.com/å"),
+ ExpectedEnd: len("This is http://google.com/å, a link, and http://google.com/å"),
+ },
+ {
+ Input: "This is http://google.com/å, a link, and http://google.com/å.",
+ Start: 41,
+ End: len("This is http://google.com/å, a link, and http://google.com/å."),
+ ExpectedEnd: len("This is http://google.com/å, a link, and http://google.com/å"),
+ },
+ {
+ Input: "http://🍄.ga/ http://x🍄.ga/",
+ Start: 0,
+ End: len("http://🍄.ga/"),
+ ExpectedEnd: len("http://🍄.ga/"),
+ },
+ {
+ Input: "http://🍄.ga/ http://x🍄.ga/",
+ Start: len("http://🍄.ga/ "),
+ End: len("http://🍄.ga/ http://x🍄.ga/"),
+ ExpectedEnd: len("http://🍄.ga/ http://x🍄.ga/"),
},
}
for _, testCase := range testCases {
t.Run(testCase.Input, func(t *testing.T) {
- assert.Equal(t, testCase.Expected, trimTrailingCharactersFromLink(testCase.Input))
+ if testCase.End == 0 {
+ testCase.End = len(testCase.Input) - testCase.Start
+ }
+
+ assert.Equal(t, testCase.ExpectedEnd, trimTrailingCharactersFromLink(testCase.Input, testCase.Start, testCase.End))
})
}
}
diff --git a/utils/markdown/commonmark_test.go b/utils/markdown/commonmark_test.go
index 13e61f52d..d1381cee5 100644
--- a/utils/markdown/commonmark_test.go
+++ b/utils/markdown/commonmark_test.go
@@ -1000,7 +1000,7 @@ func TestCommonMarkReferenceStrings(t *testing.T) {
}
}
-func TestCommonMarkRefernceAutolinks(t *testing.T) {
+func TestCommonMarkReferenceAutolinks(t *testing.T) {
// These tests are adapted from the GitHub-flavoured CommonMark extension tests located at
// https://github.com/github/cmark/blob/master/test/extensions.txt
for name, tc := range map[string]struct {
diff --git a/utils/markdown/html.go b/utils/markdown/html.go
index 1a857afed..afb72bff3 100644
--- a/utils/markdown/html.go
+++ b/utils/markdown/html.go
@@ -157,7 +157,7 @@ func RenderInlineHTML(inline Inline) (result string) {
}
result += "</a>"
case *Autolink:
- result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Link)) + `">`
+ result += `<a href="` + htmlEscaper.Replace(escapeURL(v.Destination())) + `">`
for _, inline := range v.Children {
result += RenderInlineHTML(inline)
}
diff --git a/utils/markdown/inlines.go b/utils/markdown/inlines.go
index 453f4bbe5..a3abccef3 100644
--- a/utils/markdown/inlines.go
+++ b/utils/markdown/inlines.go
@@ -86,7 +86,19 @@ type Autolink struct {
Children []Inline
- Link string
+ RawDestination Range
+
+ markdown string
+}
+
+func (i *Autolink) Destination() string {
+ destination := Unescape(i.markdown[i.RawDestination.Position:i.RawDestination.End])
+
+ if strings.HasPrefix(destination, "www") {
+ destination = "http://" + destination
+ }
+
+ return destination
}
type delimiterType int
@@ -486,15 +498,18 @@ func (p *inlineParser) parseAutolink(c rune) bool {
}
}
- link := ""
- text := ""
+ var link Range
if c == ':' {
- text = parseURLAutolink(p.raw, p.position)
- link = text
+ var ok bool
+ link, ok = parseURLAutolink(p.raw, p.position)
+
+ if !ok {
+ return false
+ }
// Since the current position is at the colon, we have to rewind the parsing slightly so that
// we don't duplicate the URL scheme
- rewind := strings.Index(text, ":")
+ rewind := strings.Index(p.raw[link.Position:link.End], ":")
if rewind != -1 {
lastInline := p.inlines[len(p.inlines)-1]
lastText, ok := lastInline.(*Text)
@@ -512,22 +527,30 @@ func (p *inlineParser) parseAutolink(c rune) bool {
Range: Range{lastText.Range.Position, lastText.Range.End - rewind},
})
p.position -= rewind
+ }
+ } else if c == 'w' || c == 'W' {
+ var ok bool
+ link, ok = parseWWWAutolink(p.raw, p.position)
+ if !ok {
+ return false
}
- } else if c == 'w' {
- text = parseWWWAutolink(p.raw, p.position)
- link = "http://" + text
}
- if text == "" {
- return false
- }
+ linkMarkdownPosition := relativeToAbsolutePosition(p.ranges, link.Position)
+ linkRange := Range{linkMarkdownPosition, linkMarkdownPosition + link.End - link.Position}
p.inlines = append(p.inlines, &Autolink{
- Link: link,
- Children: []Inline{&Text{Text: text}},
+ Children: []Inline{
+ &Text{
+ Text: p.raw[link.Position:link.End],
+ Range: linkRange,
+ },
+ },
+ RawDestination: linkRange,
+ markdown: p.markdown,
})
- p.position += len(text)
+ p.position += (link.End - link.Position)
return true
}