From 1f6c271b3bedd6656ae7155714423b1b39a669c1 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 16 May 2018 13:43:22 -0400 Subject: MM-8708 Remove api package (#8784) * Remove api package * Remove api dependency from cmd package * Remove EnableAPIv3 setting * Update web tests * Add more websocket tests * Move some ws and oauth tests to api4 package * Move command tests into api4 package * Test fixes * Fix msg command test * Add some app file tests --- api4/api.go | 7 +- api4/apitestlib.go | 2 +- api4/commands_test.go | 449 +++++++++++++++++++++++++++++++++++++++++++++++++ api4/oauth_test.go | 413 +++++++++++++++++++++++++++++++++++++++++++++ api4/websocket_test.go | 387 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1252 insertions(+), 6 deletions(-) create mode 100644 api4/commands_test.go (limited to 'api4') diff --git a/api4/api.go b/api4/api.go index 9172391dd..918154c0d 100644 --- a/api4/api.go +++ b/api4/api.go @@ -113,7 +113,7 @@ type API struct { BaseRoutes *Routes } -func Init(a *app.App, root *mux.Router, full bool) *API { +func Init(a *app.App, root *mux.Router) *API { api := &API{ App: a, BaseRoutes: &Routes{}, @@ -231,10 +231,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404)) - // REMOVE CONDITION WHEN APIv3 REMOVED - if full { - a.InitEmailBatching() - } + a.InitEmailBatching() return api } diff --git a/api4/apitestlib.go b/api4/apitestlib.go index 0ce334154..86150e05a 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -120,7 +120,7 @@ func setupTestHelper(enterprise bool) *TestHelper { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) - Init(th.App, th.App.Srv.Router, true) + Init(th.App, th.App.Srv.Router) web.NewWeb(th.App, th.App.Srv.Router) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) th.App.Srv.Store.MarkSystemRanUnitTests() diff --git a/api4/commands_test.go b/api4/commands_test.go new file mode 100644 index 000000000..cb960c608 --- /dev/null +++ b/api4/commands_test.go @@ -0,0 +1,449 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "strings" + "testing" + "time" + + "github.com/mattermost/mattermost-server/model" +) + +func TestEchoCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel1 := th.BasicChannel + + echoTestString := "/echo test" + + if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, echoTestString)).(*model.CommandResponse); r1 == nil { + t.Fatal("Echo command failed to execute") + } + + if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/echo ")).(*model.CommandResponse); r1 == nil { + t.Fatal("Echo command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel1.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Echo command failed to send") + } +} + +func TestGroupmsgCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user1 := th.BasicUser + user2 := th.BasicUser2 + user3 := th.CreateUser() + user4 := th.CreateUser() + user5 := th.CreateUser() + user6 := th.CreateUser() + user7 := th.CreateUser() + user8 := th.CreateUser() + user9 := th.CreateUser() + th.LinkUserToTeam(user3, team) + th.LinkUserToTeam(user4, team) + + rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) + + group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id}) + + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1) { + t.Fatal("failed to create group channel") + } + + rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user3.Username+","+user4.Username+" foobar")).(*model.CommandResponse) + group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id}) + + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2) { + t.Fatal("failed to create second direct channel") + } + if result := Client.Must(Client.SearchPosts(team.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { + t.Fatal("post did not get sent to direct message") + } + + rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1) { + t.Fatal("failed to go back to existing group channel") + } + + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk,junk2 foobar")) +} + +func TestInvitePeopleCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test@example.com")).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + r2 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test1@example.com test2@example.com")).(*model.CommandResponse) + if r2 == nil { + t.Fatal("Command failed to execute") + } + + r3 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people")).(*model.CommandResponse) + if r3 == nil { + t.Fatal("Command failed to execute") + } +} + +// also used to test /open (see command_open_test.go) +func testJoinCommands(t *testing.T, alias string) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user2 := th.BasicUser2 + + channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel0 = Client.Must(Client.CreateChannel(channel0)).(*model.Channel) + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel) + Client.Must(Client.RemoveUserFromChannel(channel1.Id, th.BasicUser.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel) + Client.Must(Client.RemoveUserFromChannel(channel2.Id, th.BasicUser.Id)) + + channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) + + rs5 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel2.Name)).(*model.CommandResponse) + if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) { + t.Fatal("failed to join channel") + } + + rs6 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel3.Name)).(*model.CommandResponse) + if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) { + t.Fatal("should not have joined direct message channel") + } + + c1 := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) + + found := false + for _, c := range c1 { + if c.Id == channel2.Id { + found = true + } + } + + if !found { + t.Fatal("did not join channel") + } +} + +func TestJoinCommands(t *testing.T) { + testJoinCommands(t, "join") +} + +func TestLoadTestHelpCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test help")).(*model.CommandResponse) + if !strings.Contains(rs.Text, "Mattermost testing commands to help") { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestSetupCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test setup fuzz 1 1 1")).(*model.CommandResponse) + if rs.Text != "Created environment" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestUsersCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test users fuzz 1 2")).(*model.CommandResponse) + if rs.Text != "Added users" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestChannelsCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test channels fuzz 1 2")).(*model.CommandResponse) + if rs.Text != "Added channels" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLoadTestPostsCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true }) + + rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test posts fuzz 2 3 2")).(*model.CommandResponse) + if rs.Text != "Added posts" { + t.Fatal(rs.Text) + } + + time.Sleep(2 * time.Second) +} + +func TestLeaveCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user2 := th.BasicUser2 + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel) + Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser.Id)) + + channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} + channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel) + Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id)) + Client.Must(Client.AddChannelMember(channel2.Id, user2.Id)) + + channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel) + + rs1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/leave")).(*model.CommandResponse) + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { + t.Fatal("failed to leave open channel 1") + } + + rs2 := Client.Must(Client.ExecuteCommand(channel2.Id, "/leave")).(*model.CommandResponse) + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) { + t.Fatal("failed to leave private channel 1") + } + + _, err := Client.ExecuteCommand(channel3.Id, "/leave") + if err == nil { + t.Fatal("should fail leaving direct channel") + } + + cdata := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel) + + found := false + for _, c := range cdata { + if c.Id == channel1.Id || c.Id == channel2.Id { + found = true + } + } + + if found { + t.Fatal("did not leave right channels") + } + + for _, c := range cdata { + if c.Name == model.DEFAULT_CHANNEL { + if _, err := Client.RemoveUserFromChannel(c.Id, th.BasicUser.Id); err == nil { + t.Fatal("should have errored on leaving default channel") + } + break + } + } +} + +func TestLogoutTestCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/logout")) +} + +func TestMeCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + testString := "/me hello" + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `*hello*` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug response") + } + } +} + +func TestMsgCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + team := th.BasicTeam + user1 := th.BasicUser + user2 := th.BasicUser2 + user3 := th.CreateUser() + th.LinkUserToTeam(user3, team) + + Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)) + Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user3.Id)) + + rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { + t.Fatal("failed to create direct channel") + } + + rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user3.Username+" foobar")).(*model.CommandResponse) + if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) { + t.Fatal("failed to create second direct channel") + } + if result := Client.Must(Client.SearchPosts(th.BasicTeam.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 { + t.Fatalf("post did not get sent to direct message") + } + + rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse) + if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) { + t.Fatal("failed to go back to existing direct channel") + } + + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+th.BasicUser.Username+" foobar")) + Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg junk foobar")) +} + +func TestOpenCommands(t *testing.T) { + testJoinCommands(t, "open") +} + +func TestSearchCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/search")) +} + +func TestSettingsCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/settings")) +} + +func TestShortcutsCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/shortcuts")) +} + +func TestShrugCommand(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + channel := th.BasicChannel + + testString := "/shrug" + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(100 * time.Millisecond) + + p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList) + if len(p1.Order) != 2 { + t.Fatal("Command failed to send") + } else { + if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` { + t.Log(p1.Posts[p1.Order[0]].Message) + t.Fatal("invalid shrug response") + } + } +} + +func TestStatusCommands(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + commandAndTest(t, th, "away") + commandAndTest(t, th, "offline") + commandAndTest(t, th, "online") +} + +func commandAndTest(t *testing.T, th *TestHelper, status string) { + Client := th.Client + channel := th.BasicChannel + user := th.BasicUser + + r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/"+status)).(*model.CommandResponse) + if r1 == nil { + t.Fatal("Command failed to execute") + } + + time.Sleep(1000 * time.Millisecond) + + rstatus := Client.Must(Client.GetUserStatus(user.Id, "")).(*model.Status) + + if rstatus.Status != status { + t.Fatal("Error setting status " + status) + } +} diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 0862f13f5..8cf20ca5e 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -4,12 +4,19 @@ package api4 import ( + "encoding/base64" + "io" + "io/ioutil" "net/http" "net/url" "strconv" "testing" + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" ) func TestCreateOAuthApp(t *testing.T) { @@ -726,3 +733,409 @@ func TestDeauthorizeOAuthApp(t *testing.T) { _, resp = Client.DeauthorizeOAuthApp(rapp.Id) CheckUnauthorizedStatus(t, resp) } + +func TestOAuthAccessToken(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + + enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + + oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} + oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false }) + data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Log(resp.StatusCode) + t.Fatal("should have failed - oauth providing turned off") + } + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + authRequest := &model.AuthorizeRequest{ + ResponseType: model.AUTHCODE_RESPONSE_TYPE, + ClientId: oauthApp.Id, + RedirectUri: oauthApp.CallbackUrls[0], + Scope: "all", + State: "123", + } + + redirect, resp := Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ := url.Parse(redirect) + + Client.Logout() + + data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}} + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad grant type") + } + + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing client id") + } + data.Set("client_id", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad client id") + } + + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing client secret") + } + + data.Set("client_secret", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad client secret") + } + + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("code", "") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - missing code") + } + + data.Set("code", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - bad code") + } + + data.Set("code", rurl.Query().Get("code")) + data.Set("redirect_uri", "junk") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - non-matching redirect uri") + } + + // reset data for successful request + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("code", rurl.Query().Get("code")) + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + + token := "" + refreshToken := "" + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else { + token = rsp.AccessToken + refreshToken = rsp.RefreshToken + } + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + } + + if _, err := Client.DoApiGet("/users?page=0&per_page=100&access_token="+token, ""); err != nil { + t.Fatal(err) + } + + if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil { + t.Fatal("should have failed - no access token provided") + } + + if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil { + t.Fatal("should have failed - bad access token provided") + } + + Client.SetOAuthToken(token) + if users, resp := Client.GetUsers(0, 100, ""); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(users) == 0 { + t.Fatal("users empty - did not get results correctly") + } + } + + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("should have failed - tried to reuse auth code") + } + + data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("refresh_token", "") + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + data.Del("code") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("Should have failed - refresh token empty") + } + + data.Set("refresh_token", refreshToken) + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else if rsp.RefreshToken == refreshToken { + t.Fatal("refresh token did not update") + } + + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + Client.SetOAuthToken(rsp.AccessToken) + _, resp = Client.GetMe("") + if resp.Error != nil { + t.Fatal(resp.Error) + } + + data.Set("refresh_token", rsp.RefreshToken) + } + + if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil { + t.Fatal(resp.Error) + } else { + if len(rsp.AccessToken) == 0 { + t.Fatal("access token not returned") + } else if len(rsp.RefreshToken) == 0 { + t.Fatal("refresh token not returned") + } else if rsp.RefreshToken == refreshToken { + t.Fatal("refresh token did not update") + } + + if rsp.TokenType != model.ACCESS_TOKEN_TYPE { + t.Fatal("access token type incorrect") + } + Client.SetOAuthToken(rsp.AccessToken) + _, resp = Client.GetMe("") + if resp.Error != nil { + t.Fatal(resp.Error) + } + } + + authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1} + <-th.App.Srv.Store.OAuth().SaveAuthData(authData) + + data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) + data.Set("client_id", oauthApp.Id) + data.Set("client_secret", oauthApp.ClientSecret) + data.Set("redirect_uri", oauthApp.CallbackUrls[0]) + data.Set("code", authData.Code) + data.Del("refresh_token") + if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil { + t.Fatal("Should have failed - code is expired") + } + + Client.ClearOAuthToken() +} + +func TestOAuthComplete(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + + r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123", Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = true }) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = model.NewId() }) + + stateProps := map[string]string{} + stateProps["action"] = model.OAUTH_ACTION_LOGIN + stateProps["team_id"] = th.BasicTeam.Id + stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint + + state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true) + assert.NotNil(t, err) + closeBody(r) + + // We are going to use mattermost as the provider emulating gitlab + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) + + defaultRolePermissions := th.SaveDefaultRolePermissions() + defer func() { + th.RestoreDefaultRolePermissions(defaultRolePermissions) + }() + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID) + th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) + + oauthApp := &model.OAuthApp{ + Name: "TestApp5" + model.NewId(), + Homepage: "https://nowhere.com", + Description: "test", + CallbackUrls: []string{ + Client.Url + "/signup/" + model.SERVICE_GITLAB + "/complete", + Client.Url + "/login/" + model.SERVICE_GITLAB + "/complete", + }, + IsTrusted: true, + } + oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp) + + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = oauthApp.Id }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = oauthApp.ClientSecret }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = Client.Url + "/oauth/access_token" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = Client.ApiUrl + "/users/me" }) + + provider := &MattermostTestProvider{} + + authRequest := &model.AuthorizeRequest{ + ResponseType: model.AUTHCODE_RESPONSE_TYPE, + ClientId: oauthApp.Id, + RedirectUri: oauthApp.CallbackUrls[0], + Scope: "all", + State: "123", + } + + redirect, resp := Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ := url.Parse(redirect) + + code := rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO + delete(stateProps, "team_id") + stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint + stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id) + stateProps["redirect_to"] = "/oauth/authorize" + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider) + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + if result := <-th.App.Srv.Store.User().UpdateAuthData( + th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); result.Err != nil { + t.Fatal(result.Err) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_LOGIN + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + delete(stateProps, "action") + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } + + redirect, resp = Client.AuthorizeOAuthApp(authRequest) + CheckNoError(t, resp) + rurl, _ = url.Parse(redirect) + + code = rurl.Query().Get("code") + stateProps["action"] = model.OAUTH_ACTION_SIGNUP + state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) + if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil { + closeBody(r) + } +} + +func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) { + rq, _ := http.NewRequest("GET", url, nil) + rq.Close = true + + if len(authToken) > 0 { + rq.Header.Set(model.HEADER_AUTH, authToken) + } + + if !followRedirect { + httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + } + + if rp, err := httpClient.Do(rq); err != nil { + return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0) + } else if rp.StatusCode == 304 { + return rp, nil + } else if rp.StatusCode == 307 { + return rp, nil + } else if rp.StatusCode >= 300 { + defer closeBody(rp) + return rp, model.AppErrorFromJson(rp.Body) + } else { + return rp, nil + } +} + +func closeBody(r *http.Response) { + if r != nil && r.Body != nil { + ioutil.ReadAll(r.Body) + r.Body.Close() + } +} + +type MattermostTestProvider struct { +} + +func (m *MattermostTestProvider) GetIdentifier() string { + return model.SERVICE_GITLAB +} + +func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User { + return model.UserFromJson(data) +} + +func (m *MattermostTestProvider) GetAuthDataFromJson(data io.Reader) string { + authData := model.UserFromJson(data) + return authData.Email +} diff --git a/api4/websocket_test.go b/api4/websocket_test.go index fa40629dc..9e4c1da73 100644 --- a/api4/websocket_test.go +++ b/api4/websocket_test.go @@ -4,10 +4,16 @@ package api4 import ( + "fmt" + "net/http" + "strings" "testing" "time" + "github.com/gorilla/websocket" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" ) func TestWebSocket(t *testing.T) { @@ -71,3 +77,384 @@ func TestWebSocket(t *testing.T) { } } } + +func TestWebSocketEvent(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + omitUser := make(map[string]bool, 1) + omitUser["somerandomid"] = true + evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser) + evt1.Add("user_id", "somerandomid") + th.App.Publish(evt1) + + time.Sleep(300 * time.Millisecond) + + stop := make(chan bool) + eventHit := false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { + eventHit = true + } + case <-stop: + return + } + } + }() + + time.Sleep(400 * time.Millisecond) + + stop <- true + + if !eventHit { + t.Fatal("did not receive typing event") + } + + evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil) + th.App.Publish(evt2) + time.Sleep(300 * time.Millisecond) + + eventHit = false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_TYPING { + eventHit = true + } + case <-stop: + return + } + } + }() + + time.Sleep(400 * time.Millisecond) + + stop <- true + + if eventHit { + t.Fatal("got typing event for bad channel id") + } +} + +func TestCreateDirectChannelWithSocket(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + user2 := th.BasicUser2 + + users := make([]*model.User, 0) + users = append(users, user2) + + for i := 0; i < 10; i++ { + users = append(users, th.CreateUser()) + } + + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + wsr := <-WebSocketClient.EventChannel + if wsr.Event != model.WEBSOCKET_EVENT_HELLO { + t.Fatal("missing hello") + } + + stop := make(chan bool) + count := 0 + + go func() { + for { + select { + case wsr := <-WebSocketClient.EventChannel: + if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { + count = count + 1 + } + + case <-stop: + return + } + } + }() + + for _, user := range users { + time.Sleep(100 * time.Millisecond) + if _, resp := Client.CreateDirectChannel(th.BasicUser.Id, user.Id); resp.Error != nil { + t.Fatal("failed to create DM channel") + } + } + + time.Sleep(5000 * time.Millisecond) + + stop <- true + + if count != len(users) { + t.Fatal("We didn't get the proper amount of direct_added messages") + } + +} + +func TestWebsocketOriginSecurity(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port) + + // Should fail because origin doesn't match + _, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") + } + + // We are not a browser so we can spoof this just fine + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)}, + }) + if err != nil { + t.Fatal(err) + } + + // Should succeed now because open CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err != nil { + t.Fatal(err) + } + + // Should succeed now because matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err != nil { + t.Fatal(err) + } + + // Should fail because non-matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.evil.com"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin contain AllowCorsFrom") + } + + // Should fail because non-matching CORS + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) + _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ + "Origin": []string{"http://www.good.co"}, + }) + if err == nil { + t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") + } + + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" }) +} + +func TestWebSocketStatuses(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + Client := th.Client + WebSocketClient, err := th.CreateWebSocketClient() + if err != nil { + t.Fatal(err) + } + defer WebSocketClient.Close() + WebSocketClient.Listen() + + time.Sleep(300 * time.Millisecond) + if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { + t.Fatal("should have responded OK to authentication challenge") + } + + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + rteam, _ := Client.CreateTeam(&team) + + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser := Client.Must(Client.CreateUser(&user)).(*model.User) + th.LinkUserToTeam(ruser, rteam) + store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id)) + + user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser2 := Client.Must(Client.CreateUser(&user2)).(*model.User) + th.LinkUserToTeam(ruser2, rteam) + store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id)) + + Client.Login(user.Email, user.Password) + + th.LoginBasic2() + + WebSocketClient2, err2 := th.CreateWebSocketClient() + if err2 != nil { + t.Fatal(err2) + } + + time.Sleep(1000 * time.Millisecond) + + WebSocketClient.GetStatuses() + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + for _, status := range resp.Data { + if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE && status != model.STATUS_DND { + t.Fatalf("one of the statuses had an invalid value status=%v", status) + } + } + + if status, ok := resp.Data[th.BasicUser2.Id]; !ok { + t.Log(resp.Data) + t.Fatal("should have had user status") + } else if status != model.STATUS_ONLINE { + t.Log(status) + t.Fatal("status should have been online") + } + } + + WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + for _, status := range resp.Data { + if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { + t.Fatal("one of the statuses had an invalid value") + } + } + + if status, ok := resp.Data[th.BasicUser2.Id]; !ok { + t.Log(len(resp.Data)) + t.Fatal("should have had user status") + } else if status != model.STATUS_ONLINE { + t.Log(status) + t.Fatal("status should have been online") + } else if len(resp.Data) != 1 { + t.Fatal("only 1 status should be returned") + } + } + + WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + if len(resp.Data) != 2 { + t.Fatal("2 statuses should be returned") + } + } + + WebSocketClient.GetStatusesByIds([]string{}) + if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + t.Fatal("should have errored - empty user ids") + } + + WebSocketClient2.Close() + + th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) + + awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout + defer func() { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout }) + }() + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 }) + + time.Sleep(1500 * time.Millisecond) + + th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) + th.App.SetStatusOnline(th.BasicUser.Id, "junk", false) + + time.Sleep(1500 * time.Millisecond) + + WebSocketClient.GetStatuses() + if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { + t.Fatal(resp.Error) + } else { + if resp.SeqReply != WebSocketClient.Sequence-1 { + t.Fatal("bad sequence number") + } + + if _, ok := resp.Data[th.BasicUser2.Id]; ok { + t.Fatal("should not have had user status") + } + } + + stop := make(chan bool) + onlineHit := false + awayHit := false + + go func() { + for { + select { + case resp := <-WebSocketClient.EventChannel: + if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { + status := resp.Data["status"].(string) + if status == model.STATUS_ONLINE { + onlineHit = true + } else if status == model.STATUS_AWAY { + awayHit = true + } + } + case <-stop: + return + } + } + }() + + time.Sleep(500 * time.Millisecond) + + stop <- true + + if !onlineHit { + t.Fatal("didn't get online event") + } + if !awayHit { + t.Fatal("didn't get away event") + } + + time.Sleep(500 * time.Millisecond) + + WebSocketClient.Close() +} -- cgit v1.2.3-1-g7c22