mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
1792 lines
52 KiB
Go
1792 lines
52 KiB
Go
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/utils"
|
|
)
|
|
|
|
func TestSendNotifications(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
th.App.AddUserToChannel(th.BasicUser2, th.BasicChannel)
|
|
|
|
post1, appErr := th.App.CreatePostMissingChannel(&model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "@" + th.BasicUser2.Username,
|
|
Type: model.POST_ADD_TO_CHANNEL,
|
|
Props: map[string]interface{}{model.POST_PROPS_ADDED_USER_ID: "junk"},
|
|
}, true)
|
|
require.Nil(t, appErr)
|
|
|
|
mentions, err := th.App.SendNotifications(post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, mentions)
|
|
require.True(t, utils.StringInSlice(th.BasicUser2.Id, mentions), "mentions", mentions)
|
|
|
|
dm, appErr := th.App.GetOrCreateDirectChannel(th.BasicUser.Id, th.BasicUser2.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
post2, appErr := th.App.CreatePostMissingChannel(&model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dm.Id,
|
|
Message: "dm message",
|
|
}, true)
|
|
require.Nil(t, appErr)
|
|
|
|
mentions, err = th.App.SendNotifications(post2, th.BasicTeam, dm, th.BasicUser, nil)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, mentions)
|
|
|
|
_, appErr = th.App.UpdateActive(th.BasicUser2, false)
|
|
require.Nil(t, appErr)
|
|
appErr = th.App.InvalidateAllCaches()
|
|
require.Nil(t, appErr)
|
|
|
|
post3, appErr := th.App.CreatePostMissingChannel(&model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: dm.Id,
|
|
Message: "dm message",
|
|
}, true)
|
|
require.Nil(t, appErr)
|
|
|
|
mentions, err = th.App.SendNotifications(post3, th.BasicTeam, dm, th.BasicUser, nil)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, mentions)
|
|
|
|
th.BasicChannel.DeleteAt = 1
|
|
mentions, err = th.App.SendNotifications(post1, th.BasicTeam, th.BasicChannel, th.BasicUser, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, mentions, 0)
|
|
}
|
|
|
|
func TestSendNotificationsWithManyUsers(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
users := []*model.User{}
|
|
for i := 0; i < 10; i++ {
|
|
user := th.CreateUser()
|
|
th.LinkUserToTeam(user, th.BasicTeam)
|
|
th.App.AddUserToChannel(user, th.BasicChannel)
|
|
users = append(users, user)
|
|
}
|
|
|
|
_, appErr1 := th.App.CreatePostMissingChannel(&model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "@channel",
|
|
Type: model.POST_ADD_TO_CHANNEL,
|
|
Props: map[string]interface{}{model.POST_PROPS_ADDED_USER_ID: "junk"},
|
|
}, true)
|
|
require.Nil(t, appErr1)
|
|
|
|
// Each user should have a mention count of exactly 1 in the DB at this point.
|
|
t.Run("1-mention", func(t *testing.T) {
|
|
for i, user := range users {
|
|
t.Run(fmt.Sprintf("user-%d", i+1), func(t *testing.T) {
|
|
channelUnread, appErr2 := th.Server.Store.Channel().GetChannelUnread(th.BasicChannel.Id, user.Id)
|
|
require.Nil(t, appErr2)
|
|
assert.Equal(t, int64(1), channelUnread.MentionCount)
|
|
})
|
|
}
|
|
})
|
|
|
|
_, appErr1 = th.App.CreatePostMissingChannel(&model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: th.BasicChannel.Id,
|
|
Message: "@channel",
|
|
Type: model.POST_ADD_TO_CHANNEL,
|
|
Props: map[string]interface{}{model.POST_PROPS_ADDED_USER_ID: "junk"},
|
|
}, true)
|
|
require.Nil(t, appErr1)
|
|
|
|
// Now each user should have a mention count of exactly 2 in the DB.
|
|
t.Run("2-mentions", func(t *testing.T) {
|
|
for i, user := range users {
|
|
t.Run(fmt.Sprintf("user-%d", i+1), func(t *testing.T) {
|
|
channelUnread, appErr2 := th.Server.Store.Channel().GetChannelUnread(th.BasicChannel.Id, user.Id)
|
|
require.Nil(t, appErr2)
|
|
assert.Equal(t, int64(2), channelUnread.MentionCount)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSendOutOfChannelMentions(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
|
|
t.Run("should send ephemeral post when there is an out of channel mention", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
potentialMentions := []string{user2.Username}
|
|
|
|
sent, err := th.App.sendOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.True(t, sent)
|
|
})
|
|
|
|
t.Run("should not send ephemeral post when there are no out of channel mentions", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
potentialMentions := []string{"not a user"}
|
|
|
|
sent, err := th.App.sendOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.False(t, sent)
|
|
})
|
|
}
|
|
|
|
func TestFilterOutOfChannelMentions(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
channel := th.BasicChannel
|
|
|
|
user1 := th.BasicUser
|
|
user2 := th.BasicUser2
|
|
user3 := th.CreateUser()
|
|
th.LinkUserToTeam(user3, th.BasicTeam)
|
|
|
|
t.Run("should return users not in the channel", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
potentialMentions := []string{user2.Username, user3.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Len(t, outOfChannelUsers, 2)
|
|
assert.True(t, (outOfChannelUsers[0].Id == user2.Id || outOfChannelUsers[1].Id == user2.Id))
|
|
assert.True(t, (outOfChannelUsers[0].Id == user3.Id || outOfChannelUsers[1].Id == user3.Id))
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return results for a system message", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Type: model.POST_ADD_REMOVE,
|
|
}
|
|
potentialMentions := []string{user2.Username, user3.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return results for a direct message", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
directChannel := &model.Channel{
|
|
Type: model.CHANNEL_DIRECT,
|
|
}
|
|
potentialMentions := []string{user2.Username, user3.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, directChannel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return results for a group message", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
groupChannel := &model.Channel{
|
|
Type: model.CHANNEL_GROUP,
|
|
}
|
|
potentialMentions := []string{user2.Username, user3.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, groupChannel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return inactive users", func(t *testing.T) {
|
|
inactiveUser := th.CreateUser()
|
|
inactiveUser, appErr := th.App.UpdateActive(inactiveUser, false)
|
|
require.Nil(t, appErr)
|
|
|
|
post := &model.Post{}
|
|
potentialMentions := []string{inactiveUser.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return bot users", func(t *testing.T) {
|
|
botUser := th.CreateUser()
|
|
botUser.IsBot = true
|
|
|
|
post := &model.Post{}
|
|
potentialMentions := []string{botUser.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should not return results for non-existant users", func(t *testing.T) {
|
|
post := &model.Post{}
|
|
potentialMentions := []string{"foo", "bar"}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, channel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Nil(t, outOfChannelUsers)
|
|
assert.Nil(t, outOfGroupUsers)
|
|
})
|
|
|
|
t.Run("should separate users not in the channel from users not in the group", func(t *testing.T) {
|
|
nonChannelMember := th.CreateUser()
|
|
th.LinkUserToTeam(nonChannelMember, th.BasicTeam)
|
|
nonGroupMember := th.CreateUser()
|
|
th.LinkUserToTeam(nonGroupMember, th.BasicTeam)
|
|
|
|
group := th.CreateGroup()
|
|
_, appErr := th.App.UpsertGroupMember(group.Id, th.BasicUser.Id)
|
|
require.Nil(t, appErr)
|
|
_, appErr = th.App.UpsertGroupMember(group.Id, nonChannelMember.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
constrainedChannel := th.CreateChannel(th.BasicTeam)
|
|
constrainedChannel.GroupConstrained = model.NewBool(true)
|
|
constrainedChannel, appErr = th.App.UpdateChannel(constrainedChannel)
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.CreateGroupSyncable(&model.GroupSyncable{
|
|
GroupId: group.Id,
|
|
Type: model.GroupSyncableTypeChannel,
|
|
SyncableId: constrainedChannel.Id,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := &model.Post{}
|
|
potentialMentions := []string{nonChannelMember.Username, nonGroupMember.Username}
|
|
|
|
outOfChannelUsers, outOfGroupUsers, err := th.App.filterOutOfChannelMentions(user1, post, constrainedChannel, potentialMentions)
|
|
|
|
assert.Nil(t, err)
|
|
assert.Len(t, outOfChannelUsers, 1)
|
|
assert.Equal(t, nonChannelMember.Id, outOfChannelUsers[0].Id)
|
|
assert.Len(t, outOfGroupUsers, 1)
|
|
assert.Equal(t, nonGroupMember.Id, outOfGroupUsers[0].Id)
|
|
})
|
|
}
|
|
|
|
func TestGetExplicitMentions(t *testing.T) {
|
|
id1 := model.NewId()
|
|
id2 := model.NewId()
|
|
id3 := model.NewId()
|
|
|
|
for name, tc := range map[string]struct {
|
|
Message string
|
|
Attachments []*model.SlackAttachment
|
|
Keywords map[string][]string
|
|
Expected *ExplicitMentions
|
|
}{
|
|
"Nobody": {
|
|
Message: "this is a message",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"NonexistentUser": {
|
|
Message: "this is a message for @user",
|
|
Expected: &ExplicitMentions{
|
|
OtherPotentialMentions: []string{"user"},
|
|
},
|
|
},
|
|
"OnePerson": {
|
|
Message: "this is a message for @user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithPeriodAtEndOfUsername": {
|
|
Message: "this is a message for @user.name.",
|
|
Keywords: map[string][]string{"@user.name.": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithPeriodAtEndOfUsernameButNotSimilarName": {
|
|
Message: "this is a message for @user.name.",
|
|
Keywords: map[string][]string{"@user.name.": {id1}, "@user.name": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonAtEndOfSentence": {
|
|
Message: "this is a message for @user.",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithoutAtMention": {
|
|
Message: "this is a message for @user",
|
|
Keywords: map[string][]string{"this": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
OtherPotentialMentions: []string{"user"},
|
|
},
|
|
},
|
|
"OnePersonWithPeriodAfter": {
|
|
Message: "this is a message for @user.",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithPeriodBefore": {
|
|
Message: "this is a message for .@user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithColonAfter": {
|
|
Message: "this is a message for @user:",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithColonBefore": {
|
|
Message: "this is a message for :@user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithHyphenAfter": {
|
|
Message: "this is a message for @user.",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"OnePersonWithHyphenBefore": {
|
|
Message: "this is a message for -@user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultiplePeopleWithOneWord": {
|
|
Message: "this is a message for @user",
|
|
Keywords: map[string][]string{"@user": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
},
|
|
},
|
|
"OneOfMultiplePeople": {
|
|
Message: "this is a message for @user",
|
|
Keywords: map[string][]string{"@user": {id1}, "@mention": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultiplePeopleWithMultipleWords": {
|
|
Message: "this is an @mention for @user",
|
|
Keywords: map[string][]string{"@user": {id1}, "@mention": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
},
|
|
},
|
|
"Channel": {
|
|
Message: "this is an message for @channel",
|
|
Keywords: map[string][]string{"@channel": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
|
|
"ChannelWithColonAtEnd": {
|
|
Message: "this is a message for @channel:",
|
|
Keywords: map[string][]string{"@channel": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"CapitalizedChannel": {
|
|
Message: "this is an message for @cHaNNeL",
|
|
Keywords: map[string][]string{"@channel": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"All": {
|
|
Message: "this is an message for @all",
|
|
Keywords: map[string][]string{"@all": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
"AllWithColonAtEnd": {
|
|
Message: "this is a message for @all:",
|
|
Keywords: map[string][]string{"@all": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
"CapitalizedAll": {
|
|
Message: "this is an message for @ALL",
|
|
Keywords: map[string][]string{"@all": {id1, id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
"UserWithPeriod": {
|
|
Message: "user.period doesn't complicate things at all by including periods in their username",
|
|
Keywords: map[string][]string{"user.period": {id1}, "user": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"AtUserWithColonAtEnd": {
|
|
Message: "this is a message for @user:",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"AtUserWithPeriodAtEndOfSentence": {
|
|
Message: "this is a message for @user.period.",
|
|
Keywords: map[string][]string{"@user.period": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"UserWithPeriodAtEndOfSentence": {
|
|
Message: "this is a message for user.period.",
|
|
Keywords: map[string][]string{"user.period": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"UserWithColonAtEnd": {
|
|
Message: "this is a message for user:",
|
|
Keywords: map[string][]string{"user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"PotentialOutOfChannelUser": {
|
|
Message: "this is an message for @potential and @user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
OtherPotentialMentions: []string{"potential"},
|
|
},
|
|
},
|
|
"PotentialOutOfChannelUserWithPeriod": {
|
|
Message: "this is an message for @potential.user",
|
|
Expected: &ExplicitMentions{
|
|
OtherPotentialMentions: []string{"potential.user"},
|
|
},
|
|
},
|
|
"InlineCode": {
|
|
Message: "`this shouldn't mention @channel at all`",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"FencedCodeBlock": {
|
|
Message: "```\nthis shouldn't mention @channel at all\n```",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"Emphasis": {
|
|
Message: "*@aaa @bbb @ccc*",
|
|
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
id3: true,
|
|
},
|
|
},
|
|
},
|
|
"StrongEmphasis": {
|
|
Message: "**@aaa @bbb @ccc**",
|
|
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
id3: true,
|
|
},
|
|
},
|
|
},
|
|
"Strikethrough": {
|
|
Message: "~~@aaa @bbb @ccc~~",
|
|
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
id3: true,
|
|
},
|
|
},
|
|
},
|
|
"Heading": {
|
|
Message: "### @aaa",
|
|
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"BlockQuote": {
|
|
Message: "> @aaa",
|
|
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Emoji": {
|
|
Message: ":smile:",
|
|
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"NotEmoji": {
|
|
Message: "smile",
|
|
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"UnclosedEmoji": {
|
|
Message: ":smile",
|
|
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"UnopenedEmoji": {
|
|
Message: "smile:",
|
|
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"IndentedCodeBlock": {
|
|
Message: " this shouldn't mention @channel at all",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"LinkTitle": {
|
|
Message: `[foo](this "shouldn't mention @channel at all")`,
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"MalformedInlineCode": {
|
|
Message: "`this should mention @channel``",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"MultibyteCharacter": {
|
|
Message: "My name is 萌",
|
|
Keywords: map[string][]string{"萌": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtBeginningOfSentence": {
|
|
Message: "이메일을 보내다.",
|
|
Keywords: map[string][]string{"이메일": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterInPartOfSentence": {
|
|
Message: "我爱吃番茄炒饭",
|
|
Keywords: map[string][]string{"番茄": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtEndOfSentence": {
|
|
Message: "こんにちは、世界",
|
|
Keywords: map[string][]string{"世界": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterTwiceInSentence": {
|
|
Message: "石橋さんが石橋を渡る",
|
|
Keywords: map[string][]string{"石橋": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
|
|
// The following tests cover cases where the message mentions @user.name, so we shouldn't assume that
|
|
// the user might be intending to mention some @user that isn't in the channel.
|
|
"Don't include potential mention that's part of an actual mention (without trailing period)": {
|
|
Message: "this is an message for @user.name",
|
|
Keywords: map[string][]string{"@user.name": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Don't include potential mention that's part of an actual mention (with trailing period)": {
|
|
Message: "this is an message for @user.name.",
|
|
Keywords: map[string][]string{"@user.name": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Don't include potential mention that's part of an actual mention (with multiple trailing periods)": {
|
|
Message: "this is an message for @user.name...",
|
|
Keywords: map[string][]string{"@user.name": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Don't include potential mention that's part of an actual mention (containing and followed by multiple periods)": {
|
|
Message: "this is an message for @user...name...",
|
|
Keywords: map[string][]string{"@user...name": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"should include the mentions from attachment text and preText": {
|
|
Message: "this is an message for @user1",
|
|
Attachments: []*model.SlackAttachment{
|
|
{
|
|
Text: "this is a message For @user2",
|
|
Pretext: "this is a message for @here",
|
|
},
|
|
},
|
|
Keywords: map[string][]string{"@user1": {id1}, "@user2": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
HereMentioned: true,
|
|
},
|
|
},
|
|
"Name on keywords is a prefix of a mention": {
|
|
Message: "@other @test-two",
|
|
Keywords: map[string][]string{"@test": {model.NewId()}},
|
|
Expected: &ExplicitMentions{
|
|
OtherPotentialMentions: []string{"other", "test-two"},
|
|
},
|
|
},
|
|
"Name on mentions is a prefix of other mention": {
|
|
Message: "@other-one @other @other-two",
|
|
Keywords: nil,
|
|
Expected: &ExplicitMentions{
|
|
OtherPotentialMentions: []string{"other-one", "other", "other-two"},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
post := &model.Post{Message: tc.Message, Props: model.StringInterface{
|
|
"attachments": tc.Attachments,
|
|
},
|
|
}
|
|
|
|
m := getExplicitMentions(post, tc.Keywords)
|
|
if tc.Expected.MentionedUserIds == nil {
|
|
tc.Expected.MentionedUserIds = make(map[string]bool)
|
|
}
|
|
assert.EqualValues(t, tc.Expected, m)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetExplicitMentionsAtHere(t *testing.T) {
|
|
// test all the boundary cases that we know can break up terms (and those that we know won't)
|
|
cases := map[string]bool{
|
|
"": false,
|
|
"here": false,
|
|
"@here": true,
|
|
" @here ": true,
|
|
"\n@here\n": true,
|
|
"!@here!": true,
|
|
"#@here#": true,
|
|
"$@here$": true,
|
|
"%@here%": true,
|
|
"^@here^": true,
|
|
"&@here&": true,
|
|
"*@here*": true,
|
|
"(@here(": true,
|
|
")@here)": true,
|
|
"-@here-": true,
|
|
"_@here_": true,
|
|
"=@here=": true,
|
|
"+@here+": true,
|
|
"[@here[": true,
|
|
"{@here{": true,
|
|
"]@here]": true,
|
|
"}@here}": true,
|
|
"\\@here\\": true,
|
|
"|@here|": true,
|
|
";@here;": true,
|
|
"@here:": true,
|
|
":@here:": false, // This case shouldn't trigger a mention since it follows the format of reactions e.g. :word:
|
|
"'@here'": true,
|
|
"\"@here\"": true,
|
|
",@here,": true,
|
|
"<@here<": true,
|
|
".@here.": true,
|
|
">@here>": true,
|
|
"/@here/": true,
|
|
"?@here?": true,
|
|
"`@here`": false, // This case shouldn't mention since it's a code block
|
|
"~@here~": true,
|
|
"@HERE": true,
|
|
"@hERe": true,
|
|
}
|
|
|
|
for message, shouldMention := range cases {
|
|
post := &model.Post{Message: message}
|
|
if m := getExplicitMentions(post, nil); m.HereMentioned && !shouldMention {
|
|
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
|
|
} else if !m.HereMentioned && shouldMention {
|
|
t.Fatalf("should've mentioned @here with \"%v\"", message)
|
|
}
|
|
}
|
|
|
|
// mentioning @here and someone
|
|
id := model.NewId()
|
|
if m := getExplicitMentions(&model.Post{Message: "@here @user @potential"}, map[string][]string{"@user": {id}}); !m.HereMentioned {
|
|
t.Fatal("should've mentioned @here with \"@here @user\"")
|
|
} else if len(m.MentionedUserIds) != 1 || !m.MentionedUserIds[id] {
|
|
t.Fatal("should've mentioned @user with \"@here @user\"")
|
|
} else if len(m.OtherPotentialMentions) > 1 {
|
|
t.Fatal("should've potential mentions for @potential")
|
|
}
|
|
}
|
|
|
|
func TestGetMentionKeywords(t *testing.T) {
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
// user with username or custom mentions enabled
|
|
user1 := &model.User{
|
|
Id: model.NewId(),
|
|
FirstName: "First",
|
|
Username: "User",
|
|
NotifyProps: map[string]string{
|
|
"mention_keys": "User,@User,MENTION",
|
|
},
|
|
}
|
|
|
|
channelMemberNotifyPropsMap1Off := map[string]model.StringMap{
|
|
user1.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
|
|
profiles := map[string]*model.User{user1.Id: user1}
|
|
mentions := th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap1Off)
|
|
if len(mentions) != 3 {
|
|
t.Fatal("should've returned three mention keywords")
|
|
} else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id {
|
|
t.Fatal("should've returned mention key of user")
|
|
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user1.Id {
|
|
t.Fatal("should've returned mention key of @user")
|
|
} else if ids, ok := mentions["mention"]; !ok || ids[0] != user1.Id {
|
|
t.Fatal("should've returned mention key of mention")
|
|
}
|
|
|
|
// user with first name mention enabled
|
|
user2 := &model.User{
|
|
Id: model.NewId(),
|
|
FirstName: "First",
|
|
Username: "User",
|
|
NotifyProps: map[string]string{
|
|
"first_name": "true",
|
|
},
|
|
}
|
|
|
|
channelMemberNotifyPropsMap2Off := map[string]model.StringMap{
|
|
user2.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
|
|
profiles = map[string]*model.User{user2.Id: user2}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap2Off)
|
|
if len(mentions) != 2 {
|
|
t.Fatal("should've returned two mention keyword")
|
|
} else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id {
|
|
t.Fatal("should've returned mention key of First")
|
|
}
|
|
|
|
// user with @channel/@all mentions enabled
|
|
user3 := &model.User{
|
|
Id: model.NewId(),
|
|
FirstName: "First",
|
|
Username: "User",
|
|
NotifyProps: map[string]string{
|
|
"channel": "true",
|
|
},
|
|
}
|
|
|
|
// Channel-wide mentions are not ignored on channel level
|
|
channelMemberNotifyPropsMap3Off := map[string]model.StringMap{
|
|
user3.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
profiles = map[string]*model.User{user3.Id: user3}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3Off)
|
|
if len(mentions) != 3 {
|
|
t.Fatal("should've returned three mention keywords")
|
|
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @channel")
|
|
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @all")
|
|
}
|
|
|
|
// Channel member notify props is set to default
|
|
channelMemberNotifyPropsMapDefault := map[string]model.StringMap{
|
|
user3.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_DEFAULT,
|
|
},
|
|
}
|
|
profiles = map[string]*model.User{user3.Id: user3}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapDefault)
|
|
if len(mentions) != 3 {
|
|
t.Fatal("should've returned three mention keywords")
|
|
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @channel")
|
|
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @all")
|
|
}
|
|
|
|
// Channel member notify props is empty
|
|
channelMemberNotifyPropsMapEmpty := map[string]model.StringMap{}
|
|
profiles = map[string]*model.User{user3.Id: user3}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmpty)
|
|
if len(mentions) != 3 {
|
|
t.Fatal("should've returned three mention keywords")
|
|
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @channel")
|
|
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id {
|
|
t.Fatal("should've returned mention key of @all")
|
|
}
|
|
|
|
// Channel-wide mentions are ignored channel level
|
|
channelMemberNotifyPropsMap3On := map[string]model.StringMap{
|
|
user3.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON,
|
|
},
|
|
}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3On)
|
|
if len(mentions) == 0 {
|
|
t.Fatal("should've not returned any keywords")
|
|
}
|
|
|
|
// user with all types of mentions enabled
|
|
user4 := &model.User{
|
|
Id: model.NewId(),
|
|
FirstName: "First",
|
|
Username: "User",
|
|
NotifyProps: map[string]string{
|
|
"mention_keys": "User,@User,MENTION",
|
|
"first_name": "true",
|
|
"channel": "true",
|
|
},
|
|
}
|
|
|
|
// Channel-wide mentions are not ignored on channel level
|
|
channelMemberNotifyPropsMap4Off := map[string]model.StringMap{
|
|
user4.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
|
|
profiles = map[string]*model.User{user4.Id: user4}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off)
|
|
if len(mentions) != 6 {
|
|
t.Fatal("should've returned six mention keywords")
|
|
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of user")
|
|
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of @user")
|
|
} else if ids, ok := mentions["mention"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of mention")
|
|
} else if ids, ok := mentions["First"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of First")
|
|
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of @channel")
|
|
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of @all")
|
|
}
|
|
|
|
// Channel-wide mentions are ignored on channel level
|
|
channelMemberNotifyPropsMap4On := map[string]model.StringMap{
|
|
user4.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON,
|
|
},
|
|
}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4On)
|
|
if len(mentions) != 4 {
|
|
t.Fatal("should've returned four mention keywords")
|
|
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of user")
|
|
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of @user")
|
|
} else if ids, ok := mentions["mention"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of mention")
|
|
} else if ids, ok := mentions["First"]; !ok || ids[0] != user4.Id {
|
|
t.Fatal("should've returned mention key of First")
|
|
}
|
|
|
|
dup_count := func(list []string) map[string]int {
|
|
|
|
duplicate_frequency := make(map[string]int)
|
|
|
|
for _, item := range list {
|
|
// check if the item/element exist in the duplicate_frequency map
|
|
|
|
_, exist := duplicate_frequency[item]
|
|
|
|
if exist {
|
|
duplicate_frequency[item] += 1 // increase counter by 1 if already in the map
|
|
} else {
|
|
duplicate_frequency[item] = 1 // else start counting from 1
|
|
}
|
|
}
|
|
return duplicate_frequency
|
|
}
|
|
|
|
// multiple users but no more than MaxNotificationsPerChannel
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxNotificationsPerChannel = 4 })
|
|
profiles = map[string]*model.User{
|
|
user1.Id: user1,
|
|
user2.Id: user2,
|
|
user3.Id: user3,
|
|
user4.Id: user4,
|
|
}
|
|
// Channel-wide mentions are not ignored on channel level for all users
|
|
channelMemberNotifyPropsMap5Off := map[string]model.StringMap{
|
|
user1.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
user2.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
user3.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
user4.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap5Off)
|
|
if len(mentions) != 6 {
|
|
t.Fatal("should've returned six mention keywords")
|
|
} else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
|
t.Fatal("should've mentioned user1 and user4 with user")
|
|
} else if ids := dup_count(mentions["@user"]); len(ids) != 4 || (ids[user1.Id] != 2) || (ids[user4.Id] != 2) {
|
|
t.Fatal("should've mentioned user1 and user4 with @user")
|
|
} else if ids, ok := mentions["mention"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
|
t.Fatal("should've mentioned user1 and user4 with mention")
|
|
} else if ids, ok := mentions["First"]; !ok || len(ids) != 2 || (ids[0] != user2.Id && ids[1] != user2.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
|
t.Fatal("should've mentioned user2 and user4 with First")
|
|
} else if ids, ok := mentions["@channel"]; !ok || len(ids) != 2 || (ids[0] != user3.Id && ids[1] != user3.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
|
t.Fatal("should've mentioned user3 and user4 with @channel")
|
|
} else if ids, ok := mentions["@all"]; !ok || len(ids) != 2 || (ids[0] != user3.Id && ids[1] != user3.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
|
|
t.Fatal("should've mentioned user3 and user4 with @all")
|
|
}
|
|
|
|
// multiple users and more than MaxNotificationsPerChannel
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxNotificationsPerChannel = 3 })
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off)
|
|
if len(mentions) != 4 {
|
|
t.Fatal("should've returned four mention keywords")
|
|
} else if _, ok := mentions["@channel"]; ok {
|
|
t.Fatal("should not have mentioned any user with @channel")
|
|
} else if _, ok := mentions["@all"]; ok {
|
|
t.Fatal("should not have mentioned any user with @all")
|
|
} else if _, ok := mentions["@here"]; ok {
|
|
t.Fatal("should not have mentioned any user with @here")
|
|
}
|
|
|
|
// no special mentions
|
|
profiles = map[string]*model.User{
|
|
user1.Id: user1,
|
|
}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, false, channelMemberNotifyPropsMap4Off)
|
|
if len(mentions) != 3 {
|
|
t.Fatal("should've returned three mention keywords")
|
|
} else if ids, ok := mentions["user"]; !ok || len(ids) != 1 || ids[0] != user1.Id {
|
|
t.Fatal("should've mentioned user1 with user")
|
|
} else if ids, ok := mentions["@user"]; !ok || len(ids) != 2 || ids[0] != user1.Id || ids[1] != user1.Id {
|
|
t.Fatal("should've mentioned user1 twice with @user")
|
|
} else if ids, ok := mentions["mention"]; !ok || len(ids) != 1 || ids[0] != user1.Id {
|
|
t.Fatal("should've mentioned user1 with mention")
|
|
} else if _, ok := mentions["First"]; ok {
|
|
t.Fatal("should not have mentioned user1 with First")
|
|
} else if _, ok := mentions["@channel"]; ok {
|
|
t.Fatal("should not have mentioned any user with @channel")
|
|
} else if _, ok := mentions["@all"]; ok {
|
|
t.Fatal("should not have mentioned any user with @all")
|
|
} else if _, ok := mentions["@here"]; ok {
|
|
t.Fatal("should not have mentioned any user with @here")
|
|
}
|
|
|
|
// user with empty mention keys
|
|
userNoMentionKeys := &model.User{
|
|
Id: model.NewId(),
|
|
FirstName: "First",
|
|
Username: "User",
|
|
NotifyProps: map[string]string{
|
|
"mention_keys": ",",
|
|
},
|
|
}
|
|
|
|
channelMemberNotifyPropsMapEmptyOff := map[string]model.StringMap{
|
|
userNoMentionKeys.Id: {
|
|
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
|
|
},
|
|
}
|
|
|
|
profiles = map[string]*model.User{userNoMentionKeys.Id: userNoMentionKeys}
|
|
mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmptyOff)
|
|
assert.Equal(t, 1, len(mentions), "should've returned one metion keyword")
|
|
ids, ok := mentions["@user"]
|
|
assert.True(t, ok)
|
|
assert.Equal(t, userNoMentionKeys.Id, ids[0], "should've returned mention key of @user")
|
|
}
|
|
|
|
func TestGetMentionsEnabledFields(t *testing.T) {
|
|
|
|
attachmentWithTextAndPreText := model.SlackAttachment{
|
|
Text: "@here with mentions",
|
|
Pretext: "@Channel some comment for the channel",
|
|
}
|
|
|
|
attachmentWithOutPreText := model.SlackAttachment{
|
|
Text: "some text",
|
|
}
|
|
attachments := []*model.SlackAttachment{
|
|
&attachmentWithTextAndPreText,
|
|
&attachmentWithOutPreText,
|
|
}
|
|
|
|
post := &model.Post{
|
|
Message: "This is the message",
|
|
Props: model.StringInterface{
|
|
"attachments": attachments,
|
|
},
|
|
}
|
|
expectedFields := []string{
|
|
"This is the message",
|
|
"@Channel some comment for the channel",
|
|
"@here with mentions",
|
|
"some text"}
|
|
|
|
mentionEnabledFields := getMentionsEnabledFields(post)
|
|
|
|
assert.EqualValues(t, 4, len(mentionEnabledFields))
|
|
assert.EqualValues(t, expectedFields, mentionEnabledFields)
|
|
}
|
|
|
|
func TestPostNotificationGetChannelName(t *testing.T) {
|
|
sender := &model.User{Id: model.NewId(), Username: "sender", FirstName: "Sender", LastName: "Sender", Nickname: "Sender"}
|
|
recipient := &model.User{Id: model.NewId(), Username: "recipient", FirstName: "Recipient", LastName: "Recipient", Nickname: "Recipient"}
|
|
otherUser := &model.User{Id: model.NewId(), Username: "other", FirstName: "Other", LastName: "Other", Nickname: "Other"}
|
|
profileMap := map[string]*model.User{
|
|
sender.Id: sender,
|
|
recipient.Id: recipient,
|
|
otherUser.Id: otherUser,
|
|
}
|
|
|
|
for name, testCase := range map[string]struct {
|
|
channel *model.Channel
|
|
nameFormat string
|
|
recipientId string
|
|
expected string
|
|
}{
|
|
"regular channel": {
|
|
channel: &model.Channel{Type: model.CHANNEL_OPEN, Name: "channel", DisplayName: "My Channel"},
|
|
expected: "My Channel",
|
|
},
|
|
"direct channel, unspecified": {
|
|
channel: &model.Channel{Type: model.CHANNEL_DIRECT},
|
|
expected: "@sender",
|
|
},
|
|
"direct channel, username": {
|
|
channel: &model.Channel{Type: model.CHANNEL_DIRECT},
|
|
nameFormat: model.SHOW_USERNAME,
|
|
expected: "@sender",
|
|
},
|
|
"direct channel, full name": {
|
|
channel: &model.Channel{Type: model.CHANNEL_DIRECT},
|
|
nameFormat: model.SHOW_FULLNAME,
|
|
expected: "Sender Sender",
|
|
},
|
|
"direct channel, nickname": {
|
|
channel: &model.Channel{Type: model.CHANNEL_DIRECT},
|
|
nameFormat: model.SHOW_NICKNAME_FULLNAME,
|
|
expected: "Sender",
|
|
},
|
|
"group channel, unspecified": {
|
|
channel: &model.Channel{Type: model.CHANNEL_GROUP},
|
|
expected: "other, sender",
|
|
},
|
|
"group channel, username": {
|
|
channel: &model.Channel{Type: model.CHANNEL_GROUP},
|
|
nameFormat: model.SHOW_USERNAME,
|
|
expected: "other, sender",
|
|
},
|
|
"group channel, full name": {
|
|
channel: &model.Channel{Type: model.CHANNEL_GROUP},
|
|
nameFormat: model.SHOW_FULLNAME,
|
|
expected: "Other Other, Sender Sender",
|
|
},
|
|
"group channel, nickname": {
|
|
channel: &model.Channel{Type: model.CHANNEL_GROUP},
|
|
nameFormat: model.SHOW_NICKNAME_FULLNAME,
|
|
expected: "Other, Sender",
|
|
},
|
|
"group channel, not excluding current user": {
|
|
channel: &model.Channel{Type: model.CHANNEL_GROUP},
|
|
nameFormat: model.SHOW_NICKNAME_FULLNAME,
|
|
expected: "Other, Sender",
|
|
recipientId: "",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
notification := &postNotification{
|
|
channel: testCase.channel,
|
|
sender: sender,
|
|
profileMap: profileMap,
|
|
}
|
|
|
|
recipientId := recipient.Id
|
|
if testCase.recipientId != "" {
|
|
recipientId = testCase.recipientId
|
|
}
|
|
|
|
assert.Equal(t, testCase.expected, notification.GetChannelName(testCase.nameFormat, recipientId))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPostNotificationGetSenderName(t *testing.T) {
|
|
th := Setup(t)
|
|
defer th.TearDown()
|
|
|
|
defaultChannel := &model.Channel{Type: model.CHANNEL_OPEN}
|
|
defaultPost := &model.Post{Props: model.StringInterface{}}
|
|
sender := &model.User{Id: model.NewId(), Username: "sender", FirstName: "Sender", LastName: "Sender", Nickname: "Sender"}
|
|
|
|
overriddenPost := &model.Post{
|
|
Props: model.StringInterface{
|
|
"override_username": "Overridden",
|
|
"from_webhook": "true",
|
|
},
|
|
}
|
|
|
|
for name, testCase := range map[string]struct {
|
|
channel *model.Channel
|
|
post *model.Post
|
|
nameFormat string
|
|
allowOverrides bool
|
|
expected string
|
|
}{
|
|
"name format unspecified": {
|
|
expected: "@" + sender.Username,
|
|
},
|
|
"name format username": {
|
|
nameFormat: model.SHOW_USERNAME,
|
|
expected: "@" + sender.Username,
|
|
},
|
|
"name format full name": {
|
|
nameFormat: model.SHOW_FULLNAME,
|
|
expected: sender.FirstName + " " + sender.LastName,
|
|
},
|
|
"name format nickname": {
|
|
nameFormat: model.SHOW_NICKNAME_FULLNAME,
|
|
expected: sender.Nickname,
|
|
},
|
|
"system message": {
|
|
post: &model.Post{Type: model.POST_SYSTEM_MESSAGE_PREFIX + "custom"},
|
|
expected: utils.T("system.message.name"),
|
|
},
|
|
"overridden username": {
|
|
post: overriddenPost,
|
|
allowOverrides: true,
|
|
expected: overriddenPost.Props["override_username"].(string),
|
|
},
|
|
"overridden username, direct channel": {
|
|
channel: &model.Channel{Type: model.CHANNEL_DIRECT},
|
|
post: overriddenPost,
|
|
allowOverrides: true,
|
|
expected: "@" + sender.Username,
|
|
},
|
|
"overridden username, overrides disabled": {
|
|
post: overriddenPost,
|
|
allowOverrides: false,
|
|
expected: "@" + sender.Username,
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
channel := defaultChannel
|
|
if testCase.channel != nil {
|
|
channel = testCase.channel
|
|
}
|
|
|
|
post := defaultPost
|
|
if testCase.post != nil {
|
|
post = testCase.post
|
|
}
|
|
|
|
notification := &postNotification{
|
|
channel: channel,
|
|
post: post,
|
|
sender: sender,
|
|
}
|
|
|
|
assert.Equal(t, testCase.expected, notification.GetSenderName(testCase.nameFormat, testCase.allowOverrides))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsKeywordMultibyte(t *testing.T) {
|
|
id1 := model.NewId()
|
|
|
|
for name, tc := range map[string]struct {
|
|
Message string
|
|
Attachments []*model.SlackAttachment
|
|
Keywords map[string][]string
|
|
Expected *ExplicitMentions
|
|
}{
|
|
"MultibyteCharacter": {
|
|
Message: "My name is 萌",
|
|
Keywords: map[string][]string{"萌": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterWithNoUser": {
|
|
Message: "My name is 萌",
|
|
Keywords: map[string][]string{"萌": {}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtBeginningOfSentence": {
|
|
Message: "이메일을 보내다.",
|
|
Keywords: map[string][]string{"이메일": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtBeginningOfSentenceWithNoUser": {
|
|
Message: "이메일을 보내다.",
|
|
Keywords: map[string][]string{"이메일": {}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
"MultibyteCharacterInPartOfSentence": {
|
|
Message: "我爱吃番茄炒饭",
|
|
Keywords: map[string][]string{"番茄": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterInPartOfSentenceWithNoUser": {
|
|
Message: "我爱吃番茄炒饭",
|
|
Keywords: map[string][]string{"番茄": {}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtEndOfSentence": {
|
|
Message: "こんにちは、世界",
|
|
Keywords: map[string][]string{"世界": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterAtEndOfSentenceWithNoUser": {
|
|
Message: "こんにちは、世界",
|
|
Keywords: map[string][]string{"世界": {}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
"MultibyteCharacterTwiceInSentence": {
|
|
Message: "石橋さんが石橋を渡る",
|
|
Keywords: map[string][]string{"石橋": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"MultibyteCharacterTwiceInSentenceWithNoUser": {
|
|
Message: "石橋さんが石橋を渡る",
|
|
Keywords: map[string][]string{"石橋": {}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
post := &model.Post{Message: tc.Message, Props: model.StringInterface{
|
|
"attachments": tc.Attachments,
|
|
},
|
|
}
|
|
|
|
m := getExplicitMentions(post, tc.Keywords)
|
|
if tc.Expected.MentionedUserIds == nil {
|
|
tc.Expected.MentionedUserIds = make(map[string]bool)
|
|
}
|
|
assert.EqualValues(t, tc.Expected, m)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddMentionedUsers(t *testing.T) {
|
|
id1 := model.NewId()
|
|
id2 := model.NewId()
|
|
id3 := model.NewId()
|
|
id4 := model.NewId()
|
|
id5 := model.NewId()
|
|
id6 := model.NewId()
|
|
id7 := model.NewId()
|
|
id8 := model.NewId()
|
|
id9 := model.NewId()
|
|
|
|
for name, tc := range map[string]struct {
|
|
Mentions []string
|
|
ExplicitMentions *ExplicitMentions
|
|
Expected *ExplicitMentions
|
|
}{
|
|
"test": {
|
|
Mentions: []string{id1},
|
|
ExplicitMentions: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"two users": {
|
|
Mentions: []string{id1, id2},
|
|
ExplicitMentions: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
},
|
|
},
|
|
},
|
|
"no users": {
|
|
Mentions: []string{},
|
|
ExplicitMentions: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
},
|
|
"five users": {
|
|
Mentions: []string{id1, id5, id4, id8, id9},
|
|
ExplicitMentions: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id4: true,
|
|
id5: true,
|
|
id8: true,
|
|
id9: true,
|
|
},
|
|
},
|
|
},
|
|
"nine users": {
|
|
Mentions: []string{id1, id2, id3, id4, id5, id6, id7, id8, id9},
|
|
ExplicitMentions: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{},
|
|
},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
id2: true,
|
|
id3: true,
|
|
id4: true,
|
|
id5: true,
|
|
id6: true,
|
|
id7: true,
|
|
id8: true,
|
|
id9: true,
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
tc.ExplicitMentions.addMentionedUsers(tc.Mentions)
|
|
if tc.ExplicitMentions.MentionedUserIds == nil {
|
|
tc.ExplicitMentions.MentionedUserIds = make(map[string]bool)
|
|
}
|
|
assert.EqualValues(t, tc.Expected.MentionedUserIds, tc.ExplicitMentions.MentionedUserIds)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckForMentionUsers(t *testing.T) {
|
|
id1 := model.NewId()
|
|
id2 := model.NewId()
|
|
|
|
for name, tc := range map[string]struct {
|
|
Word string
|
|
Attachments []*model.SlackAttachment
|
|
Keywords map[string][]string
|
|
Expected *ExplicitMentions
|
|
}{
|
|
"Nobody": {
|
|
Word: "nothing",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{},
|
|
},
|
|
"UppercaseUser1": {
|
|
Word: "@User",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"LowercaseUser1": {
|
|
Word: "@user",
|
|
Keywords: map[string][]string{"@user": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"LowercaseUser2": {
|
|
Word: "@user2",
|
|
Keywords: map[string][]string{"@user2": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id2: true,
|
|
},
|
|
},
|
|
},
|
|
"UppercaseUser2": {
|
|
Word: "@UsEr2",
|
|
Keywords: map[string][]string{"@user2": {id2}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id2: true,
|
|
},
|
|
},
|
|
},
|
|
"HereMention": {
|
|
Word: "@here",
|
|
Expected: &ExplicitMentions{
|
|
HereMentioned: true,
|
|
},
|
|
},
|
|
"ChannelMention": {
|
|
Word: "@channel",
|
|
Expected: &ExplicitMentions{
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"AllMention": {
|
|
Word: "@all",
|
|
Expected: &ExplicitMentions{
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
"UppercaseHere": {
|
|
Word: "@HeRe",
|
|
Expected: &ExplicitMentions{
|
|
HereMentioned: true,
|
|
},
|
|
},
|
|
"UppercaseChannel": {
|
|
Word: "@ChaNNel",
|
|
Expected: &ExplicitMentions{
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"UppercaseAll": {
|
|
Word: "@ALL",
|
|
Expected: &ExplicitMentions{
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
e := &ExplicitMentions{
|
|
MentionedUserIds: make(map[string]bool),
|
|
}
|
|
e.checkForMention(tc.Word, tc.Keywords)
|
|
if tc.Expected.MentionedUserIds == nil {
|
|
tc.Expected.MentionedUserIds = make(map[string]bool)
|
|
}
|
|
assert.EqualValues(t, tc.Expected, e)
|
|
})
|
|
}
|
|
}
|
|
func TestProcessText(t *testing.T) {
|
|
id1 := model.NewId()
|
|
|
|
for name, tc := range map[string]struct {
|
|
Text string
|
|
Keywords map[string][]string
|
|
Expected *ExplicitMentions
|
|
}{
|
|
"Mention user in text": {
|
|
Text: "hello user @user1",
|
|
Keywords: map[string][]string{"@user1": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Mention user after ending a sentence with full stop": {
|
|
Text: "hello user.@user1",
|
|
Keywords: map[string][]string{"@user1": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Mention user after hyphen": {
|
|
Text: "hello user-@user1",
|
|
Keywords: map[string][]string{"@user1": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Mention user after colon": {
|
|
Text: "hello user:@user1",
|
|
Keywords: map[string][]string{"@user1": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
},
|
|
},
|
|
"Mention here after colon": {
|
|
Text: "hello all:@here",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{
|
|
HereMentioned: true,
|
|
},
|
|
},
|
|
"Mention all after hyphen": {
|
|
Text: "hello all-@all",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{
|
|
AllMentioned: true,
|
|
},
|
|
},
|
|
"Mention channel after full stop": {
|
|
Text: "hello channel.@channel",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{
|
|
ChannelMentioned: true,
|
|
},
|
|
},
|
|
"Mention other pontential users or system calls": {
|
|
Text: "hello @potentialuser and @otherpotentialuser",
|
|
Keywords: map[string][]string{},
|
|
Expected: &ExplicitMentions{
|
|
OtherPotentialMentions: []string{"potentialuser", "otherpotentialuser"},
|
|
},
|
|
},
|
|
"Mention a user and another pontential users or system calls": {
|
|
Text: "@user1, you can use @systembot to get help",
|
|
Keywords: map[string][]string{"@user1": {id1}},
|
|
Expected: &ExplicitMentions{
|
|
MentionedUserIds: map[string]bool{
|
|
id1: true,
|
|
},
|
|
OtherPotentialMentions: []string{"systembot"},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
e := &ExplicitMentions{
|
|
MentionedUserIds: make(map[string]bool),
|
|
}
|
|
if tc.Expected.MentionedUserIds == nil {
|
|
tc.Expected.MentionedUserIds = make(map[string]bool)
|
|
}
|
|
e.processText(tc.Text, tc.Keywords)
|
|
assert.EqualValues(t, tc.Expected, e)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNotificationNameFormat(t *testing.T) {
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
|
|
t.Run("show full name on", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PrivacySettings.ShowFullName = true
|
|
*cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME
|
|
})
|
|
|
|
assert.Equal(t, model.SHOW_FULLNAME, th.App.GetNotificationNameFormat(th.BasicUser))
|
|
})
|
|
|
|
t.Run("show full name off", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PrivacySettings.ShowFullName = false
|
|
*cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME
|
|
})
|
|
|
|
assert.Equal(t, model.SHOW_USERNAME, th.App.GetNotificationNameFormat(th.BasicUser))
|
|
})
|
|
}
|