summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2018-03-19 10:53:37 +0000
committerGeorge Goldberg <george@gberg.me>2018-03-19 10:53:37 +0000
commit37f0e5e0ebc0595efe2c65ffb84fa096dc8c5493 (patch)
tree4b791b279109a10a66b62aaeeeae3d5243d2961b
parentfadcdd271a68b38571b75d1d38ab023f940ac83a (diff)
parent4b675b347b5241def7807fab5e01ce9b98531815 (diff)
downloadchat-37f0e5e0ebc0595efe2c65ffb84fa096dc8c5493.tar.gz
chat-37f0e5e0ebc0595efe2c65ffb84fa096dc8c5493.tar.bz2
chat-37f0e5e0ebc0595efe2c65ffb84fa096dc8c5493.zip
Merge branch 'master' into advanced-permissions-phase-1
-rw-r--r--api4/system.go4
-rw-r--r--api4/system_test.go57
-rw-r--r--app/notification.go15
-rw-r--r--app/plugin.go33
-rw-r--r--app/plugin_test.go25
-rw-r--r--cmd/commands/user.go4
-rw-r--r--i18n/de.json34
-rw-r--r--i18n/en.json4
-rw-r--r--i18n/it.json2
-rw-r--r--i18n/ko.json56
-rw-r--r--i18n/nl.json2
-rw-r--r--i18n/pt-BR.json22
-rw-r--r--i18n/zh-CN.json10
-rw-r--r--i18n/zh-TW.json26
-rw-r--r--mkdocs.yml11
-rw-r--r--model/client4.go4
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_linux.go49
-rw-r--r--store/sqlstore/post_store.go85
-rw-r--r--store/storetest/post_store.go248
-rw-r--r--utils/api.go1
-rw-r--r--utils/config.go3
-rw-r--r--utils/file_backend_s3.go7
-rw-r--r--utils/file_backend_s3_test.go10
-rw-r--r--utils/mail.go123
-rw-r--r--utils/mail_test.go76
25 files changed, 570 insertions, 341 deletions
diff --git a/api4/system.go b/api4/system.go
index c1541f0b5..7b63afc0b 100644
--- a/api4/system.go
+++ b/api4/system.go
@@ -395,6 +395,10 @@ func testS3(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if cfg.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING {
+ cfg.FileSettings.AmazonS3SecretAccessKey = c.App.Config().FileSettings.AmazonS3SecretAccessKey
+ }
+
license := c.App.License()
backend, appErr := utils.NewFileBackend(&cfg.FileSettings, license != nil && *license.Features.Compliance)
if appErr == nil {
diff --git a/api4/system_test.go b/api4/system_test.go
index e39486b77..6ef02cbfe 100644
--- a/api4/system_test.go
+++ b/api4/system_test.go
@@ -262,28 +262,34 @@ func TestEmailTest(t *testing.T) {
defer th.TearDown()
Client := th.Client
- SendEmailNotifications := th.App.Config().EmailSettings.SendEmailNotifications
- SMTPServer := th.App.Config().EmailSettings.SMTPServer
- SMTPPort := th.App.Config().EmailSettings.SMTPPort
- FeedbackEmail := th.App.Config().EmailSettings.FeedbackEmail
- defer func() {
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SendEmailNotifications = SendEmailNotifications })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPServer = SMTPServer })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPPort = SMTPPort })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.FeedbackEmail = FeedbackEmail })
- }()
-
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SendEmailNotifications = false })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPServer = "" })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.SMTPPort = "" })
- th.App.UpdateConfig(func(cfg *model.Config) { cfg.EmailSettings.FeedbackEmail = "" })
+ config := model.Config{
+ EmailSettings: model.EmailSettings{
+ SMTPServer: "",
+ SMTPPort: "",
+ },
+ }
- _, resp := Client.TestEmail()
+ _, resp := Client.TestEmail(&config)
CheckForbiddenStatus(t, resp)
- _, resp = th.SystemAdminClient.TestEmail()
+ _, resp = th.SystemAdminClient.TestEmail(&config)
CheckErrorMessage(t, resp, "api.admin.test_email.missing_server")
CheckBadRequestStatus(t, resp)
+
+ inbucket_host := os.Getenv("CI_HOST")
+ if inbucket_host == "" {
+ inbucket_host = "dockerhost"
+ }
+
+ inbucket_port := os.Getenv("CI_INBUCKET_PORT")
+ if inbucket_port == "" {
+ inbucket_port = "9000"
+ }
+
+ config.EmailSettings.SMTPServer = inbucket_host
+ config.EmailSettings.SMTPPort = inbucket_port
+ _, resp = th.SystemAdminClient.TestEmail(&config)
+ CheckOKStatus(t, resp)
}
func TestDatabaseRecycle(t *testing.T) {
@@ -491,7 +497,7 @@ func TestS3TestConnection(t *testing.T) {
AmazonS3AccessKeyId: model.MINIO_ACCESS_KEY,
AmazonS3SecretAccessKey: model.MINIO_SECRET_KEY,
AmazonS3Bucket: "",
- AmazonS3Endpoint: "",
+ AmazonS3Endpoint: s3Endpoint,
AmazonS3SSL: model.NewBool(false),
},
}
@@ -506,20 +512,11 @@ func TestS3TestConnection(t *testing.T) {
}
config.FileSettings.AmazonS3Bucket = model.MINIO_BUCKET
+ config.FileSettings.AmazonS3Region = "us-east-1"
_, resp = th.SystemAdminClient.TestS3Connection(&config)
- CheckBadRequestStatus(t, resp)
- if resp.Error.Message != "S3 Endpoint is required" {
- t.Fatal("should return error - missing s3 endpoint")
- }
-
- config.FileSettings.AmazonS3Endpoint = s3Endpoint
- _, resp = th.SystemAdminClient.TestS3Connection(&config)
- CheckBadRequestStatus(t, resp)
- if resp.Error.Message != "S3 Region is required" {
- t.Fatal("should return error - missing s3 region")
- }
+ CheckOKStatus(t, resp)
- config.FileSettings.AmazonS3Region = "us-east-1"
+ config.FileSettings.AmazonS3Region = ""
_, resp = th.SystemAdminClient.TestS3Connection(&config)
CheckOKStatus(t, resp)
diff --git a/app/notification.go b/app/notification.go
index 8cb63fbaf..bb0c8703f 100644
--- a/app/notification.go
+++ b/app/notification.go
@@ -55,10 +55,15 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
if channel.Type == model.CHANNEL_DIRECT {
var otherUserId string
- if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
- otherUserId = userIds[1]
- } else {
- otherUserId = userIds[0]
+
+ userIds := strings.Split(channel.Name, "__")
+
+ if userIds[0] != userIds[1] {
+ if userIds[0] == post.UserId {
+ otherUserId = userIds[1]
+ } else {
+ otherUserId = userIds[0]
+ }
}
if _, ok := profileMap[otherUserId]; ok {
@@ -89,7 +94,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
delete(mentionedUserIds, post.UserId)
}
- if len(m.OtherPotentialMentions) > 0 {
+ if len(m.OtherPotentialMentions) > 0 && !post.IsSystemMessage() {
if result := <-a.Srv.Store.User().GetProfilesByUsernames(m.OtherPotentialMentions, team.Id); result.Err == nil {
outOfChannelMentions := result.Data.([]*model.User)
if channel.Type != model.CHANNEL_GROUP {
diff --git a/app/plugin.go b/app/plugin.go
index fe671d26a..6702e9227 100644
--- a/app/plugin.go
+++ b/app/plugin.go
@@ -91,18 +91,11 @@ func (a *App) ActivatePlugins() {
active := a.PluginEnv.IsPluginActive(id)
if pluginState.Enable && !active {
- if err := a.PluginEnv.ActivatePlugin(id); err != nil {
- l4g.Error(err.Error())
+ if err := a.activatePlugin(plugin.Manifest); err != nil {
+ l4g.Error("%v plugin enabled in config.json but failing to activate err=%v", plugin.Manifest.Id, err.DetailedError)
continue
}
- if plugin.Manifest.HasClient() {
- message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil)
- message.Add("manifest", plugin.Manifest.ClientManifest())
- a.Publish(message)
- }
-
- l4g.Info("Activated %v plugin", id)
} else if !pluginState.Enable && active {
if err := a.deactivatePlugin(plugin.Manifest); err != nil {
l4g.Error(err.Error())
@@ -111,6 +104,21 @@ func (a *App) ActivatePlugins() {
}
}
+func (a *App) activatePlugin(manifest *model.Manifest) *model.AppError {
+ if err := a.PluginEnv.ActivatePlugin(manifest.Id); err != nil {
+ return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if manifest.HasClient() {
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil)
+ message.Add("manifest", manifest.ClientManifest())
+ a.Publish(message)
+ }
+
+ l4g.Info("Activated %v plugin", manifest.Id)
+ return nil
+}
+
func (a *App) deactivatePlugin(manifest *model.Manifest) *model.AppError {
if err := a.PluginEnv.DeactivatePlugin(manifest.Id); err != nil {
return model.NewAppError("removePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
@@ -301,11 +309,18 @@ func (a *App) EnablePlugin(id string) *model.AppError {
return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
}
+ if err := a.activatePlugin(manifest); err != nil {
+ return err
+ }
+
a.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true}
})
if err := a.SaveConfig(a.Config(), true); err != nil {
+ if err.Id == "ent.cluster.save_config.error" {
+ return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError)
+ }
return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
diff --git a/app/plugin_test.go b/app/plugin_test.go
index 4794d2704..9ad5dc1fa 100644
--- a/app/plugin_test.go
+++ b/app/plugin_test.go
@@ -4,8 +4,10 @@
package app
import (
+ "errors"
"net/http"
"net/http/httptest"
+ "strings"
"testing"
"github.com/gorilla/mux"
@@ -195,3 +197,26 @@ func TestPluginCommands(t *testing.T) {
require.NotNil(t, err)
assert.Equal(t, http.StatusNotFound, err.StatusCode)
}
+
+type pluginBadActivation struct {
+ testPlugin
+}
+
+func (p *pluginBadActivation) OnActivate(api plugin.API) error {
+ return errors.New("won't activate for some reason")
+}
+
+func TestPluginBadActivation(t *testing.T) {
+ th := Setup().InitBasic()
+ defer th.TearDown()
+
+ th.InstallPlugin(&model.Manifest{
+ Id: "foo",
+ }, &pluginBadActivation{})
+
+ t.Run("EnablePlugin bad activation", func(t *testing.T) {
+ err := th.App.EnablePlugin("foo")
+ assert.NotNil(t, err)
+ assert.True(t, strings.Contains(err.DetailedError, "won't activate for some reason"))
+ })
+}
diff --git a/cmd/commands/user.go b/cmd/commands/user.go
index a8b7341b2..fe4d34a48 100644
--- a/cmd/commands/user.go
+++ b/cmd/commands/user.go
@@ -553,7 +553,7 @@ func migrateAuthToLdapCmdF(command *cobra.Command, args []string) error {
}
fromAuth := args[0]
- matchField := args[1]
+ matchField := args[2]
if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") {
return errors.New("Invalid from_auth argument")
@@ -594,7 +594,7 @@ func migrateAuthToSamlCmdF(command *cobra.Command, args []string) error {
matchesFile := ""
matches := map[string]string{}
if !autoFlag {
- matchesFile = args[1]
+ matchesFile = args[2]
file, e := ioutil.ReadFile(matchesFile)
if e != nil {
diff --git a/i18n/de.json b/i18n/de.json
index 35355919c..2a7952315 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -1370,7 +1370,7 @@
},
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
- "translation": "Unable to upload files. Incorrect number of files specified."
+ "translation": "Konnte Dateien nicht hochladen. Falsche Anzahl von Dateien spezifiziert."
},
{
"id": "api.file.upload_file.large_image.app_error",
@@ -1618,7 +1618,7 @@
},
{
"id": "api.oauth.singup_with_oauth.expired_link.app_error",
- "translation": "Der Registrierungs-Link ist abgelaufen"
+ "translation": "Der Registrierungslink ist abgelaufen"
},
{
"id": "api.oauth.singup_with_oauth.invalid_link.app_error",
@@ -1812,15 +1812,15 @@
},
{
"id": "api.post.send_notifications_and_forget.push_image_only",
- "translation": " Eine oder mehrere Dateien hochgeladen in "
+ "translation": " hat eine oder mehrere Dateien hochgeladen in "
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_dm",
- "translation": " Eine oder mehrere Dateien in einer Direktnachricht hochgeladen"
+ "translation": " hat eine oder mehrere Dateien in einer Direktnachricht hochgeladen"
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
- "translation": " Eine oder mehrere Dateien hochgeladen in "
+ "translation": " hat eine oder mehrere Dateien hochgeladen"
},
{
"id": "api.post.send_notifications_and_forget.push_in",
@@ -2308,11 +2308,11 @@
},
{
"id": "api.team.move_channel.post.error",
- "translation": "Fehler beim Senden des Kanalzwecks"
+ "translation": "Fehler beim Senden der Nachricht zur Verschiebung des Kanals."
},
{
"id": "api.team.move_channel.success",
- "translation": "This channel has been moved to this team from %v."
+ "translation": "Dieser Kanal wurde von %v in dieses Team verschoben."
},
{
"id": "api.team.permanent_delete_team.attempting.warn",
@@ -2720,7 +2720,7 @@
},
{
"id": "api.user.create_user.signup_link_mismatched_invite_id.app_error",
- "translation": "Der Einladungslink scheint nicht gültig zu sein"
+ "translation": "Der Registrierungslink scheint nicht gültig zu sein"
},
{
"id": "api.user.create_user.team_name.app_error",
@@ -3080,7 +3080,7 @@
},
{
"id": "api.webhook.incoming.error",
- "translation": "Could not decode the multipart payload of incoming webhook."
+ "translation": "Konnte die Multipart-Daten des eingehenden Webhooks nicht entschlüsseln."
},
{
"id": "api.webhook.init.debug",
@@ -3656,7 +3656,7 @@
},
{
"id": "app.plugin.disabled.app_error",
- "translation": "Plugins have been disabled. Please check your logs for details."
+ "translation": "Plugins wurden deaktiviert. Bitte prüfen Sie Ihre Logs für Details."
},
{
"id": "app.plugin.extract.app_error",
@@ -4192,15 +4192,15 @@
},
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
- "translation": "Email already used by another SAML user."
+ "translation": "E-Mail-Adresse wird bereits von einem anderen SAML-Benutzer verwendet."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
- "translation": "User not found in the users file."
+ "translation": "Benutzer nicht in der Benutzerdatei gefunden."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
- "translation": "Username already used by another Mattermost user."
+ "translation": "Benutzername wird bereits von einem anderen Mattermost-Benutzer verwendet."
},
{
"id": "ent.saml.attribute.app_error",
@@ -4904,7 +4904,7 @@
},
{
"id": "model.config.is_valid.message_export.export_type.app_error",
- "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
+ "translation": "'ExportFormat' des Nachrichten-Export-Jobs muss 'actiance' oder 'globalrelay' sein."
},
{
"id": "model.config.is_valid.message_export.file_location.app_error",
@@ -4916,7 +4916,7 @@
},
{
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
- "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address"
+ "translation": "Nachrichten-Export-Job GlobalRelayEmailAddress muss eine gültige E-Mail-Adresse sein."
},
{
"id": "model.config.is_valid.password_length.app_error",
@@ -5048,7 +5048,7 @@
},
{
"id": "model.config.is_valid.websocket_url.app_error",
- "translation": "Die WebRTC-Gateway-Websocket-URL muss gesetzt und eine gültige URL sein sowie mit ws:// oder wss:// beginnen."
+ "translation": "Websocket-URL muss eine gültige URL sein sowie mit ws:// oder wss:// beginnen."
},
{
"id": "model.config.is_valid.write_timeout.app_error",
@@ -7136,7 +7136,7 @@
},
{
"id": "utils.mail.sendMail.attachments.write_error",
- "translation": "Failed to write attachment to email"
+ "translation": "Fehler beim Hinzufügen des Mailanhanges"
},
{
"id": "utils.mail.send_mail.close.app_error",
diff --git a/i18n/en.json b/i18n/en.json
index 85a09a139..c3206caff 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -3719,6 +3719,10 @@
"translation": "Error saving plugin state in config"
},
{
+ "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."
+ },
+ {
"id": "app.plugin.deactivate.app_error",
"translation": "Unable to deactivate plugin"
},
diff --git a/i18n/it.json b/i18n/it.json
index 0918ececa..5cb1c8a85 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -3636,7 +3636,7 @@
},
{
"id": "app.notification.subject.direct.full",
- "translation": "[{{.SubjectText}}] Nuovo messaggio diretto da {{.SenderDisplayName}} il {{.Day}}/{{.Month}}/{{.Year}}"
+ "translation": "[{{.SiteName}}] Nuovo messaggio diretto da {{.SenderDisplayName}} il {{.Day}}/{{.Month}}/{{.Year}}"
},
{
"id": "app.notification.subject.notification.full",
diff --git a/i18n/ko.json b/i18n/ko.json
index 79edba95c..13ba19b79 100644
--- a/i18n/ko.json
+++ b/i18n/ko.json
@@ -101,7 +101,7 @@
},
{
"id": "api.admin.test_email.reenter_password",
- "translation": "SMTP 서버, 포트, 사용자 정보가 변경되었습니다. SMTP 비밀번호를 다시 입력해주세요."
+ "translation": "SMTP 서버, 포트 또는 사용자이름이 변경되었습니다. SMTP 비밀번호를 다시 입력해주세요."
},
{
"id": "api.admin.test_email.subject",
@@ -141,7 +141,7 @@
},
{
"id": "api.auth.unable_to_get_user.app_error",
- "translation": "권한을 확인하기 위한 유저 정보를 가져올 수 없습니다."
+ "translation": "권한을 확인하기 위한 사용자 정보를 가져올 수 없습니다."
},
{
"id": "api.brand.init.debug",
@@ -197,11 +197,11 @@
},
{
"id": "api.channel.change_channel_privacy.private_to_public",
- "translation": "This channel has been converted to a Public Channel and can be joined by any team member."
+ "translation": "이 채널은 공개 채널로 전환되어 모든 팀원이 들어올 수 있습니다."
},
{
"id": "api.channel.change_channel_privacy.public_to_private",
- "translation": "This channel has been converted to a Private Channel."
+ "translation": "이 채널은 비공개 채널로 변경되었습니다."
},
{
"id": "api.channel.create_channel.direct_channel.app_error",
@@ -433,7 +433,7 @@
},
{
"id": "api.command.execute_command.not_found.app_error",
- "translation": "Command with a trigger of '{{.Trigger}}' not found. To send a message beginning with \"/\", try adding an empty space at the beginning of the message."
+ "translation": "'{{.Trigger}}' 트리거에 실행되는 커맨드를 찾지 못했습니다. \"/\"로 시작하는 메시지를 보내려면 메시지 시작 부분에 빈 공간을 추가하십시오."
},
{
"id": "api.command.execute_command.save.app_error",
@@ -521,7 +521,7 @@
},
{
"id": "api.command_channel_header.name",
- "translation": "header"
+ "translation": "머리글"
},
{
"id": "api.command_channel_header.permission.app_error",
@@ -541,11 +541,11 @@
},
{
"id": "api.command_channel_purpose.desc",
- "translation": "Edit the channel purpose"
+ "translation": "체널 설명 수정하기"
},
{
"id": "api.command_channel_purpose.direct_group.app_error",
- "translation": "Cannot set purpose for direct message channels. Use /header to set the header instead."
+ "translation": "개인 메시지의 설명은 설정할 수 없습니다. 머리글을 설정하려면 /header 를 사용하세요."
},
{
"id": "api.command_channel_purpose.hint",
@@ -557,11 +557,11 @@
},
{
"id": "api.command_channel_purpose.name",
- "translation": "purpose"
+ "translation": "설명"
},
{
"id": "api.command_channel_purpose.permission.app_error",
- "translation": "당신은 채널 머릿말을 수정할 권한을 가지고 있지 않습니다."
+ "translation": "당신은 채널 설명을 수정할 권한을 가지고 있지 않습니다."
},
{
"id": "api.command_channel_purpose.update_channel.app_error",
@@ -573,11 +573,11 @@
},
{
"id": "api.command_channel_rename.desc",
- "translation": "Rename the channel"
+ "translation": "채널 이름 바꾸기"
},
{
"id": "api.command_channel_rename.direct_group.app_error",
- "translation": "개인 메시지 채널은 나갈 수 없습니다"
+ "translation": "개인 메시지 채널의 이름은 변경 할 수 없습니다."
},
{
"id": "api.command_channel_rename.hint",
@@ -585,23 +585,23 @@
},
{
"id": "api.command_channel_rename.message.app_error",
- "translation": "메시지는 /echo 명령어와 함께 제공되어야 합니다."
+ "translation": "메시지는 /rename 명령어와 함께 제공되어야 합니다."
},
{
"id": "api.command_channel_rename.name",
- "translation": "rename"
+ "translation": "이름변경"
},
{
"id": "api.command_channel_rename.permission.app_error",
- "translation": "당신은 채널 머릿말을 수정할 권한을 가지고 있지 않습니다."
+ "translation": "당신은 채널 이름을 수정할 권한을 가지고 있지 않습니다."
},
{
"id": "api.command_channel_rename.too_long.app_error",
- "translation": "Channel name must be {{.Length}} or fewer characters"
+ "translation": "채널 이름은 {{.Length}} 자 이하여야 합니다."
},
{
"id": "api.command_channel_rename.too_short.app_error",
- "translation": "Channel name must be {{.Length}} or more characters"
+ "translation": "채널 이름은 {{.Length}} 자 이상이여야 합니다."
},
{
"id": "api.command_channel_rename.update_channel.app_error",
@@ -609,11 +609,11 @@
},
{
"id": "api.command_channel_rename.update_channel.success",
- "translation": "채널 머릿말이 성공적으로 업데이트되었습니다."
+ "translation": "채널이름이 성공적으로 업데이트되었습니다."
},
{
"id": "api.command_code.desc",
- "translation": "Display text as a code block"
+ "translation": "텍스트를 코드 블록으로 표시합니다."
},
{
"id": "api.command_code.hint",
@@ -621,11 +621,11 @@
},
{
"id": "api.command_code.message.app_error",
- "translation": "메시지는 /echo 명령어와 함께 제공되어야 합니다."
+ "translation": "메시지는 /code 명령어와 함께 제공되어야 합니다."
},
{
"id": "api.command_code.name",
- "translation": "code"
+ "translation": "코드"
},
{
"id": "api.command_collapse.desc",
@@ -645,7 +645,7 @@
},
{
"id": "api.command_dnd.disabled",
- "translation": "Do Not Disturb is disabled."
+ "translation": "방해 금지 모드가 해제되었습니다."
},
{
"id": "api.command_dnd.error",
@@ -705,7 +705,7 @@
},
{
"id": "api.command_groupmsg.desc",
- "translation": "Sends a Group Message to the specified users"
+ "translation": "지정된 사용자에게 그룹 메시지를 보냅니다."
},
{
"id": "api.command_groupmsg.fail.app_error",
@@ -760,7 +760,7 @@
},
{
"id": "api.command_help.name",
- "translation": "help"
+ "translation": "도움말"
},
{
"id": "api.command_join.desc",
@@ -816,7 +816,7 @@
},
{
"id": "api.command_leave.success",
- "translation": "%v 가 채널을 떠났습니다."
+ "translation": "채널을 떠났습니다."
},
{
"id": "api.command_logout.desc",
@@ -1772,7 +1772,7 @@
},
{
"id": "api.post.make_direct_channel_visible.get_2_members.error",
- "translation": "다이렉트 채널에 2명의 구성원을 가져올 수 없습니다. channel_id={{.ChannelId}}"
+ "translation": "개인 메시지 채널에 2명의 사용자를 가져오는데 실패했습니다. 채널 아이디={{.ChannelId}}"
},
{
"id": "api.post.make_direct_channel_visible.get_members.error",
@@ -3636,7 +3636,7 @@
},
{
"id": "app.notification.subject.direct.full",
- "translation": "{{.SubjectText}} on {{.TeamDisplayName}} at {{.Month}} {{.Day}}, {{.Year}}"
+ "translation": "[{{.SiteName}}] {{.SenderDisplayName}} (으)로부터 {{.Month}} {{.Day}}, {{.Year}} 에 새로운 개인 메시지가 왔습니다."
},
{
"id": "app.notification.subject.notification.full",
@@ -3712,7 +3712,7 @@
},
{
"id": "app.user_access_token.disabled",
- "translation": "Personal access tokens are disabled on this server. Please contact your system administrator for details."
+ "translation": "개인 액세스 토큰이 현재 서버에서 활성화 되어있지 않습니다. 시스템 관리자에게 연락하여 자세한 사항을 확인하시길 바랍니다."
},
{
"id": "app.user_access_token.invalid_or_missing",
diff --git a/i18n/nl.json b/i18n/nl.json
index dda0ebab9..1777b59d1 100644
--- a/i18n/nl.json
+++ b/i18n/nl.json
@@ -3636,7 +3636,7 @@
},
{
"id": "app.notification.subject.direct.full",
- "translation": "{{.SubjectText}} in {{.TeamDisplayName}} van {{.SenderDisplayName}} op {{.Month}} {{.Day}} {{.Year}}"
+ "translation": "[{{.SiteName}}] Nieuw direct bericht van {{.SenderDisplayName}} op {{.Month}} {{.Day}} {{.Year}}"
},
{
"id": "app.notification.subject.notification.full",
diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json
index 44400b8c0..000d87a0c 100644
--- a/i18n/pt-BR.json
+++ b/i18n/pt-BR.json
@@ -1370,7 +1370,7 @@
},
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
- "translation": "Unable to upload files. Incorrect number of files specified."
+ "translation": "Não é possível enviar arquivos. Número incorreto de arquivos especificado."
},
{
"id": "api.file.upload_file.large_image.app_error",
@@ -1820,7 +1820,7 @@
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_no_channel",
- "translation": " enviado um ou mais arquivos em "
+ "translation": " enviado um ou mais arquivos"
},
{
"id": "api.post.send_notifications_and_forget.push_in",
@@ -3080,7 +3080,7 @@
},
{
"id": "api.webhook.incoming.error",
- "translation": "Could not decode the multipart payload of incoming webhook."
+ "translation": "Não foi possível decodificar a carga multiparte do webhook de entrada."
},
{
"id": "api.webhook.init.debug",
@@ -3656,7 +3656,7 @@
},
{
"id": "app.plugin.disabled.app_error",
- "translation": "Plugins have been disabled. Please check your logs for details."
+ "translation": "Os plugins foram desativados. Verifique os seus logs para obter detalhes."
},
{
"id": "app.plugin.extract.app_error",
@@ -4192,15 +4192,15 @@
},
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
- "translation": "Email already used by another SAML user."
+ "translation": "Email já usado por outro usuário SAML."
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
- "translation": "User not found in the users file."
+ "translation": "Usuário não encontrado no arquivo de usuários."
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
- "translation": "Username already used by another Mattermost user."
+ "translation": "Nome de usuário já usado por outro usuário Mattermost."
},
{
"id": "ent.saml.attribute.app_error",
@@ -4904,7 +4904,7 @@
},
{
"id": "model.config.is_valid.message_export.export_type.app_error",
- "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
+ "translation": "Na tarefa de exportação de mensagens o ExportFormat deve ser 'actiance' ou 'globalrelay'"
},
{
"id": "model.config.is_valid.message_export.file_location.app_error",
@@ -4916,7 +4916,7 @@
},
{
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
- "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address"
+ "translation": "Na tarefa de exportação de mensagens o GlobalRelayEmailAddress deve ser um endereço de email válido"
},
{
"id": "model.config.is_valid.password_length.app_error",
@@ -5048,7 +5048,7 @@
},
{
"id": "model.config.is_valid.websocket_url.app_error",
- "translation": "A URL Websocket do WebRTC Gateway deve ser uma URL válida e começar com ws:// ou wss://."
+ "translation": "A URL Websocket deve ser uma URL válida e começar com ws:// ou wss://."
},
{
"id": "model.config.is_valid.write_timeout.app_error",
@@ -7136,7 +7136,7 @@
},
{
"id": "utils.mail.sendMail.attachments.write_error",
- "translation": "Failed to write attachment to email"
+ "translation": "Falha ao escrever o anexo para o e-mail"
},
{
"id": "utils.mail.send_mail.close.app_error",
diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json
index f476b48ed..43d3fde87 100644
--- a/i18n/zh-CN.json
+++ b/i18n/zh-CN.json
@@ -2308,11 +2308,11 @@
},
{
"id": "api.team.move_channel.post.error",
- "translation": "发送频道作用消息失败"
+ "translation": "发送频道移动消息失败。"
},
{
"id": "api.team.move_channel.success",
- "translation": "This channel has been moved to this team from %v."
+ "translation": "此频道已从 %v 移至此团队。"
},
{
"id": "api.team.permanent_delete_team.attempting.warn",
@@ -3080,7 +3080,7 @@
},
{
"id": "api.webhook.incoming.error",
- "translation": "Could not decode the multipart payload of incoming webhook."
+ "translation": "无法解码传入的 webhook 混合数据。"
},
{
"id": "api.webhook.init.debug",
@@ -4904,7 +4904,7 @@
},
{
"id": "model.config.is_valid.message_export.export_type.app_error",
- "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
+ "translation": "消息导出任务 ExportFormat 必须为 'actiance' 或 'globalrelay'"
},
{
"id": "model.config.is_valid.message_export.file_location.app_error",
@@ -4916,7 +4916,7 @@
},
{
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
- "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address"
+ "translation": "消息导出任务 GlobalRelayEmailAddress 必须为有效的电子邮箱地址"
},
{
"id": "model.config.is_valid.password_length.app_error",
diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json
index b9edc5edf..0700a15a5 100644
--- a/i18n/zh-TW.json
+++ b/i18n/zh-TW.json
@@ -1370,7 +1370,7 @@
},
{
"id": "api.file.upload_file.incorrect_number_of_files.app_error",
- "translation": "Unable to upload files. Incorrect number of files specified."
+ "translation": "無法上傳檔案。檔案數量不對。"
},
{
"id": "api.file.upload_file.large_image.app_error",
@@ -1812,7 +1812,7 @@
},
{
"id": "api.post.send_notifications_and_forget.push_image_only",
- "translation": "已上傳一個或更多檔案"
+ "translation": "已上傳一個或更多檔案至"
},
{
"id": "api.post.send_notifications_and_forget.push_image_only_dm",
@@ -2308,11 +2308,11 @@
},
{
"id": "api.team.move_channel.post.error",
- "translation": "發送頻道用途訊息失敗"
+ "translation": "發送頻道移動訊息失敗"
},
{
"id": "api.team.move_channel.success",
- "translation": "This channel has been moved to this team from %v."
+ "translation": "此頻道已從 %v 移動至此團隊。"
},
{
"id": "api.team.permanent_delete_team.attempting.warn",
@@ -3080,7 +3080,7 @@
},
{
"id": "api.webhook.incoming.error",
- "translation": "Could not decode the multipart payload of incoming webhook."
+ "translation": "無法解碼 Incoming Webhook 的 multipart 內容。"
},
{
"id": "api.webhook.init.debug",
@@ -3656,7 +3656,7 @@
},
{
"id": "app.plugin.disabled.app_error",
- "translation": "Plugins have been disabled. Please check your logs for details."
+ "translation": "模組已被停用。詳情請看系統紀錄。"
},
{
"id": "app.plugin.extract.app_error",
@@ -4192,15 +4192,15 @@
},
{
"id": "ent.migration.migratetosaml.email_already_used_by_other_user",
- "translation": "Email already used by another SAML user."
+ "translation": "電子郵件已被其他 SAML 使用者使用。"
},
{
"id": "ent.migration.migratetosaml.user_not_found_in_users_mapping_file",
- "translation": "User not found in the users file."
+ "translation": "在使用者檔案中找不到使用者。"
},
{
"id": "ent.migration.migratetosaml.username_already_used_by_other_user",
- "translation": "Username already used by another Mattermost user."
+ "translation": "使用者名稱已被其他 Mattermost 使用者使用。 "
},
{
"id": "ent.saml.attribute.app_error",
@@ -4904,7 +4904,7 @@
},
{
"id": "model.config.is_valid.message_export.export_type.app_error",
- "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
+ "translation": "訊息匯出工作的 ExportFormat 必須為 'actiance' 或 'globalrelay'"
},
{
"id": "model.config.is_valid.message_export.file_location.app_error",
@@ -4916,7 +4916,7 @@
},
{
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
- "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address"
+ "translation": "訊息匯出工作的 GlobalRelayEmailAddress 必須為 'actiance' 或 'globalrelay'"
},
{
"id": "model.config.is_valid.password_length.app_error",
@@ -5048,7 +5048,7 @@
},
{
"id": "model.config.is_valid.websocket_url.app_error",
- "translation": "WebRTC 閘道 Websocket 網址必須是以 ws:// 或 wss:// 起始的有效網址。"
+ "translation": "Websocket 網址必須是以 ws:// 或 wss:// 起始的有效網址。"
},
{
"id": "model.config.is_valid.write_timeout.app_error",
@@ -7136,7 +7136,7 @@
},
{
"id": "utils.mail.sendMail.attachments.write_error",
- "translation": "Failed to write attachment to email"
+ "translation": "無法寫入附加檔案到電子郵件"
},
{
"id": "utils.mail.send_mail.close.app_error",
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 412914738..000000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-site_name: Mattermost Documentation
-site_url: http://docs.mattermost.org
-repo_url: https://github.com/mattermost/platform
-repo_name: GitHub
-site_favicon: favicon.ico
-copyright: "Copyright (c) 2015-2017 Mattermost, Inc. All Rights Reserved."
-strict: true
-docs_dir: doc
-site_dir: documentation-html
-use_directory_urls: false
-theme: mkdocs
diff --git a/model/client4.go b/model/client4.go
index f12e4712b..693008734 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -2102,8 +2102,8 @@ func (c *Client4) GetPing() (string, *Response) {
}
// TestEmail will attempt to connect to the configured SMTP server.
-func (c *Client4) TestEmail() (bool, *Response) {
- if r, err := c.DoApiPost(c.GetTestEmailRoute(), ""); err != nil {
+func (c *Client4) TestEmail(config *Config) (bool, *Response) {
+ if r, err := c.DoApiPost(c.GetTestEmailRoute(), config.ToJson()); err != nil {
return false, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
diff --git a/plugin/rpcplugin/sandbox/sandbox_linux.go b/plugin/rpcplugin/sandbox/sandbox_linux.go
index 4ade00cf2..beb00995d 100644
--- a/plugin/rpcplugin/sandbox/sandbox_linux.go
+++ b/plugin/rpcplugin/sandbox/sandbox_linux.go
@@ -23,7 +23,7 @@ import (
)
func init() {
- if len(os.Args) < 3 || os.Args[0] != "sandbox.runProcess" {
+ if len(os.Args) < 4 || os.Args[0] != "sandbox.runProcess" {
return
}
@@ -32,7 +32,7 @@ func init() {
fmt.Println(err.Error())
os.Exit(1)
}
- if err := runProcess(&config, os.Args[2]); err != nil {
+ if err := runProcess(&config, os.Args[2], os.Args[3]); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
if status, ok := eerr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
@@ -98,13 +98,7 @@ func systemMountPoints() (points []*MountPoint) {
return
}
-func runProcess(config *Configuration, path string) error {
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return err
- }
- defer os.RemoveAll(root)
-
+func runProcess(config *Configuration, path, root string) error {
if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
return errors.Wrapf(err, "unable to make root private")
}
@@ -330,9 +324,10 @@ func runExecutable(path string) error {
type process struct {
command *exec.Cmd
+ root string
}
-func newProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) {
+func newProcess(ctx context.Context, config *Configuration, path string) (pOut rpcplugin.Process, rwcOut io.ReadWriteCloser, errOut error) {
configJSON, err := json.Marshal(config)
if err != nil {
return nil, nil, err
@@ -345,8 +340,18 @@ func newProcess(ctx context.Context, config *Configuration, path string) (rpcplu
defer childFiles[0].Close()
defer childFiles[1].Close()
+ root, err := ioutil.TempDir("", "")
+ if err != nil {
+ return nil, nil, err
+ }
+ defer func() {
+ if errOut != nil {
+ os.RemoveAll(root)
+ }
+ }()
+
cmd := exec.CommandContext(ctx, "/proc/self/exe")
- cmd.Args = []string{"sandbox.runProcess", string(configJSON), path}
+ cmd.Args = []string{"sandbox.runProcess", string(configJSON), path, root}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = childFiles
@@ -378,19 +383,21 @@ func newProcess(ctx context.Context, config *Configuration, path string) (rpcplu
return &process{
command: cmd,
+ root: root,
}, ipc, nil
}
func (p *process) Wait() error {
+ defer os.RemoveAll(p.root)
return p.command.Wait()
}
func init() {
- if len(os.Args) < 1 || os.Args[0] != "sandbox.checkSupportInNamespace" {
+ if len(os.Args) < 2 || os.Args[0] != "sandbox.checkSupportInNamespace" {
return
}
- if err := checkSupportInNamespace(); err != nil {
+ if err := checkSupportInNamespace(os.Args[1]); err != nil {
fmt.Fprintf(os.Stderr, "%v", err.Error())
os.Exit(1)
}
@@ -398,13 +405,7 @@ func init() {
os.Exit(0)
}
-func checkSupportInNamespace() error {
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return err
- }
- defer os.RemoveAll(root)
-
+func checkSupportInNamespace(root string) error {
if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
return errors.Wrapf(err, "unable to make root private")
}
@@ -444,8 +445,14 @@ func checkSupport() error {
stderr := &bytes.Buffer{}
+ root, err := ioutil.TempDir("", "")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(root)
+
cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{"sandbox.checkSupportInNamespace"}
+ cmd.Args = []string{"sandbox.checkSupportInNamespace", root}
cmd.Stderr = stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go
index 92ee28ffa..3ff9a3e1b 100644
--- a/store/sqlstore/post_store.go
+++ b/store/sqlstore/post_store.go
@@ -687,31 +687,70 @@ func (s SqlPostStore) getRootPosts(channelId string, offset int, limit int) stor
func (s SqlPostStore) getParentsPosts(channelId string, offset int, limit int) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var posts []*model.Post
- _, err := s.GetReplica().Select(&posts,
- `SELECT
- q2.*
+ _, err := s.GetReplica().Select(&posts, `
+ SELECT
+ *
FROM
- Posts q2
- INNER JOIN
- (SELECT DISTINCT
- q3.RootId
- FROM
- (SELECT
- RootId
- FROM
- Posts
- WHERE
- ChannelId = :ChannelId1
- AND DeleteAt = 0
- ORDER BY CreateAt DESC
- LIMIT :Limit OFFSET :Offset) q3
- WHERE q3.RootId != '') q1
- ON q1.RootId = q2.Id OR q1.RootId = q2.RootId
+ Posts
WHERE
- ChannelId = :ChannelId2
- AND DeleteAt = 0
- ORDER BY CreateAt`,
- map[string]interface{}{"ChannelId1": channelId, "Offset": offset, "Limit": limit, "ChannelId2": channelId})
+ Id IN (SELECT * FROM (
+ -- The root post of any replies in the window
+ (SELECT * FROM (
+ SELECT
+ CASE RootId
+ WHEN '' THEN NULL
+ ELSE RootId
+ END
+ FROM
+ Posts
+ WHERE
+ ChannelId = :ChannelId1
+ AND DeleteAt = 0
+ ORDER BY
+ CreateAt DESC
+ LIMIT :Limit1 OFFSET :Offset1
+ ) x )
+
+ UNION
+
+ -- The reply posts to all threads intersecting with the window, including replies
+ -- to root posts in the window itself.
+ (
+ SELECT
+ Id
+ FROM
+ Posts
+ WHERE RootId IN (SELECT * FROM (
+ SELECT
+ CASE RootId
+ -- If there is no RootId, return the post id itself to be considered
+ -- as a root post.
+ WHEN '' THEN Id
+ -- If there is a RootId, this post isn't a root post and return its
+ -- root to be considered as a root post.
+ ELSE RootId
+ END
+ FROM
+ Posts
+ WHERE
+ ChannelId = :ChannelId2
+ AND DeleteAt = 0
+ ORDER BY
+ CreateAt DESC
+ LIMIT :Limit2 OFFSET :Offset2
+ ) x )
+ )
+ ) x )
+ AND
+ DeleteAt = 0
+ `, map[string]interface{}{
+ "ChannelId1": channelId,
+ "ChannelId2": channelId,
+ "Offset1": offset,
+ "Offset2": offset,
+ "Limit1": limit,
+ "Limit2": limit,
+ })
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetLinearPosts", "store.sql_post.get_parents_posts.app_error", nil, "channelId="+channelId+" err="+err.Error(), http.StatusInternalServerError)
} else {
diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go
index e663d5a41..91fc40213 100644
--- a/store/storetest/post_store.go
+++ b/store/storetest/post_store.go
@@ -5,6 +5,7 @@ package storetest
import (
"fmt"
+ "sort"
"strings"
"testing"
"time"
@@ -491,125 +492,182 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) {
}
func testPostStoreGetPostsWithDetails(t *testing.T, ss store.Store) {
- o1 := &model.Post{}
- o1.ChannelId = model.NewId()
- o1.UserId = model.NewId()
- o1.Message = "zz" + model.NewId() + "b"
- o1 = (<-ss.Post().Save(o1)).Data.(*model.Post)
- time.Sleep(2 * time.Millisecond)
+ assertPosts := func(expected []*model.Post, actual map[string]*model.Post) {
+ expectedIds := make([]string, 0, len(expected))
+ expectedMessages := make([]string, 0, len(expected))
+ for _, post := range expected {
+ expectedIds = append(expectedIds, post.Id)
+ expectedMessages = append(expectedMessages, post.Message)
+ }
+ sort.Strings(expectedIds)
+ sort.Strings(expectedMessages)
+
+ actualIds := make([]string, 0, len(actual))
+ actualMessages := make([]string, 0, len(actual))
+ for _, post := range actual {
+ actualIds = append(actualIds, post.Id)
+ actualMessages = append(actualMessages, post.Message)
+ }
+ sort.Strings(actualIds)
+ sort.Strings(actualMessages)
- o2 := &model.Post{}
- o2.ChannelId = o1.ChannelId
- o2.UserId = model.NewId()
- o2.Message = "zz" + model.NewId() + "b"
- o2.ParentId = o1.Id
- o2.RootId = o1.Id
- o2 = (<-ss.Post().Save(o2)).Data.(*model.Post)
- time.Sleep(2 * time.Millisecond)
+ if assert.Equal(t, expectedIds, actualIds) {
+ assert.Equal(t, expectedMessages, actualMessages)
+ }
+ }
- o2a := &model.Post{}
- o2a.ChannelId = o1.ChannelId
- o2a.UserId = model.NewId()
- o2a.Message = "zz" + model.NewId() + "b"
- o2a.ParentId = o1.Id
- o2a.RootId = o1.Id
- o2a = (<-ss.Post().Save(o2a)).Data.(*model.Post)
+ root1 := &model.Post{}
+ root1.ChannelId = model.NewId()
+ root1.UserId = model.NewId()
+ root1.Message = "zz" + model.NewId() + "b"
+ root1 = (<-ss.Post().Save(root1)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
- o3 := &model.Post{}
- o3.ChannelId = o1.ChannelId
- o3.UserId = model.NewId()
- o3.Message = "zz" + model.NewId() + "b"
- o3.ParentId = o1.Id
- o3.RootId = o1.Id
- o3 = (<-ss.Post().Save(o3)).Data.(*model.Post)
+ root1Reply1 := &model.Post{}
+ root1Reply1.ChannelId = root1.ChannelId
+ root1Reply1.UserId = model.NewId()
+ root1Reply1.Message = "zz" + model.NewId() + "b"
+ root1Reply1.ParentId = root1.Id
+ root1Reply1.RootId = root1.Id
+ root1Reply1 = (<-ss.Post().Save(root1Reply1)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
- o4 := &model.Post{}
- o4.ChannelId = o1.ChannelId
- o4.UserId = model.NewId()
- o4.Message = "zz" + model.NewId() + "b"
- o4 = (<-ss.Post().Save(o4)).Data.(*model.Post)
+ root1Reply2 := &model.Post{}
+ root1Reply2.ChannelId = root1.ChannelId
+ root1Reply2.UserId = model.NewId()
+ root1Reply2.Message = "zz" + model.NewId() + "b"
+ root1Reply2.ParentId = root1.Id
+ root1Reply2.RootId = root1.Id
+ root1Reply2 = (<-ss.Post().Save(root1Reply2)).Data.(*model.Post)
time.Sleep(2 * time.Millisecond)
- o5 := &model.Post{}
- o5.ChannelId = o1.ChannelId
- o5.UserId = model.NewId()
- o5.Message = "zz" + model.NewId() + "b"
- o5.ParentId = o4.Id
- o5.RootId = o4.Id
- o5 = (<-ss.Post().Save(o5)).Data.(*model.Post)
-
- r1 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 4, false)).Data.(*model.PostList)
-
- if r1.Order[0] != o5.Id {
- t.Fatal("invalid order")
- }
-
- if r1.Order[1] != o4.Id {
- t.Fatal("invalid order")
- }
-
- if r1.Order[2] != o3.Id {
- t.Fatal("invalid order")
- }
-
- if r1.Order[3] != o2a.Id {
- t.Fatal("invalid order")
- }
-
- if len(r1.Posts) != 6 { //the last 4, + o1 (o2a and o3's parent) + o2 (in same thread as o2a and o3)
- t.Fatal("wrong size")
- }
-
- if r1.Posts[o1.Id].Message != o1.Message {
- t.Fatal("Missing parent")
- }
+ root1Reply3 := &model.Post{}
+ root1Reply3.ChannelId = root1.ChannelId
+ root1Reply3.UserId = model.NewId()
+ root1Reply3.Message = "zz" + model.NewId() + "b"
+ root1Reply3.ParentId = root1.Id
+ root1Reply3.RootId = root1.Id
+ root1Reply3 = (<-ss.Post().Save(root1Reply3)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
- r2 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 4, true)).Data.(*model.PostList)
+ root2 := &model.Post{}
+ root2.ChannelId = root1.ChannelId
+ root2.UserId = model.NewId()
+ root2.Message = "zz" + model.NewId() + "b"
+ root2 = (<-ss.Post().Save(root2)).Data.(*model.Post)
+ time.Sleep(2 * time.Millisecond)
- if r2.Order[0] != o5.Id {
- t.Fatal("invalid order")
- }
+ root2Reply1 := &model.Post{}
+ root2Reply1.ChannelId = root1.ChannelId
+ root2Reply1.UserId = model.NewId()
+ root2Reply1.Message = "zz" + model.NewId() + "b"
+ root2Reply1.ParentId = root2.Id
+ root2Reply1.RootId = root2.Id
+ root2Reply1 = (<-ss.Post().Save(root2Reply1)).Data.(*model.Post)
- if r2.Order[1] != o4.Id {
- t.Fatal("invalid order")
- }
+ r1 := (<-ss.Post().GetPosts(root1.ChannelId, 0, 4, false)).Data.(*model.PostList)
- if r2.Order[2] != o3.Id {
- t.Fatal("invalid order")
+ expectedOrder := []string{
+ root2Reply1.Id,
+ root2.Id,
+ root1Reply3.Id,
+ root1Reply2.Id,
}
- if r2.Order[3] != o2a.Id {
- t.Fatal("invalid order")
+ expectedPosts := []*model.Post{
+ root1,
+ root1Reply1,
+ root1Reply2,
+ root1Reply3,
+ root2,
+ root2Reply1,
}
- if len(r2.Posts) != 6 { //the last 4, + o1 (o2a and o3's parent) + o2 (in same thread as o2a and o3)
- t.Fatal("wrong size")
- }
+ assert.Equal(t, expectedOrder, r1.Order)
+ assertPosts(expectedPosts, r1.Posts)
- if r2.Posts[o1.Id].Message != o1.Message {
- t.Fatal("Missing parent")
- }
+ r2 := (<-ss.Post().GetPosts(root1.ChannelId, 0, 4, true)).Data.(*model.PostList)
+ assert.Equal(t, expectedOrder, r2.Order)
+ assertPosts(expectedPosts, r2.Posts)
// Run once to fill cache
- <-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)
+ <-ss.Post().GetPosts(root1.ChannelId, 0, 30, true)
+ expectedOrder = []string{
+ root2Reply1.Id,
+ root2.Id,
+ root1Reply3.Id,
+ root1Reply2.Id,
+ root1Reply1.Id,
+ root1.Id,
+ }
- o6 := &model.Post{}
- o6.ChannelId = o1.ChannelId
- o6.UserId = model.NewId()
- o6.Message = "zz" + model.NewId() + "b"
- o6 = (<-ss.Post().Save(o6)).Data.(*model.Post)
+ root3 := &model.Post{}
+ root3.ChannelId = root1.ChannelId
+ root3.UserId = model.NewId()
+ root3.Message = "zz" + model.NewId() + "b"
+ root3 = (<-ss.Post().Save(root3)).Data.(*model.Post)
- // Should only be 6 since we hit the cache
- r3 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList)
- assert.Equal(t, 6, len(r3.Order))
+ // Response should be the same despite the new post since we hit the cache
+ r3 := (<-ss.Post().GetPosts(root1.ChannelId, 0, 30, true)).Data.(*model.PostList)
+ assert.Equal(t, expectedOrder, r3.Order)
+ assertPosts(expectedPosts, r3.Posts)
- ss.Post().InvalidateLastPostTimeCache(o1.ChannelId)
+ ss.Post().InvalidateLastPostTimeCache(root1.ChannelId)
// Cache was invalidated, we should get all the posts
- r4 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList)
- assert.Equal(t, 7, len(r4.Order))
+ r4 := (<-ss.Post().GetPosts(root1.ChannelId, 0, 30, true)).Data.(*model.PostList)
+ expectedOrder = []string{
+ root3.Id,
+ root2Reply1.Id,
+ root2.Id,
+ root1Reply3.Id,
+ root1Reply2.Id,
+ root1Reply1.Id,
+ root1.Id,
+ }
+ expectedPosts = []*model.Post{
+ root1,
+ root1Reply1,
+ root1Reply2,
+ root1Reply3,
+ root2,
+ root2Reply1,
+ root3,
+ }
+
+ assert.Equal(t, expectedOrder, r4.Order)
+ assertPosts(expectedPosts, r4.Posts)
+
+ // Replies past the window should be included if the root post itself is in the window
+ root3Reply1 := &model.Post{}
+ root3Reply1.ChannelId = root1.ChannelId
+ root3Reply1.UserId = model.NewId()
+ root3Reply1.Message = "zz" + model.NewId() + "b"
+ root3Reply1.ParentId = root3.Id
+ root3Reply1.RootId = root3.Id
+ root3Reply1 = (<-ss.Post().Save(root3Reply1)).Data.(*model.Post)
+
+ r5 := (<-ss.Post().GetPosts(root1.ChannelId, 1, 5, false)).Data.(*model.PostList)
+ expectedOrder = []string{
+ root3.Id,
+ root2Reply1.Id,
+ root2.Id,
+ root1Reply3.Id,
+ root1Reply2.Id,
+ }
+ expectedPosts = []*model.Post{
+ root1,
+ root1Reply1,
+ root1Reply2,
+ root1Reply3,
+ root2,
+ root2Reply1,
+ root3,
+ root3Reply1,
+ }
+
+ assert.Equal(t, expectedOrder, r5.Order)
+ assertPosts(expectedPosts, r5.Posts)
}
func testPostStoreGetPostsBeforeAfter(t *testing.T, ss store.Store) {
diff --git a/utils/api.go b/utils/api.go
index 0f2640829..b5e490eb7 100644
--- a/utils/api.go
+++ b/utils/api.go
@@ -59,6 +59,7 @@ func RenderWebError(w http.ResponseWriter, r *http.Request, status int, params u
return
}
+ w.Header().Set("Content-Type", "text/html")
w.WriteHeader(status)
fmt.Fprintln(w, `<!DOCTYPE html><html><head></head>`)
fmt.Fprintln(w, `<body onload="window.location = '`+template.HTMLEscapeString(template.JSEscapeString(destination))+`'">`)
diff --git a/utils/config.go b/utils/config.go
index 8e9bafc6e..8befef94d 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -449,6 +449,9 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != ""
props["HasImageProxy"] = strconv.FormatBool(hasImageProxy)
+ props["EnableThemeSelection"] = "true"
+ props["AllowCustomThemes"] = "true"
+
if license != nil {
props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly)
props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer)
diff --git a/utils/file_backend_s3.go b/utils/file_backend_s3.go
index b0601bc8a..75282897f 100644
--- a/utils/file_backend_s3.go
+++ b/utils/file_backend_s3.go
@@ -253,12 +253,9 @@ func CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest)
}
+ // if S3 endpoint is not set call the set defaults to set that
if len(settings.AmazonS3Endpoint) == 0 {
- return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_endpoint", nil, "", http.StatusBadRequest)
- }
-
- if len(settings.AmazonS3Region) == 0 {
- return model.NewAppError("S3File", "api.admin.test_s3.missing_s3_region", nil, "", http.StatusBadRequest)
+ settings.SetDefaults()
}
return nil
diff --git a/utils/file_backend_s3_test.go b/utils/file_backend_s3_test.go
index ff42a4d19..a8834f226 100644
--- a/utils/file_backend_s3_test.go
+++ b/utils/file_backend_s3_test.go
@@ -19,14 +19,14 @@ func TestCheckMandatoryS3Fields(t *testing.T) {
cfg.AmazonS3Bucket = "test-mm"
err = CheckMandatoryS3Fields(&cfg)
- if err == nil || err.Message != "api.admin.test_s3.missing_s3_endpoint" {
- t.Fatal("should've failed with missing s3 endpoint")
+ if err != nil {
+ t.Fatal("should've not failed")
}
- cfg.AmazonS3Endpoint = "s3.newendpoint.com"
+ cfg.AmazonS3Endpoint = ""
err = CheckMandatoryS3Fields(&cfg)
- if err == nil || err.Message != "api.admin.test_s3.missing_s3_region" {
- t.Fatal("should've failed with missing s3 region")
+ if err != nil || cfg.AmazonS3Endpoint != "s3.amazonaws.com" {
+ t.Fatal("should've not failed because it should set the endpoint to the default")
}
}
diff --git a/utils/mail.go b/utils/mail.go
index 3b9f4bd9d..c59406a18 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -26,16 +26,27 @@ func encodeRFC2047Word(s string) string {
return mime.BEncoding.Encode("utf-8", s)
}
+type SmtpConnectionInfo struct {
+ SmtpUsername string
+ SmtpPassword string
+ SmtpServer string
+ SmtpPort string
+ SkipCertVerification bool
+ ConnectionSecurity string
+ Auth bool
+}
+
type authChooser struct {
smtp.Auth
- Config *model.Config
+ connectionInfo *SmtpConnectionInfo
}
func (a *authChooser) Start(server *smtp.ServerInfo) (string, []byte, error) {
- a.Auth = LoginAuth(a.Config.EmailSettings.SMTPUsername, a.Config.EmailSettings.SMTPPassword, a.Config.EmailSettings.SMTPServer+":"+a.Config.EmailSettings.SMTPPort)
+ smtpAddress := a.connectionInfo.SmtpServer + ":" + a.connectionInfo.SmtpPort
+ a.Auth = LoginAuth(a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, smtpAddress)
for _, method := range server.Auth {
if method == "PLAIN" {
- a.Auth = smtp.PlainAuth("", a.Config.EmailSettings.SMTPUsername, a.Config.EmailSettings.SMTPPassword, a.Config.EmailSettings.SMTPServer+":"+a.Config.EmailSettings.SMTPPort)
+ a.Auth = smtp.PlainAuth("", a.connectionInfo.SmtpUsername, a.connectionInfo.SmtpPassword, a.connectionInfo.SmtpServer+":"+a.connectionInfo.SmtpPort)
break
}
}
@@ -76,22 +87,23 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
return nil, nil
}
-func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
+func ConnectToSMTPServerAdvanced(connectionInfo *SmtpConnectionInfo) (net.Conn, *model.AppError) {
var conn net.Conn
var err error
- if config.EmailSettings.ConnectionSecurity == model.CONN_SECURITY_TLS {
+ smtpAddress := connectionInfo.SmtpServer + ":" + connectionInfo.SmtpPort
+ if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_TLS {
tlsconfig := &tls.Config{
- InsecureSkipVerify: *config.EmailSettings.SkipServerCertificateVerification,
- ServerName: config.EmailSettings.SMTPServer,
+ InsecureSkipVerify: connectionInfo.SkipCertVerification,
+ ServerName: connectionInfo.SmtpServer,
}
- conn, err = tls.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort, tlsconfig)
+ conn, err = tls.Dial("tcp", smtpAddress, tlsconfig)
if err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError)
}
} else {
- conn, err = net.Dial("tcp", config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort)
+ conn, err = net.Dial("tcp", smtpAddress)
if err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -100,14 +112,24 @@ func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
return conn, nil
}
-func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) {
- c, err := smtp.NewClient(conn, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort)
+func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) {
+ return ConnectToSMTPServerAdvanced(
+ &SmtpConnectionInfo{
+ ConnectionSecurity: config.EmailSettings.ConnectionSecurity,
+ SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification,
+ SmtpServer: config.EmailSettings.SMTPServer,
+ SmtpPort: config.EmailSettings.SMTPPort,
+ },
+ )
+}
+
+func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) {
+ c, err := smtp.NewClient(conn, connectionInfo.SmtpServer+":"+connectionInfo.SmtpPort)
if err != nil {
l4g.Error(T("utils.mail.new_client.open.error"), err)
return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError)
}
- hostname := GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL)
if hostname != "" {
err := c.Hello(hostname)
if err != nil {
@@ -116,35 +138,51 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap
}
}
- if config.EmailSettings.ConnectionSecurity == model.CONN_SECURITY_STARTTLS {
+ if connectionInfo.ConnectionSecurity == model.CONN_SECURITY_STARTTLS {
tlsconfig := &tls.Config{
- InsecureSkipVerify: *config.EmailSettings.SkipServerCertificateVerification,
- ServerName: config.EmailSettings.SMTPServer,
+ InsecureSkipVerify: connectionInfo.SkipCertVerification,
+ ServerName: connectionInfo.SmtpServer,
}
c.StartTLS(tlsconfig)
}
- if *config.EmailSettings.EnableSMTPAuth {
- if err = c.Auth(&authChooser{Config: config}); err != nil {
+ if connectionInfo.Auth {
+ if err = c.Auth(&authChooser{connectionInfo: connectionInfo}); err != nil {
return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError)
}
}
return c, nil
}
+func NewSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) {
+ return NewSMTPClientAdvanced(
+ conn,
+ GetHostnameFromSiteURL(*config.ServiceSettings.SiteURL),
+ &SmtpConnectionInfo{
+ ConnectionSecurity: config.EmailSettings.ConnectionSecurity,
+ SkipCertVerification: *config.EmailSettings.SkipServerCertificateVerification,
+ SmtpServer: config.EmailSettings.SMTPServer,
+ SmtpPort: config.EmailSettings.SMTPPort,
+ Auth: *config.EmailSettings.EnableSMTPAuth,
+ SmtpUsername: config.EmailSettings.SMTPUsername,
+ SmtpPassword: config.EmailSettings.SMTPPassword,
+ },
+ )
+}
+
func TestConnection(config *model.Config) {
if !config.EmailSettings.SendEmailNotifications {
return
}
- conn, err1 := connectToSMTPServer(config)
+ conn, err1 := ConnectToSMTPServer(config)
if err1 != nil {
l4g.Error(T("utils.mail.test.configured.error"), T(err1.Message), err1.DetailedError)
return
}
defer conn.Close()
- c, err2 := newSMTPClient(conn, config)
+ c, err2 := NewSMTPClient(conn, config)
if err2 != nil {
l4g.Error(T("utils.mail.test.configured.error"), T(err2.Message), err2.DetailedError)
return
@@ -155,19 +193,38 @@ func TestConnection(config *model.Config) {
func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
- return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures)
+
+ return SendMailUsingConfigAdvanced(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures)
}
// allows for sending an email with attachments and differing MIME/SMTP recipients
func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
- return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config, enableComplianceFeatures)
-}
-
-func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil
}
+ conn, err := ConnectToSMTPServer(config)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ c, err := NewSMTPClient(conn, config)
+ if err != nil {
+ return err
+ }
+ defer c.Quit()
+ defer c.Close()
+
+ fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures)
+ if err != nil {
+ return err
+ }
+
+ return SendMail(c, mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, fileBackend)
+}
+
+func SendMail(c *smtp.Client, mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend FileBackend) *model.AppError {
l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject)
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
@@ -197,11 +254,6 @@ func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string
m.AddAlternative("text/html", htmlMessage)
if attachments != nil {
- fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures)
- if err != nil {
- return err
- }
-
for _, fileInfo := range attachments {
bytes, err := fileBackend.ReadFile(fileInfo.Path)
if err != nil {
@@ -217,19 +269,6 @@ func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string
}
}
- conn, err1 := connectToSMTPServer(config)
- if err1 != nil {
- return err1
- }
- defer conn.Close()
-
- c, err2 := newSMTPClient(conn, config)
- if err2 != nil {
- return err2
- }
- defer c.Quit()
- defer c.Close()
-
if err := c.Mail(from.Address); err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError)
}
diff --git a/utils/mail_test.go b/utils/mail_test.go
index 31a4f8996..50cf09dac 100644
--- a/utils/mail_test.go
+++ b/utils/mail_test.go
@@ -4,24 +4,27 @@
package utils
import (
+ "fmt"
"strings"
"testing"
+ "net/mail"
"net/smtp"
"github.com/mattermost/mattermost-server/model"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
-func TestMailConnection(t *testing.T) {
+func TestMailConnectionFromConfig(t *testing.T) {
cfg, _, err := LoadConfig("config.json")
require.Nil(t, err)
- if conn, err := connectToSMTPServer(cfg); err != nil {
+ if conn, err := ConnectToSMTPServer(cfg); err != nil {
t.Log(err)
t.Fatal("Should connect to the STMP Server")
} else {
- if _, err1 := newSMTPClient(conn, cfg); err1 != nil {
+ if _, err1 := NewSMTPClient(conn, cfg); err1 != nil {
t.Log(err)
t.Fatal("Should get new smtp client")
}
@@ -30,7 +33,53 @@ func TestMailConnection(t *testing.T) {
cfg.EmailSettings.SMTPServer = "wrongServer"
cfg.EmailSettings.SMTPPort = "553"
- if _, err := connectToSMTPServer(cfg); err == nil {
+ if _, err := ConnectToSMTPServer(cfg); err == nil {
+ t.Log(err)
+ t.Fatal("Should not to the STMP Server")
+ }
+}
+
+func TestMailConnectionAdvanced(t *testing.T) {
+ cfg, _, err := LoadConfig("config.json")
+ require.Nil(t, err)
+
+ if conn, err := ConnectToSMTPServerAdvanced(
+ &SmtpConnectionInfo{
+ ConnectionSecurity: cfg.EmailSettings.ConnectionSecurity,
+ SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
+ SmtpServer: cfg.EmailSettings.SMTPServer,
+ SmtpPort: cfg.EmailSettings.SMTPPort,
+ },
+ ); err != nil {
+ t.Log(err)
+ t.Fatal("Should connect to the STMP Server")
+ } else {
+ if _, err1 := NewSMTPClientAdvanced(
+ conn,
+ GetHostnameFromSiteURL(*cfg.ServiceSettings.SiteURL),
+ &SmtpConnectionInfo{
+ ConnectionSecurity: cfg.EmailSettings.ConnectionSecurity,
+ SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
+ SmtpServer: cfg.EmailSettings.SMTPServer,
+ SmtpPort: cfg.EmailSettings.SMTPPort,
+ Auth: *cfg.EmailSettings.EnableSMTPAuth,
+ SmtpUsername: cfg.EmailSettings.SMTPUsername,
+ SmtpPassword: cfg.EmailSettings.SMTPPassword,
+ },
+ ); err1 != nil {
+ t.Log(err)
+ t.Fatal("Should get new smtp client")
+ }
+ }
+
+ if _, err := ConnectToSMTPServerAdvanced(
+ &SmtpConnectionInfo{
+ ConnectionSecurity: cfg.EmailSettings.ConnectionSecurity,
+ SkipCertVerification: *cfg.EmailSettings.SkipServerCertificateVerification,
+ SmtpServer: "wrongServer",
+ SmtpPort: "553",
+ },
+ ); err == nil {
t.Log(err)
t.Fatal("Should not to the STMP Server")
}
@@ -79,7 +128,7 @@ func TestSendMailUsingConfig(t *testing.T) {
}
}
-/*func TestSendMailUsingConfigAdvanced(t *testing.T) {
+func TestSendMailUsingConfigAdvanced(t *testing.T) {
cfg, _, err := LoadConfig("config.json")
require.Nil(t, err)
T = GetUserTranslations("en")
@@ -171,20 +220,17 @@ func TestSendMailUsingConfig(t *testing.T) {
}
}
}
-}*/
+}
func TestAuthMethods(t *testing.T) {
- config := model.Config{
- EmailSettings: model.EmailSettings{
- EnableSMTPAuth: model.NewBool(false),
- SMTPUsername: "test",
- SMTPPassword: "fakepass",
- SMTPServer: "fakeserver",
- SMTPPort: "25",
+ auth := &authChooser{
+ connectionInfo: &SmtpConnectionInfo{
+ SmtpUsername: "test",
+ SmtpPassword: "fakepass",
+ SmtpServer: "fakeserver",
+ SmtpPort: "25",
},
}
-
- auth := &authChooser{Config: &config}
tests := []struct {
desc string
server *smtp.ServerInfo