Skip to content

Commit

Permalink
Fixed: Better way to select the random user
Browse files Browse the repository at this point in the history
Added: Tests for /icebreaker command
  • Loading branch information
Nils Brinkmann committed Aug 5, 2020
1 parent 446156a commit 2c41fb9
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 10 deletions.
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ github.com/mattermost/mattermost-server/v5 v5.21.0 h1:cu5XP+AQF7D+hmo00Tm2l/eH65
github.com/mattermost/mattermost-server/v5 v5.21.0/go.mod h1:wklWzfPTiBHGwdSjJAAVXX1WOba2huw3Ov4Qa4hkBno=
github.com/mattermost/mattermost-server/v5 v5.24.1 h1:dkEDqjLTtgqlQTc03kEi7N2IgBT9cZmzLs6XPylpMUo=
github.com/mattermost/mattermost-server/v5 v5.24.2 h1:dIh6Xlz257m+Y6m0VcqsTCekT+qdGPd+Usxj7Kfk980=
github.com/mattermost/mattermost-server/v5 v5.25.2 h1:A1nyhIbRgY6NoSqg5zQP47F3zt2KEDEBcQs0sy5fAmw=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
github.com/mattermost/viper v1.0.4/go.mod h1:uc5hKG9lv4/KRwPOt2c1omOyirS/UnuA2TytiZQSFHM=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "com.nilsbrinkmann.icebreaker",
"name": "Icebreaker Plugin",
"description": "This plugin creates a bot which asks random questions",
"version": "1.4.0",
"version": "1.4.1",
"min_server_version": "5.12.0",
"server": {
"executables": {
Expand Down
273 changes: 273 additions & 0 deletions server/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"math/rand"
"testing"

"github.com/mattermost/mattermost-server/v5/model"
Expand All @@ -11,6 +12,278 @@ import (
"github.com/stretchr/testify/mock"
)

func TestAskIcebreaker_fail(t *testing.T) {
t.Run("No approved questions", func(t *testing.T) {
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{},
}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
UserId: "TestUser",
}

result := plugin.executeCommandIcebreaker(args)
assert.Equal(t, "Error: There are no approved questions that I can ask. Be the first one to propose a question by using '/icebreaker add <question>'", result.Text)
})
t.Run("No users in channel", func(t *testing.T) {
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return([]*model.User{}, nil)
plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
UserId: "TestUser",
}

result := plugin.executeCommandIcebreaker(args)
assert.Equal(t, "Error: Cannot get a user to ask a question for. Note: This plugin will not ask questions to offline or DND users.", result.Text)
})
t.Run("Only bots", func(t *testing.T) {
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

users := []*model.User{
&model.User{
IsBot: true,
},
&model.User{
IsBot: true,
},
&model.User{
IsBot: true,
},
}

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return(users, nil)
plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
UserId: "TestUser",
}

result := plugin.executeCommandIcebreaker(args)
assert.Equal(t, "Error: Cannot get a user to ask a question for. Note: This plugin will not ask questions to offline or DND users.", result.Text)
})
t.Run("Only own user", func(t *testing.T) {
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

users := []*model.User{
&model.User{
Id: "TestUser",
},
}

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return(users, nil)
plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
UserId: "TestUser",
}

result := plugin.executeCommandIcebreaker(args)
assert.Equal(t, "Error: Cannot get a user to ask a question for. Note: This plugin will not ask questions to offline or DND users.", result.Text)
})
t.Run("Only offline and DND", func(t *testing.T) {
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

users := []*model.User{
&model.User{Id: "User1"},
&model.User{Id: "User2"},
}

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return(users, nil)
api.On("GetUserStatus", "User1").Return(&model.Status{Status: "offline"}, nil)
api.On("GetUserStatus", "User2").Return(&model.Status{Status: "dnd"}, nil)

plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
UserId: "TestUser",
}

result := plugin.executeCommandIcebreaker(args)
assert.Equal(t, "Error: Cannot get a user to ask a question for. Note: This plugin will not ask questions to offline or DND users.", result.Text)
})
}

func TestAskIcebreaker_success(t *testing.T) {
t.Run("Successful, first user", func(t *testing.T) {
rand.Seed(5) //seed guarantees that the loop goes through a few users before picking success_user
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

users := []*model.User{
&model.User{IsBot: true},
&model.User{Id: "TestUser"},
&model.User{Id: "User1"},
&model.User{Id: "User2"},
&model.User{Id: "SuccessUser", Username: "success_user"},
&model.User{Id: "SuccessUser2", Username: "success_user2"},
}

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return(users, nil)
api.On("GetUserStatus", "User1").Return(&model.Status{Status: "offline"}, nil)
api.On("GetUserStatus", "User2").Return(&model.Status{Status: "dnd"}, nil)
api.On("GetUserStatus", "SuccessUser").Return(&model.Status{Status: "online"}, nil)
api.On("GetUserStatus", "SuccessUser2").Return(&model.Status{Status: "online"}, nil)
api.On("CreatePost", &model.Post{
ChannelId: "TestChannel",
RootId: "TestRoot",
UserId: "",
Message: "Hey @success_user! How do you do?",
}).Return(nil, nil)

plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
RootId: "TestRoot",
UserId: "TestUser",
}

plugin.executeCommandIcebreaker(args)
})
t.Run("Successful, other user", func(t *testing.T) {
rand.Seed(4) //seed guarantees that the loop goes through a few users before picking success_user2
icebreakerData := &IceBreakerData{ApprovedQuestions: map[string]map[string][]Question{
"TestTeam": map[string][]Question{
"TestChannel": []Question{
Question{
Creator: "TestUser", Question: "How do you do?",
},
}}}}
reqBodyBytes := new(bytes.Buffer)
json.NewEncoder(reqBodyBytes).Encode(icebreakerData)

users := []*model.User{
&model.User{IsBot: true},
&model.User{Id: "TestUser"},
&model.User{Id: "User1"},
&model.User{Id: "User2"},
&model.User{Id: "SuccessUser", Username: "success_user"},
&model.User{Id: "SuccessUser2", Username: "success_user2"},
}

plugin := &Plugin{}
api := &plugintest.API{}
api.On("GetUser", mock.AnythingOfType("string")).Return(&model.User{Username: "TestUser"}, nil)
api.On("KVGet", mock.AnythingOfType("string")).Return(reqBodyBytes.Bytes(), nil)
api.On("GetUsersInChannel", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).
Return(users, nil)
api.On("GetUserStatus", "User1").Return(&model.Status{Status: "offline"}, nil)
api.On("GetUserStatus", "User2").Return(&model.Status{Status: "dnd"}, nil)
api.On("GetUserStatus", "SuccessUser").Return(&model.Status{Status: "online"}, nil)
api.On("GetUserStatus", "SuccessUser2").Return(&model.Status{Status: "online"}, nil)
api.On("CreatePost", &model.Post{
ChannelId: "TestChannel",
RootId: "TestRoot",
UserId: "",
Message: "Hey @success_user2! How do you do?",
}).Return(nil, nil)

plugin.SetAPI(api)

args := &model.CommandArgs{
Command: "/icebreaker",
ChannelId: "TestChannel",
TeamId: "TestTeam",
RootId: "TestRoot",
UserId: "TestUser",
}

plugin.executeCommandIcebreaker(args)
})
}

func TestAddIcebreaker(t *testing.T) {
t.Run("No question given w/o whitespace", func(t *testing.T) {
plugin := &Plugin{}
Expand Down
18 changes: 10 additions & 8 deletions server/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,27 @@ import (
func (p *Plugin) GetRandomUser(channelID string, userIDToIgnore string) (*model.User, *model.AppError) {
//get a random user that is not a bot
users, _ := p.API.GetUsersInChannel(channelID, "username", 0, 1000)
rand.Shuffle(len(users), func(i, j int) {
users[i], users[j] = users[j], users[i]
})

targetuser := new(model.User)
hasUserBeenFound := false
for _, user := range users {
if user.IsBot {
for len(users) > 0 { //as long as there are users left continue to search for a good candidate
randomIndex := rand.Intn(len(users))
currentUser := users[randomIndex]
if currentUser.IsBot {
users = append(users[:randomIndex], users[randomIndex+1:]...) //from https://stackoverflow.com/a/37335777/199513
continue
}
if user.Id == userIDToIgnore {
if currentUser.Id == userIDToIgnore {
users = append(users[:randomIndex], users[randomIndex+1:]...) //from https://stackoverflow.com/a/37335777/199513
continue
}
status, err := p.API.GetUserStatus(user.Id)
status, err := p.API.GetUserStatus(currentUser.Id)
if (err != nil) || (status.Status == "offline") || (status.Status == "dnd") {
users = append(users[:randomIndex], users[randomIndex+1:]...) //from https://stackoverflow.com/a/37335777/199513
continue
}

targetuser = user
targetuser = currentUser
hasUserBeenFound = true
break
}
Expand Down
2 changes: 1 addition & 1 deletion server/manifest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2c41fb9

Please sign in to comment.