github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/api4/post_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"net/url"
    14  	"os"
    15  	"reflect"
    16  	"sort"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  
    24  	"github.com/masterhung0112/hk_server/v5/app"
    25  	"github.com/masterhung0112/hk_server/v5/model"
    26  	"github.com/masterhung0112/hk_server/v5/plugin/plugintest/mock"
    27  	"github.com/masterhung0112/hk_server/v5/store/storetest/mocks"
    28  	"github.com/masterhung0112/hk_server/v5/utils"
    29  	"github.com/masterhung0112/hk_server/v5/utils/testutils"
    30  )
    31  
    32  func TestCreatePost(t *testing.T) {
    33  	th := Setup(t).InitBasic()
    34  	defer th.TearDown()
    35  	Client := th.Client
    36  
    37  	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a", Props: model.StringInterface{model.PROPS_ADD_CHANNEL_MEMBER: "no good"}}
    38  	rpost, resp := Client.CreatePost(post)
    39  	CheckNoError(t, resp)
    40  	CheckCreatedStatus(t, resp)
    41  
    42  	require.Equal(t, post.Message, rpost.Message, "message didn't match")
    43  	require.Equal(t, "#hashtag", rpost.Hashtags, "hashtag didn't match")
    44  	require.Empty(t, rpost.FileIds)
    45  	require.Equal(t, 0, int(rpost.EditAt), "newly created post shouldn't have EditAt set")
    46  	require.Nil(t, rpost.GetProp(model.PROPS_ADD_CHANNEL_MEMBER), "newly created post shouldn't have Props['add_channel_member'] set")
    47  
    48  	post.RootId = rpost.Id
    49  	post.ParentId = rpost.Id
    50  	_, resp = Client.CreatePost(post)
    51  	CheckNoError(t, resp)
    52  
    53  	post.RootId = "junk"
    54  	_, resp = Client.CreatePost(post)
    55  	CheckBadRequestStatus(t, resp)
    56  
    57  	post.RootId = rpost.Id
    58  	post.ParentId = "junk"
    59  	_, resp = Client.CreatePost(post)
    60  	CheckBadRequestStatus(t, resp)
    61  
    62  	post2 := &model.Post{ChannelId: th.BasicChannel2.Id, Message: "zz" + model.NewId() + "a", CreateAt: 123}
    63  	rpost2, _ := Client.CreatePost(post2)
    64  	require.NotEqual(t, post2.CreateAt, rpost2.CreateAt, "create at should not match")
    65  
    66  	t.Run("with file uploaded by same user", func(t *testing.T) {
    67  		fileResp, subResponse := Client.UploadFile([]byte("data"), th.BasicChannel.Id, "test")
    68  		CheckNoError(t, subResponse)
    69  		fileId := fileResp.FileInfos[0].Id
    70  
    71  		postWithFiles, subResponse := Client.CreatePost(&model.Post{
    72  			ChannelId: th.BasicChannel.Id,
    73  			Message:   "with files",
    74  			FileIds:   model.StringArray{fileId},
    75  		})
    76  		CheckNoError(t, subResponse)
    77  		assert.Equal(t, model.StringArray{fileId}, postWithFiles.FileIds)
    78  
    79  		actualPostWithFiles, subResponse := Client.GetPost(postWithFiles.Id, "")
    80  		CheckNoError(t, subResponse)
    81  		assert.Equal(t, model.StringArray{fileId}, actualPostWithFiles.FileIds)
    82  	})
    83  
    84  	t.Run("with file uploaded by different user", func(t *testing.T) {
    85  		fileResp, subResponse := th.SystemAdminClient.UploadFile([]byte("data"), th.BasicChannel.Id, "test")
    86  		CheckNoError(t, subResponse)
    87  		fileId := fileResp.FileInfos[0].Id
    88  
    89  		postWithFiles, subResponse := Client.CreatePost(&model.Post{
    90  			ChannelId: th.BasicChannel.Id,
    91  			Message:   "with files",
    92  			FileIds:   model.StringArray{fileId},
    93  		})
    94  		CheckNoError(t, subResponse)
    95  		assert.Empty(t, postWithFiles.FileIds)
    96  
    97  		actualPostWithFiles, subResponse := Client.GetPost(postWithFiles.Id, "")
    98  		CheckNoError(t, subResponse)
    99  		assert.Empty(t, actualPostWithFiles.FileIds)
   100  	})
   101  
   102  	t.Run("with file uploaded by nouser", func(t *testing.T) {
   103  		fileInfo, err := th.App.UploadFile(th.Context, []byte("data"), th.BasicChannel.Id, "test")
   104  		require.Nil(t, err)
   105  		fileId := fileInfo.Id
   106  
   107  		postWithFiles, subResponse := Client.CreatePost(&model.Post{
   108  			ChannelId: th.BasicChannel.Id,
   109  			Message:   "with files",
   110  			FileIds:   model.StringArray{fileId},
   111  		})
   112  		CheckNoError(t, subResponse)
   113  		assert.Equal(t, model.StringArray{fileId}, postWithFiles.FileIds)
   114  
   115  		actualPostWithFiles, subResponse := Client.GetPost(postWithFiles.Id, "")
   116  		CheckNoError(t, subResponse)
   117  		assert.Equal(t, model.StringArray{fileId}, actualPostWithFiles.FileIds)
   118  	})
   119  
   120  	t.Run("Create posts without the USE_CHANNEL_MENTIONS Permission - returns ephemeral message with mentions and no ephemeral message without mentions", func(t *testing.T) {
   121  		WebSocketClient, err := th.CreateWebSocketClient()
   122  		WebSocketClient.Listen()
   123  		require.Nil(t, err)
   124  
   125  		defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
   126  
   127  		th.RemovePermissionFromRole(model.PERMISSION_USE_CHANNEL_MENTIONS.Id, model.CHANNEL_USER_ROLE_ID)
   128  
   129  		post.RootId = rpost.Id
   130  		post.ParentId = rpost.Id
   131  		post.Message = "a post with no channel mentions"
   132  		_, resp = Client.CreatePost(post)
   133  		CheckNoError(t, resp)
   134  
   135  		// Message with no channel mentions should result in no ephemeral message
   136  		timeout := time.After(300 * time.Millisecond)
   137  		waiting := true
   138  		for waiting {
   139  			select {
   140  			case event := <-WebSocketClient.EventChannel:
   141  				require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.EventType(), "should not have ephemeral message event")
   142  			case <-timeout:
   143  				waiting = false
   144  			}
   145  		}
   146  
   147  		post.RootId = rpost.Id
   148  		post.ParentId = rpost.Id
   149  		post.Message = "a post with @channel"
   150  		_, resp = Client.CreatePost(post)
   151  		CheckNoError(t, resp)
   152  
   153  		post.RootId = rpost.Id
   154  		post.ParentId = rpost.Id
   155  		post.Message = "a post with @all"
   156  		_, resp = Client.CreatePost(post)
   157  		CheckNoError(t, resp)
   158  
   159  		post.RootId = rpost.Id
   160  		post.ParentId = rpost.Id
   161  		post.Message = "a post with @here"
   162  		_, resp = Client.CreatePost(post)
   163  		CheckNoError(t, resp)
   164  
   165  		timeout = time.After(600 * time.Millisecond)
   166  		eventsToGo := 3 // 3 Posts created with @ mentions should result in 3 websocket events
   167  		for eventsToGo > 0 {
   168  			select {
   169  			case event := <-WebSocketClient.EventChannel:
   170  				if event.Event == model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
   171  					require.Equal(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.Event)
   172  					eventsToGo = eventsToGo - 1
   173  				}
   174  			case <-timeout:
   175  				require.Fail(t, "Should have received ephemeral message event and not timedout")
   176  				eventsToGo = 0
   177  			}
   178  		}
   179  	})
   180  
   181  	post.RootId = ""
   182  	post.ParentId = ""
   183  	post.Type = model.POST_SYSTEM_GENERIC
   184  	_, resp = Client.CreatePost(post)
   185  	CheckBadRequestStatus(t, resp)
   186  
   187  	post.Type = ""
   188  	post.RootId = rpost2.Id
   189  	post.ParentId = rpost2.Id
   190  	_, resp = Client.CreatePost(post)
   191  	CheckBadRequestStatus(t, resp)
   192  
   193  	post.RootId = ""
   194  	post.ParentId = ""
   195  	post.ChannelId = "junk"
   196  	_, resp = Client.CreatePost(post)
   197  	CheckForbiddenStatus(t, resp)
   198  
   199  	post.ChannelId = model.NewId()
   200  	_, resp = Client.CreatePost(post)
   201  	CheckForbiddenStatus(t, resp)
   202  
   203  	r, err := Client.DoApiPost("/posts", "garbage")
   204  	require.NotNil(t, err)
   205  	require.Equal(t, http.StatusBadRequest, r.StatusCode)
   206  
   207  	Client.Logout()
   208  	_, resp = Client.CreatePost(post)
   209  	CheckUnauthorizedStatus(t, resp)
   210  
   211  	post.ChannelId = th.BasicChannel.Id
   212  	post.CreateAt = 123
   213  	rpost, resp = th.SystemAdminClient.CreatePost(post)
   214  	CheckNoError(t, resp)
   215  	require.Equal(t, post.CreateAt, rpost.CreateAt, "create at should match")
   216  }
   217  
   218  func TestCreatePostEphemeral(t *testing.T) {
   219  	th := Setup(t).InitBasic()
   220  	defer th.TearDown()
   221  	Client := th.SystemAdminClient
   222  
   223  	ephemeralPost := &model.PostEphemeral{
   224  		UserID: th.BasicUser2.Id,
   225  		Post:   &model.Post{ChannelId: th.BasicChannel.Id, Message: "a" + model.NewId() + "a", Props: model.StringInterface{model.PROPS_ADD_CHANNEL_MEMBER: "no good"}},
   226  	}
   227  
   228  	rpost, resp := Client.CreatePostEphemeral(ephemeralPost)
   229  	CheckNoError(t, resp)
   230  	CheckCreatedStatus(t, resp)
   231  	require.Equal(t, ephemeralPost.Post.Message, rpost.Message, "message didn't match")
   232  	require.Equal(t, 0, int(rpost.EditAt), "newly created ephemeral post shouldn't have EditAt set")
   233  
   234  	r, err := Client.DoApiPost("/posts/ephemeral", "garbage")
   235  	require.NotNil(t, err)
   236  	require.Equal(t, http.StatusBadRequest, r.StatusCode)
   237  
   238  	Client.Logout()
   239  	_, resp = Client.CreatePostEphemeral(ephemeralPost)
   240  	CheckUnauthorizedStatus(t, resp)
   241  
   242  	Client = th.Client
   243  	_, resp = Client.CreatePostEphemeral(ephemeralPost)
   244  	CheckForbiddenStatus(t, resp)
   245  }
   246  
   247  func testCreatePostWithOutgoingHook(
   248  	t *testing.T,
   249  	hookContentType, expectedContentType, message, triggerWord string,
   250  	fileIds []string,
   251  	triggerWhen int,
   252  	commentPostType bool,
   253  ) {
   254  	th := Setup(t).InitBasic()
   255  	defer th.TearDown()
   256  	user := th.SystemAdminUser
   257  	team := th.BasicTeam
   258  	channel := th.BasicChannel
   259  
   260  	enableOutgoingWebhooks := *th.App.Config().ServiceSettings.EnableOutgoingWebhooks
   261  	allowedUntrustedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
   262  	defer func() {
   263  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingWebhooks })
   264  		th.App.UpdateConfig(func(cfg *model.Config) {
   265  			*cfg.ServiceSettings.AllowedUntrustedInternalConnections = allowedUntrustedInternalConnections
   266  		})
   267  	}()
   268  
   269  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
   270  	th.App.UpdateConfig(func(cfg *model.Config) {
   271  		*cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
   272  	})
   273  
   274  	var hook *model.OutgoingWebhook
   275  	var post *model.Post
   276  
   277  	// Create a test server that is the target of the outgoing webhook. It will
   278  	// validate the webhook body fields and write to the success channel on
   279  	// success/failure.
   280  	success := make(chan bool)
   281  	wait := make(chan bool, 1)
   282  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   283  		<-wait
   284  
   285  		requestContentType := r.Header.Get("Content-Type")
   286  		if requestContentType != expectedContentType {
   287  			t.Logf("Content-Type is %s, should be %s", requestContentType, expectedContentType)
   288  			success <- false
   289  			return
   290  		}
   291  
   292  		expectedPayload := &model.OutgoingWebhookPayload{
   293  			Token:       hook.Token,
   294  			TeamId:      hook.TeamId,
   295  			TeamDomain:  team.Name,
   296  			ChannelId:   post.ChannelId,
   297  			ChannelName: channel.Name,
   298  			Timestamp:   post.CreateAt,
   299  			UserId:      post.UserId,
   300  			UserName:    user.Username,
   301  			PostId:      post.Id,
   302  			Text:        post.Message,
   303  			TriggerWord: triggerWord,
   304  			FileIds:     strings.Join(post.FileIds, ","),
   305  		}
   306  
   307  		// depending on the Content-Type, we expect to find a JSON or form encoded payload
   308  		if requestContentType == "application/json" {
   309  			decoder := json.NewDecoder(r.Body)
   310  			o := &model.OutgoingWebhookPayload{}
   311  			decoder.Decode(&o)
   312  
   313  			if !reflect.DeepEqual(expectedPayload, o) {
   314  				t.Logf("JSON payload is %+v, should be %+v", o, expectedPayload)
   315  				success <- false
   316  				return
   317  			}
   318  		} else {
   319  			err := r.ParseForm()
   320  			if err != nil {
   321  				t.Logf("Error parsing form: %q", err)
   322  				success <- false
   323  				return
   324  			}
   325  
   326  			expectedFormValues, _ := url.ParseQuery(expectedPayload.ToFormValues())
   327  
   328  			if !reflect.DeepEqual(expectedFormValues, r.Form) {
   329  				t.Logf("Form values are: %q\n, should be: %q\n", r.Form, expectedFormValues)
   330  				success <- false
   331  				return
   332  			}
   333  		}
   334  
   335  		respPostType := "" //if is empty or post will do a normal post.
   336  		if commentPostType {
   337  			respPostType = model.OUTGOING_HOOK_RESPONSE_TYPE_COMMENT
   338  		}
   339  
   340  		outGoingHookResponse := &model.OutgoingWebhookResponse{
   341  			Text:         model.NewString("some test text"),
   342  			Username:     "TestCommandServer",
   343  			IconURL:      "https://www.mattermost.org/wp-content/uploads/2016/04/icon.png",
   344  			Type:         "custom_as",
   345  			ResponseType: respPostType,
   346  		}
   347  
   348  		fmt.Fprintf(w, outGoingHookResponse.ToJson())
   349  		success <- true
   350  	}))
   351  	defer ts.Close()
   352  
   353  	// create an outgoing webhook, passing it the test server URL
   354  	var triggerWords []string
   355  	if triggerWord != "" {
   356  		triggerWords = []string{triggerWord}
   357  	}
   358  
   359  	hook = &model.OutgoingWebhook{
   360  		ChannelId:    channel.Id,
   361  		TeamId:       team.Id,
   362  		ContentType:  hookContentType,
   363  		TriggerWords: triggerWords,
   364  		TriggerWhen:  triggerWhen,
   365  		CallbackURLs: []string{ts.URL},
   366  	}
   367  
   368  	hook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook)
   369  	CheckNoError(t, resp)
   370  
   371  	// create a post to trigger the webhook
   372  	post = &model.Post{
   373  		ChannelId: channel.Id,
   374  		Message:   message,
   375  		FileIds:   fileIds,
   376  	}
   377  
   378  	post, resp = th.SystemAdminClient.CreatePost(post)
   379  	CheckNoError(t, resp)
   380  
   381  	wait <- true
   382  
   383  	// We wait for the test server to write to the success channel and we make
   384  	// the test fail if that doesn't happen before the timeout.
   385  	select {
   386  	case ok := <-success:
   387  		require.True(t, ok, "Test server did send an invalid webhook.")
   388  	case <-time.After(time.Second):
   389  		require.FailNow(t, "Timeout, test server did not send the webhook.")
   390  	}
   391  
   392  	if commentPostType {
   393  		time.Sleep(time.Millisecond * 100)
   394  		postList, resp := th.SystemAdminClient.GetPostThread(post.Id, "", false)
   395  		CheckNoError(t, resp)
   396  		require.Equal(t, post.Id, postList.Order[0], "wrong order")
   397  
   398  		_, ok := postList.Posts[post.Id]
   399  		require.True(t, ok, "should have had post")
   400  		require.Len(t, postList.Posts, 2, "should have 2 posts")
   401  	}
   402  }
   403  
   404  func TestCreatePostWithOutgoingHook_form_urlencoded(t *testing.T) {
   405  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
   406  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
   407  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
   408  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
   409  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, true)
   410  	testCreatePostWithOutgoingHook(t, "application/x-www-form-urlencoded", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, true)
   411  }
   412  
   413  func TestCreatePostWithOutgoingHook_json(t *testing.T) {
   414  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, false)
   415  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsStartsWith, false)
   416  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
   417  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
   418  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerword lorem ipsum", "triggerword", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, true)
   419  	testCreatePostWithOutgoingHook(t, "application/json", "application/json", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1"}, app.TriggerwordsStartsWith, true)
   420  }
   421  
   422  // hooks created before we added the ContentType field should be considered as
   423  // application/x-www-form-urlencoded
   424  func TestCreatePostWithOutgoingHook_no_content_type(t *testing.T) {
   425  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, false)
   426  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsStartsWith, false)
   427  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, false)
   428  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerwordaaazzz lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsStartsWith, false)
   429  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "triggerword", []string{"file_id_1"}, app.TriggerwordsExactMatch, true)
   430  	testCreatePostWithOutgoingHook(t, "", "application/x-www-form-urlencoded", "triggerword lorem ipsum", "", []string{"file_id_1, file_id_2"}, app.TriggerwordsExactMatch, true)
   431  }
   432  
   433  func TestCreatePostPublic(t *testing.T) {
   434  	th := Setup(t).InitBasic()
   435  	defer th.TearDown()
   436  	Client := th.Client
   437  
   438  	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
   439  
   440  	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_USER_ROLE_ID}
   441  
   442  	ruser, resp := Client.CreateUser(&user)
   443  	CheckNoError(t, resp)
   444  
   445  	Client.Login(user.Email, user.Password)
   446  
   447  	_, resp = Client.CreatePost(post)
   448  	CheckForbiddenStatus(t, resp)
   449  
   450  	th.App.UpdateUserRoles(ruser.Id, model.SYSTEM_USER_ROLE_ID+" "+model.SYSTEM_POST_ALL_PUBLIC_ROLE_ID, false)
   451  	th.App.Srv().InvalidateAllCaches()
   452  
   453  	Client.Login(user.Email, user.Password)
   454  
   455  	_, resp = Client.CreatePost(post)
   456  	CheckNoError(t, resp)
   457  
   458  	post.ChannelId = th.BasicPrivateChannel.Id
   459  	_, resp = Client.CreatePost(post)
   460  	CheckForbiddenStatus(t, resp)
   461  
   462  	th.App.UpdateUserRoles(ruser.Id, model.SYSTEM_USER_ROLE_ID, false)
   463  	th.App.JoinUserToTeam(th.Context, th.BasicTeam, ruser, "")
   464  	th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, model.TEAM_USER_ROLE_ID+" "+model.TEAM_POST_ALL_PUBLIC_ROLE_ID)
   465  	th.App.Srv().InvalidateAllCaches()
   466  
   467  	Client.Login(user.Email, user.Password)
   468  
   469  	post.ChannelId = th.BasicPrivateChannel.Id
   470  	_, resp = Client.CreatePost(post)
   471  	CheckForbiddenStatus(t, resp)
   472  
   473  	post.ChannelId = th.BasicChannel.Id
   474  	_, resp = Client.CreatePost(post)
   475  	CheckNoError(t, resp)
   476  }
   477  
   478  func TestCreatePostAll(t *testing.T) {
   479  	th := Setup(t).InitBasic()
   480  	defer th.TearDown()
   481  	Client := th.Client
   482  
   483  	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "#hashtag a" + model.NewId() + "a"}
   484  
   485  	user := model.User{Email: th.GenerateTestEmail(), Nickname: "Joram Wilander", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_USER_ROLE_ID}
   486  
   487  	directChannel, _ := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
   488  
   489  	ruser, resp := Client.CreateUser(&user)
   490  	CheckNoError(t, resp)
   491  
   492  	Client.Login(user.Email, user.Password)
   493  
   494  	_, resp = Client.CreatePost(post)
   495  	CheckForbiddenStatus(t, resp)
   496  
   497  	th.App.UpdateUserRoles(ruser.Id, model.SYSTEM_USER_ROLE_ID+" "+model.SYSTEM_POST_ALL_ROLE_ID, false)
   498  	th.App.Srv().InvalidateAllCaches()
   499  
   500  	Client.Login(user.Email, user.Password)
   501  
   502  	_, resp = Client.CreatePost(post)
   503  	CheckNoError(t, resp)
   504  
   505  	post.ChannelId = th.BasicPrivateChannel.Id
   506  	_, resp = Client.CreatePost(post)
   507  	CheckNoError(t, resp)
   508  
   509  	post.ChannelId = directChannel.Id
   510  	_, resp = Client.CreatePost(post)
   511  	CheckNoError(t, resp)
   512  
   513  	th.App.UpdateUserRoles(ruser.Id, model.SYSTEM_USER_ROLE_ID, false)
   514  	th.App.JoinUserToTeam(th.Context, th.BasicTeam, ruser, "")
   515  	th.App.UpdateTeamMemberRoles(th.BasicTeam.Id, ruser.Id, model.TEAM_USER_ROLE_ID+" "+model.TEAM_POST_ALL_ROLE_ID)
   516  	th.App.Srv().InvalidateAllCaches()
   517  
   518  	Client.Login(user.Email, user.Password)
   519  
   520  	post.ChannelId = th.BasicPrivateChannel.Id
   521  	_, resp = Client.CreatePost(post)
   522  	CheckNoError(t, resp)
   523  
   524  	post.ChannelId = th.BasicChannel.Id
   525  	_, resp = Client.CreatePost(post)
   526  	CheckNoError(t, resp)
   527  
   528  	post.ChannelId = directChannel.Id
   529  	_, resp = Client.CreatePost(post)
   530  	CheckForbiddenStatus(t, resp)
   531  }
   532  
   533  func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
   534  	th := Setup(t).InitBasic()
   535  	defer th.TearDown()
   536  	Client := th.Client
   537  
   538  	WebSocketClient, err := th.CreateWebSocketClient()
   539  	require.Nil(t, err)
   540  	WebSocketClient.Listen()
   541  
   542  	inChannelUser := th.CreateUser()
   543  	th.LinkUserToTeam(inChannelUser, th.BasicTeam)
   544  	th.App.AddUserToChannel(inChannelUser, th.BasicChannel, false)
   545  
   546  	post1 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "@" + inChannelUser.Username}
   547  	_, resp := Client.CreatePost(post1)
   548  	CheckNoError(t, resp)
   549  	CheckCreatedStatus(t, resp)
   550  
   551  	timeout := time.After(300 * time.Millisecond)
   552  	waiting := true
   553  	for waiting {
   554  		select {
   555  		case event := <-WebSocketClient.EventChannel:
   556  			require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.EventType(), "should not have ephemeral message event")
   557  		case <-timeout:
   558  			waiting = false
   559  		}
   560  	}
   561  
   562  	outOfChannelUser := th.CreateUser()
   563  	th.LinkUserToTeam(outOfChannelUser, th.BasicTeam)
   564  
   565  	post2 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "@" + outOfChannelUser.Username}
   566  	_, resp = Client.CreatePost(post2)
   567  	CheckNoError(t, resp)
   568  	CheckCreatedStatus(t, resp)
   569  
   570  	timeout = time.After(300 * time.Millisecond)
   571  	waiting = true
   572  	for waiting {
   573  		select {
   574  		case event := <-WebSocketClient.EventChannel:
   575  			if event.EventType() != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
   576  				// Ignore any other events
   577  				continue
   578  			}
   579  
   580  			wpost := model.PostFromJson(strings.NewReader(event.GetData()["post"].(string)))
   581  
   582  			acm, ok := wpost.GetProp(model.PROPS_ADD_CHANNEL_MEMBER).(map[string]interface{})
   583  			require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props")
   584  			require.True(t, acm["post_id"] != nil, "should not be nil")
   585  			require.True(t, acm["user_ids"] != nil, "should not be nil")
   586  			require.True(t, acm["usernames"] != nil, "should not be nil")
   587  			waiting = false
   588  		case <-timeout:
   589  			require.FailNow(t, "timed out waiting for ephemeral message event")
   590  		}
   591  	}
   592  }
   593  
   594  func TestCreatePostCheckOnlineStatus(t *testing.T) {
   595  	th := Setup(t).InitBasic()
   596  	defer th.TearDown()
   597  
   598  	api := Init(th.App, th.Server.Router)
   599  	session, _ := th.App.GetSession(th.Client.AuthToken)
   600  
   601  	cli := th.CreateClient()
   602  	_, loginResp := cli.Login(th.BasicUser2.Username, th.BasicUser2.Password)
   603  	require.Nil(t, loginResp.Error)
   604  
   605  	wsClient, err := th.CreateWebSocketClientWithClient(cli)
   606  	require.Nil(t, err)
   607  	defer wsClient.Close()
   608  
   609  	wsClient.Listen()
   610  
   611  	waitForEvent := func(isSetOnline bool) {
   612  		timeout := time.After(5 * time.Second)
   613  		for {
   614  			select {
   615  			case ev := <-wsClient.EventChannel:
   616  				if ev.EventType() == model.WEBSOCKET_EVENT_POSTED {
   617  					assert.True(t, ev.GetData()["set_online"].(bool) == isSetOnline)
   618  					return
   619  				}
   620  			case <-timeout:
   621  				// We just skip the test instead of failing because waiting for more than 5 seconds
   622  				// to get a response does not make sense, and it will unnecessarily slow down
   623  				// the tests further in an already congested CI environment.
   624  				t.Skip("timed out waiting for event")
   625  			}
   626  		}
   627  	}
   628  
   629  	handler := api.ApiHandler(createPost)
   630  	resp := httptest.NewRecorder()
   631  	post := &model.Post{
   632  		ChannelId: th.BasicChannel.Id,
   633  		Message:   "some message",
   634  	}
   635  
   636  	req := httptest.NewRequest("POST", "/api/v4/posts?set_online=false", strings.NewReader(post.ToJson()))
   637  	req.Header.Set(model.HEADER_AUTH, "Bearer "+session.Token)
   638  
   639  	handler.ServeHTTP(resp, req)
   640  	assert.Equal(t, http.StatusCreated, resp.Code)
   641  	waitForEvent(false)
   642  
   643  	_, err = th.App.GetStatus(th.BasicUser.Id)
   644  	require.NotNil(t, err)
   645  	assert.Equal(t, "app.status.get.missing.app_error", err.Id)
   646  
   647  	req = httptest.NewRequest("POST", "/api/v4/posts", strings.NewReader(post.ToJson()))
   648  	req.Header.Set(model.HEADER_AUTH, "Bearer "+session.Token)
   649  
   650  	handler.ServeHTTP(resp, req)
   651  	assert.Equal(t, http.StatusCreated, resp.Code)
   652  	waitForEvent(true)
   653  
   654  	st, err := th.App.GetStatus(th.BasicUser.Id)
   655  	require.Nil(t, err)
   656  	assert.Equal(t, "online", st.Status)
   657  }
   658  
   659  func TestUpdatePost(t *testing.T) {
   660  	th := Setup(t).InitBasic()
   661  	defer th.TearDown()
   662  	Client := th.Client
   663  	channel := th.BasicChannel
   664  
   665  	th.App.Srv().SetLicense(model.NewTestLicense())
   666  
   667  	fileIds := make([]string, 3)
   668  	data, err := testutils.ReadTestFile("test.png")
   669  	require.NoError(t, err)
   670  	for i := 0; i < len(fileIds); i++ {
   671  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
   672  		CheckNoError(t, resp)
   673  		fileIds[i] = fileResp.FileInfos[0].Id
   674  	}
   675  
   676  	rpost, appErr := th.App.CreatePost(th.Context, &model.Post{
   677  		UserId:    th.BasicUser.Id,
   678  		ChannelId: channel.Id,
   679  		Message:   "zz" + model.NewId() + "a",
   680  		FileIds:   fileIds,
   681  	}, channel, false, true)
   682  	require.Nil(t, appErr)
   683  
   684  	assert.Equal(t, rpost.Message, rpost.Message, "full name didn't match")
   685  	assert.EqualValues(t, 0, rpost.EditAt, "Newly created post shouldn't have EditAt set")
   686  	assert.Equal(t, model.StringArray(fileIds), rpost.FileIds, "FileIds should have been set")
   687  
   688  	t.Run("same message, fewer files", func(t *testing.T) {
   689  		msg := "zz" + model.NewId() + " update post"
   690  		rpost.Message = msg
   691  		rpost.UserId = ""
   692  
   693  		rupost, resp := Client.UpdatePost(rpost.Id, &model.Post{
   694  			Id:      rpost.Id,
   695  			Message: rpost.Message,
   696  			FileIds: fileIds[0:2], // one fewer file id
   697  		})
   698  		CheckNoError(t, resp)
   699  
   700  		assert.Equal(t, rupost.Message, msg, "failed to updates")
   701  		assert.NotEqual(t, 0, rupost.EditAt, "EditAt not updated for post")
   702  		assert.Equal(t, model.StringArray(fileIds), rupost.FileIds, "FileIds should have not have been updated")
   703  
   704  		actual, resp := Client.GetPost(rpost.Id, "")
   705  		CheckNoError(t, resp)
   706  
   707  		assert.Equal(t, actual.Message, msg, "failed to updates")
   708  		assert.NotEqual(t, 0, actual.EditAt, "EditAt not updated for post")
   709  		assert.Equal(t, model.StringArray(fileIds), actual.FileIds, "FileIds should have not have been updated")
   710  	})
   711  
   712  	t.Run("new message, invalid props", func(t *testing.T) {
   713  		msg1 := "#hashtag a" + model.NewId() + " update post again"
   714  		rpost.Message = msg1
   715  		rpost.AddProp(model.PROPS_ADD_CHANNEL_MEMBER, "no good")
   716  		rrupost, resp := Client.UpdatePost(rpost.Id, rpost)
   717  		CheckNoError(t, resp)
   718  
   719  		assert.Equal(t, msg1, rrupost.Message, "failed to update message")
   720  		assert.Equal(t, "#hashtag", rrupost.Hashtags, "failed to update hashtags")
   721  		assert.Nil(t, rrupost.GetProp(model.PROPS_ADD_CHANNEL_MEMBER), "failed to sanitize Props['add_channel_member'], should be nil")
   722  
   723  		actual, resp := Client.GetPost(rpost.Id, "")
   724  		CheckNoError(t, resp)
   725  
   726  		assert.Equal(t, msg1, actual.Message, "failed to update message")
   727  		assert.Equal(t, "#hashtag", actual.Hashtags, "failed to update hashtags")
   728  		assert.Nil(t, actual.GetProp(model.PROPS_ADD_CHANNEL_MEMBER), "failed to sanitize Props['add_channel_member'], should be nil")
   729  	})
   730  
   731  	t.Run("join/leave post", func(t *testing.T) {
   732  		rpost2, err := th.App.CreatePost(th.Context, &model.Post{
   733  			ChannelId: channel.Id,
   734  			Message:   "zz" + model.NewId() + "a",
   735  			Type:      model.POST_JOIN_LEAVE,
   736  			UserId:    th.BasicUser.Id,
   737  		}, channel, false, true)
   738  		require.Nil(t, err)
   739  
   740  		up2 := &model.Post{
   741  			Id:        rpost2.Id,
   742  			ChannelId: channel.Id,
   743  			Message:   "zz" + model.NewId() + " update post 2",
   744  		}
   745  		_, resp := Client.UpdatePost(rpost2.Id, up2)
   746  		CheckBadRequestStatus(t, resp)
   747  	})
   748  
   749  	rpost3, appErr := th.App.CreatePost(th.Context, &model.Post{
   750  		ChannelId: channel.Id,
   751  		Message:   "zz" + model.NewId() + "a",
   752  		UserId:    th.BasicUser.Id,
   753  	}, channel, false, true)
   754  	require.Nil(t, appErr)
   755  
   756  	t.Run("new message, add files", func(t *testing.T) {
   757  		up3 := &model.Post{
   758  			Id:        rpost3.Id,
   759  			ChannelId: channel.Id,
   760  			Message:   "zz" + model.NewId() + " update post 3",
   761  			FileIds:   fileIds[0:2],
   762  		}
   763  		rrupost3, resp := Client.UpdatePost(rpost3.Id, up3)
   764  		CheckNoError(t, resp)
   765  		assert.Empty(t, rrupost3.FileIds)
   766  
   767  		actual, resp := Client.GetPost(rpost.Id, "")
   768  		CheckNoError(t, resp)
   769  		assert.Equal(t, model.StringArray(fileIds), actual.FileIds)
   770  	})
   771  
   772  	t.Run("add slack attachments", func(t *testing.T) {
   773  		up4 := &model.Post{
   774  			Id:        rpost3.Id,
   775  			ChannelId: channel.Id,
   776  			Message:   "zz" + model.NewId() + " update post 3",
   777  		}
   778  		up4.AddProp("attachments", []model.SlackAttachment{
   779  			{
   780  				Text: "Hello World",
   781  			},
   782  		})
   783  		rrupost3, resp := Client.UpdatePost(rpost3.Id, up4)
   784  		CheckNoError(t, resp)
   785  		assert.NotEqual(t, rpost3.EditAt, rrupost3.EditAt)
   786  		assert.NotEqual(t, rpost3.Attachments(), rrupost3.Attachments())
   787  	})
   788  
   789  	t.Run("logged out", func(t *testing.T) {
   790  		Client.Logout()
   791  		_, resp := Client.UpdatePost(rpost.Id, rpost)
   792  		CheckUnauthorizedStatus(t, resp)
   793  	})
   794  
   795  	t.Run("different user", func(t *testing.T) {
   796  		th.LoginBasic2()
   797  		_, resp := Client.UpdatePost(rpost.Id, rpost)
   798  		CheckForbiddenStatus(t, resp)
   799  
   800  		Client.Logout()
   801  	})
   802  
   803  	t.Run("different user, but team admin", func(t *testing.T) {
   804  		th.LoginTeamAdmin()
   805  		_, resp := Client.UpdatePost(rpost.Id, rpost)
   806  		CheckForbiddenStatus(t, resp)
   807  
   808  		Client.Logout()
   809  	})
   810  
   811  	t.Run("different user, but system admin", func(t *testing.T) {
   812  		_, resp := th.SystemAdminClient.UpdatePost(rpost.Id, rpost)
   813  		CheckNoError(t, resp)
   814  	})
   815  }
   816  
   817  func TestUpdateOthersPostInDirectMessageChannel(t *testing.T) {
   818  	// This test checks that a sysadmin with the "EDIT_OTHERS_POSTS" permission can edit someone else's post in a
   819  	// channel without a team (DM/GM). This indirectly checks for the proper cascading all the way to system-wide roles
   820  	// on the user object of permissions based on a post in a channel with no team ID.
   821  	th := Setup(t).InitBasic()
   822  	defer th.TearDown()
   823  
   824  	dmChannel := th.CreateDmChannel(th.SystemAdminUser)
   825  
   826  	post := &model.Post{
   827  		Message:       "asd",
   828  		ChannelId:     dmChannel.Id,
   829  		PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
   830  		UserId:        th.BasicUser.Id,
   831  		CreateAt:      0,
   832  	}
   833  
   834  	post, resp := th.Client.CreatePost(post)
   835  	CheckNoError(t, resp)
   836  
   837  	post.Message = "changed"
   838  	post, resp = th.SystemAdminClient.UpdatePost(post.Id, post)
   839  	CheckNoError(t, resp)
   840  }
   841  
   842  func TestPatchPost(t *testing.T) {
   843  	th := Setup(t).InitBasic()
   844  	defer th.TearDown()
   845  	Client := th.Client
   846  	channel := th.BasicChannel
   847  
   848  	th.App.Srv().SetLicense(model.NewTestLicense())
   849  
   850  	fileIds := make([]string, 3)
   851  	data, err := testutils.ReadTestFile("test.png")
   852  	require.NoError(t, err)
   853  	for i := 0; i < len(fileIds); i++ {
   854  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
   855  		CheckNoError(t, resp)
   856  		fileIds[i] = fileResp.FileInfos[0].Id
   857  	}
   858  
   859  	post := &model.Post{
   860  		ChannelId:    channel.Id,
   861  		IsPinned:     true,
   862  		Message:      "#hashtag a message",
   863  		Props:        model.StringInterface{"channel_header": "old_header"},
   864  		FileIds:      fileIds[0:2],
   865  		HasReactions: true,
   866  	}
   867  	post, _ = Client.CreatePost(post)
   868  
   869  	var rpost *model.Post
   870  	t.Run("new message, props, files, HasReactions bit", func(t *testing.T) {
   871  		patch := &model.PostPatch{}
   872  
   873  		patch.IsPinned = model.NewBool(false)
   874  		patch.Message = model.NewString("#otherhashtag other message")
   875  		patch.Props = &model.StringInterface{"channel_header": "new_header"}
   876  		patchFileIds := model.StringArray(fileIds) // one extra file
   877  		patch.FileIds = &patchFileIds
   878  		patch.HasReactions = model.NewBool(false)
   879  
   880  		var resp *model.Response
   881  		rpost, resp = Client.PatchPost(post.Id, patch)
   882  		CheckNoError(t, resp)
   883  
   884  		assert.False(t, rpost.IsPinned, "IsPinned did not update properly")
   885  		assert.Equal(t, "#otherhashtag other message", rpost.Message, "Message did not update properly")
   886  		assert.Equal(t, *patch.Props, rpost.GetProps(), "Props did not update properly")
   887  		assert.Equal(t, "#otherhashtag", rpost.Hashtags, "Message did not update properly")
   888  		assert.Equal(t, model.StringArray(fileIds[0:2]), rpost.FileIds, "FileIds should not update")
   889  		assert.False(t, rpost.HasReactions, "HasReactions did not update properly")
   890  	})
   891  
   892  	t.Run("add slack attachments", func(t *testing.T) {
   893  		patch2 := &model.PostPatch{}
   894  		attachments := []model.SlackAttachment{
   895  			{
   896  				Text: "Hello World",
   897  			},
   898  		}
   899  		patch2.Props = &model.StringInterface{"attachments": attachments}
   900  
   901  		rpost2, resp := Client.PatchPost(post.Id, patch2)
   902  		CheckNoError(t, resp)
   903  		assert.NotEmpty(t, rpost2.GetProp("attachments"))
   904  		assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
   905  	})
   906  
   907  	t.Run("invalid requests", func(t *testing.T) {
   908  		r, err := Client.DoApiPut("/posts/"+post.Id+"/patch", "garbage")
   909  		require.EqualError(t, err, ": Invalid or missing post in request body., ")
   910  		require.Equal(t, http.StatusBadRequest, r.StatusCode, "wrong status code")
   911  
   912  		patch := &model.PostPatch{}
   913  		_, resp := Client.PatchPost("junk", patch)
   914  		CheckBadRequestStatus(t, resp)
   915  	})
   916  
   917  	t.Run("unknown post", func(t *testing.T) {
   918  		patch := &model.PostPatch{}
   919  		_, resp := Client.PatchPost(GenerateTestId(), patch)
   920  		CheckForbiddenStatus(t, resp)
   921  	})
   922  
   923  	t.Run("logged out", func(t *testing.T) {
   924  		Client.Logout()
   925  		patch := &model.PostPatch{}
   926  		_, resp := Client.PatchPost(post.Id, patch)
   927  		CheckUnauthorizedStatus(t, resp)
   928  	})
   929  
   930  	t.Run("different user", func(t *testing.T) {
   931  		th.LoginBasic2()
   932  		patch := &model.PostPatch{}
   933  		_, resp := Client.PatchPost(post.Id, patch)
   934  		CheckForbiddenStatus(t, resp)
   935  	})
   936  
   937  	t.Run("different user, but team admin", func(t *testing.T) {
   938  		th.LoginTeamAdmin()
   939  		patch := &model.PostPatch{}
   940  		_, resp := Client.PatchPost(post.Id, patch)
   941  		CheckForbiddenStatus(t, resp)
   942  	})
   943  
   944  	t.Run("different user, but system admin", func(t *testing.T) {
   945  		patch := &model.PostPatch{}
   946  		_, resp := th.SystemAdminClient.PatchPost(post.Id, patch)
   947  		CheckNoError(t, resp)
   948  	})
   949  
   950  	t.Run("edit others posts permission can function independently of edit own post", func(t *testing.T) {
   951  		th.LoginBasic2()
   952  		patch := &model.PostPatch{}
   953  		_, resp := Client.PatchPost(post.Id, patch)
   954  		CheckForbiddenStatus(t, resp)
   955  
   956  		// Add permission to edit others'
   957  		defer th.RestoreDefaultRolePermissions(th.SaveDefaultRolePermissions())
   958  		th.RemovePermissionFromRole(model.PERMISSION_EDIT_POST.Id, model.CHANNEL_USER_ROLE_ID)
   959  		th.AddPermissionToRole(model.PERMISSION_EDIT_OTHERS_POSTS.Id, model.CHANNEL_USER_ROLE_ID)
   960  
   961  		_, resp = Client.PatchPost(post.Id, patch)
   962  		CheckNoError(t, resp)
   963  	})
   964  }
   965  
   966  func TestPinPost(t *testing.T) {
   967  	th := Setup(t).InitBasic()
   968  	defer th.TearDown()
   969  	Client := th.Client
   970  
   971  	post := th.BasicPost
   972  	pass, resp := Client.PinPost(post.Id)
   973  	CheckNoError(t, resp)
   974  
   975  	require.True(t, pass, "should have passed")
   976  	rpost, err := th.App.GetSinglePost(post.Id)
   977  	require.Nil(t, err)
   978  	require.True(t, rpost.IsPinned, "failed to pin post")
   979  
   980  	pass, resp = Client.PinPost("junk")
   981  	CheckBadRequestStatus(t, resp)
   982  	require.False(t, pass, "should have failed")
   983  
   984  	_, resp = Client.PinPost(GenerateTestId())
   985  	CheckForbiddenStatus(t, resp)
   986  
   987  	t.Run("unable-to-pin-post-in-read-only-town-square", func(t *testing.T) {
   988  		townSquareIsReadOnly := *th.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly
   989  		th.App.Srv().SetLicense(model.NewTestLicense())
   990  		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true })
   991  
   992  		defer th.App.Srv().RemoveLicense()
   993  		defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = townSquareIsReadOnly })
   994  
   995  		channel, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, true)
   996  		assert.Nil(t, err)
   997  		adminPost := th.CreatePostWithClient(th.SystemAdminClient, channel)
   998  
   999  		_, resp = Client.PinPost(adminPost.Id)
  1000  		CheckForbiddenStatus(t, resp)
  1001  	})
  1002  
  1003  	Client.Logout()
  1004  	_, resp = Client.PinPost(post.Id)
  1005  	CheckUnauthorizedStatus(t, resp)
  1006  
  1007  	_, resp = th.SystemAdminClient.PinPost(post.Id)
  1008  	CheckNoError(t, resp)
  1009  }
  1010  
  1011  func TestUnpinPost(t *testing.T) {
  1012  	th := Setup(t).InitBasic()
  1013  	defer th.TearDown()
  1014  	Client := th.Client
  1015  
  1016  	pinnedPost := th.CreatePinnedPost()
  1017  	pass, resp := Client.UnpinPost(pinnedPost.Id)
  1018  	CheckNoError(t, resp)
  1019  	require.True(t, pass, "should have passed")
  1020  
  1021  	rpost, err := th.App.GetSinglePost(pinnedPost.Id)
  1022  	require.Nil(t, err)
  1023  	require.False(t, rpost.IsPinned)
  1024  
  1025  	pass, resp = Client.UnpinPost("junk")
  1026  	CheckBadRequestStatus(t, resp)
  1027  	require.False(t, pass, "should have failed")
  1028  
  1029  	_, resp = Client.UnpinPost(GenerateTestId())
  1030  	CheckForbiddenStatus(t, resp)
  1031  
  1032  	Client.Logout()
  1033  	_, resp = Client.UnpinPost(pinnedPost.Id)
  1034  	CheckUnauthorizedStatus(t, resp)
  1035  
  1036  	_, resp = th.SystemAdminClient.UnpinPost(pinnedPost.Id)
  1037  	CheckNoError(t, resp)
  1038  }
  1039  
  1040  func TestGetPostsForChannel(t *testing.T) {
  1041  	th := Setup(t).InitBasic()
  1042  	defer th.TearDown()
  1043  	Client := th.Client
  1044  
  1045  	post1 := th.CreatePost()
  1046  	post2 := th.CreatePost()
  1047  	post3 := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", RootId: post1.Id}
  1048  	post3, _ = Client.CreatePost(post3)
  1049  
  1050  	time.Sleep(300 * time.Millisecond)
  1051  	since := model.GetMillis()
  1052  	time.Sleep(300 * time.Millisecond)
  1053  
  1054  	post4 := th.CreatePost()
  1055  
  1056  	th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
  1057  		posts, resp := c.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "", false)
  1058  		CheckNoError(t, resp)
  1059  		require.Equal(t, post4.Id, posts.Order[0], "wrong order")
  1060  		require.Equal(t, post3.Id, posts.Order[1], "wrong order")
  1061  		require.Equal(t, post2.Id, posts.Order[2], "wrong order")
  1062  		require.Equal(t, post1.Id, posts.Order[3], "wrong order")
  1063  
  1064  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 0, 3, resp.Etag, false)
  1065  		CheckEtag(t, posts, resp)
  1066  
  1067  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 0, 3, "", false)
  1068  		CheckNoError(t, resp)
  1069  		require.Len(t, posts.Order, 3, "wrong number returned")
  1070  
  1071  		_, ok := posts.Posts[post3.Id]
  1072  		require.True(t, ok, "missing comment")
  1073  		_, ok = posts.Posts[post1.Id]
  1074  		require.True(t, ok, "missing root post")
  1075  
  1076  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 1, 1, "", false)
  1077  		CheckNoError(t, resp)
  1078  		require.Equal(t, post3.Id, posts.Order[0], "wrong order")
  1079  
  1080  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 10000, 10000, "", false)
  1081  		CheckNoError(t, resp)
  1082  		require.Empty(t, posts.Order, "should be no posts")
  1083  	})
  1084  
  1085  	post5 := th.CreatePost()
  1086  
  1087  	th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
  1088  		posts, resp := c.GetPostsSince(th.BasicChannel.Id, since, false)
  1089  		CheckNoError(t, resp)
  1090  		require.Len(t, posts.Posts, 2, "should return 2 posts")
  1091  
  1092  		// "since" query to return empty NextPostId and PrevPostId
  1093  		require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1094  		require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1095  
  1096  		found := make([]bool, 2)
  1097  		for _, p := range posts.Posts {
  1098  			require.LessOrEqual(t, since, p.CreateAt, "bad create at for post returned")
  1099  
  1100  			if p.Id == post4.Id {
  1101  				found[0] = true
  1102  			} else if p.Id == post5.Id {
  1103  				found[1] = true
  1104  			}
  1105  		}
  1106  		for _, f := range found {
  1107  			require.True(t, f, "missing post")
  1108  		}
  1109  
  1110  		_, resp = c.GetPostsForChannel("", 0, 60, "", false)
  1111  		CheckBadRequestStatus(t, resp)
  1112  
  1113  		_, resp = c.GetPostsForChannel("junk", 0, 60, "", false)
  1114  		CheckBadRequestStatus(t, resp)
  1115  	})
  1116  
  1117  	_, resp := Client.GetPostsForChannel(model.NewId(), 0, 60, "", false)
  1118  	CheckForbiddenStatus(t, resp)
  1119  
  1120  	Client.Logout()
  1121  	_, resp = Client.GetPostsForChannel(model.NewId(), 0, 60, "", false)
  1122  	CheckUnauthorizedStatus(t, resp)
  1123  
  1124  	// more tests for next_post_id, prev_post_id, and order
  1125  	// There are 12 posts composed of first 2 system messages and 10 created posts
  1126  	Client.Login(th.BasicUser.Email, th.BasicUser.Password)
  1127  	th.CreatePost() // post6
  1128  	post7 := th.CreatePost()
  1129  	post8 := th.CreatePost()
  1130  	th.CreatePost() // post9
  1131  	post10 := th.CreatePost()
  1132  
  1133  	var posts *model.PostList
  1134  	th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
  1135  		// get the system post IDs posted before the created posts above
  1136  		posts, resp = c.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "", false)
  1137  		systemPostId1 := posts.Order[1]
  1138  
  1139  		// similar to '/posts'
  1140  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "", false)
  1141  		CheckNoError(t, resp)
  1142  		require.Len(t, posts.Order, 12, "expected 12 posts")
  1143  		require.Equal(t, post10.Id, posts.Order[0], "posts not in order")
  1144  		require.Equal(t, systemPostId1, posts.Order[11], "posts not in order")
  1145  		require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1146  		require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1147  
  1148  		// similar to '/posts?per_page=3'
  1149  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 0, 3, "", false)
  1150  		CheckNoError(t, resp)
  1151  		require.Len(t, posts.Order, 3, "expected 3 posts")
  1152  		require.Equal(t, post10.Id, posts.Order[0], "posts not in order")
  1153  		require.Equal(t, post8.Id, posts.Order[2], "should return 3 posts and match order")
  1154  		require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1155  		require.Equal(t, post7.Id, posts.PrevPostId, "should return post7.Id as PrevPostId")
  1156  
  1157  		// similar to '/posts?per_page=3&page=1'
  1158  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 1, 3, "", false)
  1159  		CheckNoError(t, resp)
  1160  		require.Len(t, posts.Order, 3, "expected 3 posts")
  1161  		require.Equal(t, post7.Id, posts.Order[0], "posts not in order")
  1162  		require.Equal(t, post5.Id, posts.Order[2], "posts not in order")
  1163  		require.Equal(t, post8.Id, posts.NextPostId, "should return post8.Id as NextPostId")
  1164  		require.Equal(t, post4.Id, posts.PrevPostId, "should return post4.Id as PrevPostId")
  1165  
  1166  		// similar to '/posts?per_page=3&page=2'
  1167  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 2, 3, "", false)
  1168  		CheckNoError(t, resp)
  1169  		require.Len(t, posts.Order, 3, "expected 3 posts")
  1170  		require.Equal(t, post4.Id, posts.Order[0], "posts not in order")
  1171  		require.Equal(t, post2.Id, posts.Order[2], "should return 3 posts and match order")
  1172  		require.Equal(t, post5.Id, posts.NextPostId, "should return post5.Id as NextPostId")
  1173  		require.Equal(t, post1.Id, posts.PrevPostId, "should return post1.Id as PrevPostId")
  1174  
  1175  		// similar to '/posts?per_page=3&page=3'
  1176  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 3, 3, "", false)
  1177  		CheckNoError(t, resp)
  1178  		require.Len(t, posts.Order, 3, "expected 3 posts")
  1179  		require.Equal(t, post1.Id, posts.Order[0], "posts not in order")
  1180  		require.Equal(t, systemPostId1, posts.Order[2], "should return 3 posts and match order")
  1181  		require.Equal(t, post2.Id, posts.NextPostId, "should return post2.Id as NextPostId")
  1182  		require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1183  
  1184  		// similar to '/posts?per_page=3&page=4'
  1185  		posts, resp = c.GetPostsForChannel(th.BasicChannel.Id, 4, 3, "", false)
  1186  		CheckNoError(t, resp)
  1187  		require.Empty(t, posts.Order, "should return 0 post")
  1188  		require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1189  		require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1190  	})
  1191  }
  1192  
  1193  func TestGetFlaggedPostsForUser(t *testing.T) {
  1194  	th := Setup(t).InitBasic()
  1195  	defer th.TearDown()
  1196  	Client := th.Client
  1197  	user := th.BasicUser
  1198  	team1 := th.BasicTeam
  1199  	channel1 := th.BasicChannel
  1200  	post1 := th.CreatePost()
  1201  	channel2 := th.CreatePublicChannel()
  1202  	post2 := th.CreatePostWithClient(Client, channel2)
  1203  
  1204  	preference := model.Preference{
  1205  		UserId:   user.Id,
  1206  		Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
  1207  		Name:     post1.Id,
  1208  		Value:    "true",
  1209  	}
  1210  	_, resp := Client.UpdatePreferences(user.Id, &model.Preferences{preference})
  1211  	CheckNoError(t, resp)
  1212  	preference.Name = post2.Id
  1213  	_, resp = Client.UpdatePreferences(user.Id, &model.Preferences{preference})
  1214  	CheckNoError(t, resp)
  1215  
  1216  	opl := model.NewPostList()
  1217  	opl.AddPost(post1)
  1218  	opl.AddOrder(post1.Id)
  1219  
  1220  	rpl, resp := Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
  1221  	CheckNoError(t, resp)
  1222  
  1223  	require.Len(t, rpl.Posts, 1, "should have returned 1 post")
  1224  	require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
  1225  
  1226  	rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 1)
  1227  	CheckNoError(t, resp)
  1228  	require.Len(t, rpl.Posts, 1, "should have returned 1 post")
  1229  
  1230  	rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 1, 1)
  1231  	CheckNoError(t, resp)
  1232  	require.Empty(t, rpl.Posts)
  1233  
  1234  	rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, GenerateTestId(), 0, 10)
  1235  	CheckNoError(t, resp)
  1236  	require.Empty(t, rpl.Posts)
  1237  
  1238  	rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, "junk", 0, 10)
  1239  	CheckBadRequestStatus(t, resp)
  1240  	require.Nil(t, rpl)
  1241  
  1242  	opl.AddPost(post2)
  1243  	opl.AddOrder(post2.Id)
  1244  
  1245  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
  1246  	CheckNoError(t, resp)
  1247  	require.Len(t, rpl.Posts, 2, "should have returned 2 posts")
  1248  	require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
  1249  
  1250  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 1)
  1251  	CheckNoError(t, resp)
  1252  	require.Len(t, rpl.Posts, 1, "should have returned 1 post")
  1253  
  1254  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 1, 1)
  1255  	CheckNoError(t, resp)
  1256  	require.Len(t, rpl.Posts, 1, "should have returned 1 post")
  1257  
  1258  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 1000, 10)
  1259  	CheckNoError(t, resp)
  1260  	require.Empty(t, rpl.Posts)
  1261  
  1262  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, GenerateTestId(), 0, 10)
  1263  	CheckNoError(t, resp)
  1264  	require.Empty(t, rpl.Posts)
  1265  
  1266  	rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, "junk", 0, 10)
  1267  	CheckBadRequestStatus(t, resp)
  1268  	require.Nil(t, rpl)
  1269  
  1270  	channel3 := th.CreatePrivateChannel()
  1271  	post4 := th.CreatePostWithClient(Client, channel3)
  1272  
  1273  	preference.Name = post4.Id
  1274  	Client.UpdatePreferences(user.Id, &model.Preferences{preference})
  1275  
  1276  	opl.AddPost(post4)
  1277  	opl.AddOrder(post4.Id)
  1278  
  1279  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
  1280  	CheckNoError(t, resp)
  1281  	require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
  1282  	require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
  1283  
  1284  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 2)
  1285  	CheckNoError(t, resp)
  1286  	require.Len(t, rpl.Posts, 2, "should have returned 2 posts")
  1287  
  1288  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 2, 2)
  1289  	CheckNoError(t, resp)
  1290  	require.Len(t, rpl.Posts, 1, "should have returned 1 post")
  1291  
  1292  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 1000, 10)
  1293  	CheckNoError(t, resp)
  1294  	require.Empty(t, rpl.Posts)
  1295  
  1296  	channel4 := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE)
  1297  	post5 := th.CreatePostWithClient(th.SystemAdminClient, channel4)
  1298  
  1299  	preference.Name = post5.Id
  1300  	_, resp = Client.UpdatePreferences(user.Id, &model.Preferences{preference})
  1301  	CheckForbiddenStatus(t, resp)
  1302  
  1303  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
  1304  	CheckNoError(t, resp)
  1305  	require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
  1306  	require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
  1307  
  1308  	th.AddUserToChannel(user, channel4)
  1309  	_, resp = Client.UpdatePreferences(user.Id, &model.Preferences{preference})
  1310  	CheckNoError(t, resp)
  1311  
  1312  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
  1313  	CheckNoError(t, resp)
  1314  
  1315  	opl.AddPost(post5)
  1316  	opl.AddOrder(post5.Id)
  1317  	require.Len(t, rpl.Posts, 4, "should have returned 4 posts")
  1318  	require.Equal(t, opl.Posts, rpl.Posts, "posts should have matched")
  1319  
  1320  	err := th.App.RemoveUserFromChannel(th.Context, user.Id, "", channel4)
  1321  	assert.Nil(t, err, "unable to remove user from channel")
  1322  
  1323  	rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
  1324  	CheckNoError(t, resp)
  1325  
  1326  	opl2 := model.NewPostList()
  1327  	opl2.AddPost(post1)
  1328  	opl2.AddOrder(post1.Id)
  1329  	opl2.AddPost(post2)
  1330  	opl2.AddOrder(post2.Id)
  1331  	opl2.AddPost(post4)
  1332  	opl2.AddOrder(post4.Id)
  1333  
  1334  	require.Len(t, rpl.Posts, 3, "should have returned 3 posts")
  1335  	require.Equal(t, opl2.Posts, rpl.Posts, "posts should have matched")
  1336  
  1337  	_, resp = Client.GetFlaggedPostsForUser("junk", 0, 10)
  1338  	CheckBadRequestStatus(t, resp)
  1339  
  1340  	_, resp = Client.GetFlaggedPostsForUser(GenerateTestId(), 0, 10)
  1341  	CheckForbiddenStatus(t, resp)
  1342  
  1343  	Client.Logout()
  1344  
  1345  	_, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
  1346  	CheckUnauthorizedStatus(t, resp)
  1347  
  1348  	_, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
  1349  	CheckUnauthorizedStatus(t, resp)
  1350  
  1351  	_, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
  1352  	CheckUnauthorizedStatus(t, resp)
  1353  
  1354  	_, resp = th.SystemAdminClient.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
  1355  	CheckNoError(t, resp)
  1356  
  1357  	_, resp = th.SystemAdminClient.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
  1358  	CheckNoError(t, resp)
  1359  
  1360  	_, resp = th.SystemAdminClient.GetFlaggedPostsForUser(user.Id, 0, 10)
  1361  	CheckNoError(t, resp)
  1362  
  1363  	mockStore := mocks.Store{}
  1364  	mockPostStore := mocks.PostStore{}
  1365  	mockPostStore.On("GetFlaggedPosts", mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("int")).Return(nil, errors.New("some-error"))
  1366  	mockPostStore.On("ClearCaches").Return()
  1367  	mockStore.On("Team").Return(th.App.Srv().Store.Team())
  1368  	mockStore.On("Channel").Return(th.App.Srv().Store.Channel())
  1369  	mockStore.On("User").Return(th.App.Srv().Store.User())
  1370  	mockStore.On("Scheme").Return(th.App.Srv().Store.Scheme())
  1371  	mockStore.On("Post").Return(&mockPostStore)
  1372  	mockStore.On("FileInfo").Return(th.App.Srv().Store.FileInfo())
  1373  	mockStore.On("Webhook").Return(th.App.Srv().Store.Webhook())
  1374  	mockStore.On("System").Return(th.App.Srv().Store.System())
  1375  	mockStore.On("License").Return(th.App.Srv().Store.License())
  1376  	mockStore.On("Role").Return(th.App.Srv().Store.Role())
  1377  	mockStore.On("Close").Return(nil)
  1378  	th.App.Srv().Store = &mockStore
  1379  
  1380  	_, resp = th.SystemAdminClient.GetFlaggedPostsForUser(user.Id, 0, 10)
  1381  	CheckInternalErrorStatus(t, resp)
  1382  }
  1383  
  1384  func TestGetPostsBefore(t *testing.T) {
  1385  	th := Setup(t).InitBasic()
  1386  	defer th.TearDown()
  1387  	Client := th.Client
  1388  
  1389  	post1 := th.CreatePost()
  1390  	post2 := th.CreatePost()
  1391  	post3 := th.CreatePost()
  1392  	post4 := th.CreatePost()
  1393  	post5 := th.CreatePost()
  1394  
  1395  	posts, resp := Client.GetPostsBefore(th.BasicChannel.Id, post3.Id, 0, 100, "", false)
  1396  	CheckNoError(t, resp)
  1397  
  1398  	found := make([]bool, 2)
  1399  	for _, p := range posts.Posts {
  1400  		if p.Id == post1.Id {
  1401  			found[0] = true
  1402  		} else if p.Id == post2.Id {
  1403  			found[1] = true
  1404  		}
  1405  
  1406  		require.NotEqual(t, post4.Id, p.Id, "returned posts after")
  1407  		require.NotEqual(t, post5.Id, p.Id, "returned posts after")
  1408  	}
  1409  
  1410  	for _, f := range found {
  1411  		require.True(t, f, "missing post")
  1412  	}
  1413  
  1414  	require.Equal(t, post3.Id, posts.NextPostId, "should match NextPostId")
  1415  	require.Equal(t, "", posts.PrevPostId, "should match empty PrevPostId")
  1416  
  1417  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post4.Id, 1, 1, "", false)
  1418  	CheckNoError(t, resp)
  1419  	require.Len(t, posts.Posts, 1, "too many posts returned")
  1420  	require.Equal(t, post2.Id, posts.Order[0], "should match returned post")
  1421  	require.Equal(t, post3.Id, posts.NextPostId, "should match NextPostId")
  1422  	require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
  1423  
  1424  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, "junk", 1, 1, "", false)
  1425  	CheckBadRequestStatus(t, resp)
  1426  
  1427  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 0, 3, "", false)
  1428  	CheckNoError(t, resp)
  1429  	require.Len(t, posts.Posts, 3, "should match length of posts returned")
  1430  	require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
  1431  	require.Equal(t, post2.Id, posts.Order[2], "should match returned post")
  1432  	require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
  1433  	require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
  1434  
  1435  	// get the system post IDs posted before the created posts above
  1436  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "", false)
  1437  	CheckNoError(t, resp)
  1438  	systemPostId2 := posts.Order[0]
  1439  	systemPostId1 := posts.Order[1]
  1440  
  1441  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 1, 3, "", false)
  1442  	CheckNoError(t, resp)
  1443  	require.Len(t, posts.Posts, 3, "should match length of posts returned")
  1444  	require.Equal(t, post1.Id, posts.Order[0], "should match returned post")
  1445  	require.Equal(t, systemPostId2, posts.Order[1], "should match returned post")
  1446  	require.Equal(t, systemPostId1, posts.Order[2], "should match returned post")
  1447  	require.Equal(t, post2.Id, posts.NextPostId, "should match NextPostId")
  1448  	require.Equal(t, "", posts.PrevPostId, "should return empty PrevPostId")
  1449  
  1450  	// more tests for next_post_id, prev_post_id, and order
  1451  	// There are 12 posts composed of first 2 system messages and 10 created posts
  1452  	post6 := th.CreatePost()
  1453  	th.CreatePost() // post7
  1454  	post8 := th.CreatePost()
  1455  	post9 := th.CreatePost()
  1456  	th.CreatePost() // post10
  1457  
  1458  	// similar to '/posts?before=post9'
  1459  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 60, "", false)
  1460  	CheckNoError(t, resp)
  1461  	require.Len(t, posts.Order, 10, "expected 10 posts")
  1462  	require.Equal(t, post8.Id, posts.Order[0], "posts not in order")
  1463  	require.Equal(t, systemPostId1, posts.Order[9], "posts not in order")
  1464  	require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
  1465  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1466  
  1467  	// similar to '/posts?before=post9&per_page=3'
  1468  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 3, "", false)
  1469  	CheckNoError(t, resp)
  1470  	require.Len(t, posts.Order, 3, "expected 3 posts")
  1471  	require.Equal(t, post8.Id, posts.Order[0], "posts not in order")
  1472  	require.Equal(t, post6.Id, posts.Order[2], "should return 3 posts and match order")
  1473  	require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
  1474  	require.Equal(t, post5.Id, posts.PrevPostId, "should return post5.Id as PrevPostId")
  1475  
  1476  	// similar to '/posts?before=post9&per_page=3&page=1'
  1477  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 1, 3, "", false)
  1478  	CheckNoError(t, resp)
  1479  	require.Len(t, posts.Order, 3, "expected 3 posts")
  1480  	require.Equal(t, post5.Id, posts.Order[0], "posts not in order")
  1481  	require.Equal(t, post3.Id, posts.Order[2], "posts not in order")
  1482  	require.Equal(t, post6.Id, posts.NextPostId, "should return post6.Id as NextPostId")
  1483  	require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
  1484  
  1485  	// similar to '/posts?before=post9&per_page=3&page=2'
  1486  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 2, 3, "", false)
  1487  	CheckNoError(t, resp)
  1488  	require.Len(t, posts.Order, 3, "expected 3 posts")
  1489  	require.Equal(t, post2.Id, posts.Order[0], "posts not in order")
  1490  	require.Equal(t, systemPostId2, posts.Order[2], "posts not in order")
  1491  	require.Equal(t, post3.Id, posts.NextPostId, "should return post3.Id as NextPostId")
  1492  	require.Equal(t, systemPostId1, posts.PrevPostId, "should return systemPostId1 as PrevPostId")
  1493  
  1494  	// similar to '/posts?before=post1&per_page=3'
  1495  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 3, "", false)
  1496  	CheckNoError(t, resp)
  1497  	require.Len(t, posts.Order, 2, "expected 2 posts")
  1498  	require.Equal(t, systemPostId2, posts.Order[0], "posts not in order")
  1499  	require.Equal(t, systemPostId1, posts.Order[1], "posts not in order")
  1500  	require.Equal(t, post1.Id, posts.NextPostId, "should return post1.Id as NextPostId")
  1501  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1502  
  1503  	// similar to '/posts?before=systemPostId1'
  1504  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 0, 60, "", false)
  1505  	CheckNoError(t, resp)
  1506  	require.Empty(t, posts.Order, "should return 0 post")
  1507  	require.Equal(t, systemPostId1, posts.NextPostId, "should return systemPostId1 as NextPostId")
  1508  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1509  
  1510  	// similar to '/posts?before=systemPostId1&per_page=60&page=1'
  1511  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 1, 60, "", false)
  1512  	CheckNoError(t, resp)
  1513  	require.Empty(t, posts.Order, "should return 0 posts")
  1514  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1515  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1516  
  1517  	// similar to '/posts?before=non-existent-post'
  1518  	nonExistentPostId := model.NewId()
  1519  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, nonExistentPostId, 0, 60, "", false)
  1520  	CheckNoError(t, resp)
  1521  	require.Empty(t, posts.Order, "should return 0 post")
  1522  	require.Equal(t, nonExistentPostId, posts.NextPostId, "should return nonExistentPostId as NextPostId")
  1523  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1524  }
  1525  
  1526  func TestGetPostsAfter(t *testing.T) {
  1527  	th := Setup(t).InitBasic()
  1528  	defer th.TearDown()
  1529  	Client := th.Client
  1530  
  1531  	post1 := th.CreatePost()
  1532  	post2 := th.CreatePost()
  1533  	post3 := th.CreatePost()
  1534  	post4 := th.CreatePost()
  1535  	post5 := th.CreatePost()
  1536  
  1537  	posts, resp := Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 0, 100, "", false)
  1538  	CheckNoError(t, resp)
  1539  
  1540  	found := make([]bool, 2)
  1541  	for _, p := range posts.Posts {
  1542  		if p.Id == post4.Id {
  1543  			found[0] = true
  1544  		} else if p.Id == post5.Id {
  1545  			found[1] = true
  1546  		}
  1547  		require.NotEqual(t, post1.Id, p.Id, "returned posts before")
  1548  		require.NotEqual(t, post2.Id, p.Id, "returned posts before")
  1549  	}
  1550  
  1551  	for _, f := range found {
  1552  		require.True(t, f, "missing post")
  1553  	}
  1554  	require.Equal(t, "", posts.NextPostId, "should match empty NextPostId")
  1555  	require.Equal(t, post3.Id, posts.PrevPostId, "should match PrevPostId")
  1556  
  1557  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 1, "", false)
  1558  	CheckNoError(t, resp)
  1559  	require.Len(t, posts.Posts, 1, "too many posts returned")
  1560  	require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
  1561  	require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
  1562  	require.Equal(t, post3.Id, posts.PrevPostId, "should match PrevPostId")
  1563  
  1564  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, "junk", 1, 1, "", false)
  1565  	CheckBadRequestStatus(t, resp)
  1566  
  1567  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 0, 3, "", false)
  1568  	CheckNoError(t, resp)
  1569  	require.Len(t, posts.Posts, 3, "should match length of posts returned")
  1570  	require.Equal(t, post4.Id, posts.Order[0], "should match returned post")
  1571  	require.Equal(t, post2.Id, posts.Order[2], "should match returned post")
  1572  	require.Equal(t, post5.Id, posts.NextPostId, "should match NextPostId")
  1573  	require.Equal(t, post1.Id, posts.PrevPostId, "should match PrevPostId")
  1574  
  1575  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 1, 3, "", false)
  1576  	CheckNoError(t, resp)
  1577  	require.Len(t, posts.Posts, 1, "should match length of posts returned")
  1578  	require.Equal(t, post5.Id, posts.Order[0], "should match returned post")
  1579  	require.Equal(t, "", posts.NextPostId, "should match NextPostId")
  1580  	require.Equal(t, post4.Id, posts.PrevPostId, "should match PrevPostId")
  1581  
  1582  	// more tests for next_post_id, prev_post_id, and order
  1583  	// There are 12 posts composed of first 2 system messages and 10 created posts
  1584  	post6 := th.CreatePost()
  1585  	th.CreatePost() // post7
  1586  	post8 := th.CreatePost()
  1587  	post9 := th.CreatePost()
  1588  	post10 := th.CreatePost()
  1589  
  1590  	// similar to '/posts?after=post2'
  1591  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 60, "", false)
  1592  	CheckNoError(t, resp)
  1593  	require.Len(t, posts.Order, 8, "expected 8 posts")
  1594  	require.Equal(t, post10.Id, posts.Order[0], "should match order")
  1595  	require.Equal(t, post3.Id, posts.Order[7], "should match order")
  1596  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1597  	require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
  1598  
  1599  	// similar to '/posts?after=post2&per_page=3'
  1600  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 3, "", false)
  1601  	CheckNoError(t, resp)
  1602  	require.Len(t, posts.Order, 3, "expected 3 posts")
  1603  	require.Equal(t, post5.Id, posts.Order[0], "should match order")
  1604  	require.Equal(t, post3.Id, posts.Order[2], "should return 3 posts and match order")
  1605  	require.Equal(t, post6.Id, posts.NextPostId, "should return post6.Id as NextPostId")
  1606  	require.Equal(t, post2.Id, posts.PrevPostId, "should return post2.Id as PrevPostId")
  1607  
  1608  	// similar to '/posts?after=post2&per_page=3&page=1'
  1609  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 3, "", false)
  1610  	CheckNoError(t, resp)
  1611  	require.Len(t, posts.Order, 3, "expected 3 posts")
  1612  	require.Equal(t, post8.Id, posts.Order[0], "should match order")
  1613  	require.Equal(t, post6.Id, posts.Order[2], "should match order")
  1614  	require.Equal(t, post9.Id, posts.NextPostId, "should return post9.Id as NextPostId")
  1615  	require.Equal(t, post5.Id, posts.PrevPostId, "should return post5.Id as PrevPostId")
  1616  
  1617  	// similar to '/posts?after=post2&per_page=3&page=2'
  1618  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 2, 3, "", false)
  1619  	CheckNoError(t, resp)
  1620  	require.Len(t, posts.Order, 2, "expected 2 posts")
  1621  	require.Equal(t, post10.Id, posts.Order[0], "should match order")
  1622  	require.Equal(t, post9.Id, posts.Order[1], "should match order")
  1623  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1624  	require.Equal(t, post8.Id, posts.PrevPostId, "should return post8.Id as PrevPostId")
  1625  
  1626  	// similar to '/posts?after=post10'
  1627  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 0, 60, "", false)
  1628  	CheckNoError(t, resp)
  1629  	require.Empty(t, posts.Order, "should return 0 post")
  1630  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1631  	require.Equal(t, post10.Id, posts.PrevPostId, "should return post10.Id as PrevPostId")
  1632  
  1633  	// similar to '/posts?after=post10&page=1'
  1634  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 1, 60, "", false)
  1635  	CheckNoError(t, resp)
  1636  	require.Empty(t, posts.Order, "should return 0 post")
  1637  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1638  	require.Equal(t, "", posts.PrevPostId, "should return an empty PrevPostId")
  1639  
  1640  	// similar to '/posts?after=non-existent-post'
  1641  	nonExistentPostId := model.NewId()
  1642  	posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, nonExistentPostId, 0, 60, "", false)
  1643  	CheckNoError(t, resp)
  1644  	require.Empty(t, posts.Order, "should return 0 post")
  1645  	require.Equal(t, "", posts.NextPostId, "should return an empty NextPostId")
  1646  	require.Equal(t, nonExistentPostId, posts.PrevPostId, "should return nonExistentPostId as PrevPostId")
  1647  }
  1648  
  1649  func TestGetPostsForChannelAroundLastUnread(t *testing.T) {
  1650  	th := Setup(t).InitBasic()
  1651  	defer th.TearDown()
  1652  	Client := th.Client
  1653  	userId := th.BasicUser.Id
  1654  	channelId := th.BasicChannel.Id
  1655  
  1656  	// 12 posts = 2 systems posts + 10 created posts below
  1657  	post1 := th.CreatePost()
  1658  	post2 := th.CreatePost()
  1659  	post3 := th.CreatePost()
  1660  	post4 := th.CreatePost()
  1661  	post5 := th.CreatePost()
  1662  	replyPost := &model.Post{ChannelId: channelId, Message: model.NewId(), RootId: post4.Id, ParentId: post4.Id}
  1663  	post6, resp := Client.CreatePost(replyPost)
  1664  	CheckNoError(t, resp)
  1665  	post7, resp := Client.CreatePost(replyPost)
  1666  	CheckNoError(t, resp)
  1667  	post8, resp := Client.CreatePost(replyPost)
  1668  	CheckNoError(t, resp)
  1669  	post9, resp := Client.CreatePost(replyPost)
  1670  	CheckNoError(t, resp)
  1671  	post10, resp := Client.CreatePost(replyPost)
  1672  	CheckNoError(t, resp)
  1673  
  1674  	postIdNames := map[string]string{
  1675  		post1.Id:  "post1",
  1676  		post2.Id:  "post2",
  1677  		post3.Id:  "post3",
  1678  		post4.Id:  "post4",
  1679  		post5.Id:  "post5",
  1680  		post6.Id:  "post6 (reply to post4)",
  1681  		post7.Id:  "post7 (reply to post4)",
  1682  		post8.Id:  "post8 (reply to post4)",
  1683  		post9.Id:  "post9 (reply to post4)",
  1684  		post10.Id: "post10 (reply to post4)",
  1685  	}
  1686  
  1687  	namePost := func(postId string) string {
  1688  		name, ok := postIdNames[postId]
  1689  		if ok {
  1690  			return name
  1691  		}
  1692  
  1693  		return fmt.Sprintf("unknown (%s)", postId)
  1694  	}
  1695  
  1696  	namePosts := func(postIds []string) []string {
  1697  		namedPostIds := make([]string, 0, len(postIds))
  1698  		for _, postId := range postIds {
  1699  			namedPostIds = append(namedPostIds, namePost(postId))
  1700  		}
  1701  
  1702  		return namedPostIds
  1703  	}
  1704  
  1705  	namePostsMap := func(posts map[string]*model.Post) []string {
  1706  		namedPostIds := make([]string, 0, len(posts))
  1707  		for postId := range posts {
  1708  			namedPostIds = append(namedPostIds, namePost(postId))
  1709  		}
  1710  		sort.Strings(namedPostIds)
  1711  
  1712  		return namedPostIds
  1713  	}
  1714  
  1715  	assertPostList := func(t *testing.T, expected, actual *model.PostList) {
  1716  		t.Helper()
  1717  
  1718  		require.Equal(t, namePosts(expected.Order), namePosts(actual.Order), "unexpected post order")
  1719  		require.Equal(t, namePostsMap(expected.Posts), namePostsMap(actual.Posts), "unexpected posts")
  1720  		require.Equal(t, namePost(expected.NextPostId), namePost(actual.NextPostId), "unexpected next post id")
  1721  		require.Equal(t, namePost(expected.PrevPostId), namePost(actual.PrevPostId), "unexpected prev post id")
  1722  	}
  1723  
  1724  	// Setting limit_after to zero should fail with a 400 BadRequest.
  1725  	posts, resp := Client.GetPostsAroundLastUnread(userId, channelId, 20, 0, false)
  1726  	require.NotNil(t, resp.Error)
  1727  	require.Equal(t, "api.context.invalid_url_param.app_error", resp.Error.Id)
  1728  	require.Equal(t, http.StatusBadRequest, resp.StatusCode)
  1729  	require.Nil(t, posts)
  1730  
  1731  	// All returned posts are all read by the user, since it's created by the user itself.
  1732  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 20, 20, false)
  1733  	CheckNoError(t, resp)
  1734  	require.Len(t, posts.Order, 12, "Should return 12 posts only since there's no unread post")
  1735  
  1736  	// Set channel member's last viewed to 0.
  1737  	// All returned posts are latest posts as if all previous posts were already read by the user.
  1738  	channelMember, err := th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1739  	require.NoError(t, err)
  1740  	channelMember.LastViewedAt = 0
  1741  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1742  	require.NoError(t, err)
  1743  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1744  
  1745  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 20, 20, false)
  1746  	CheckNoError(t, resp)
  1747  
  1748  	require.Len(t, posts.Order, 12, "Should return 12 posts only since there's no unread post")
  1749  
  1750  	// get the first system post generated before the created posts above
  1751  	posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "", false)
  1752  	CheckNoError(t, resp)
  1753  	systemPost0 := posts.Posts[posts.Order[0]]
  1754  	postIdNames[systemPost0.Id] = "system post 0"
  1755  	systemPost1 := posts.Posts[posts.Order[1]]
  1756  	postIdNames[systemPost1.Id] = "system post 1"
  1757  
  1758  	// Set channel member's last viewed before post1.
  1759  	channelMember, err = th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1760  	require.NoError(t, err)
  1761  	channelMember.LastViewedAt = post1.CreateAt - 1
  1762  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1763  	require.NoError(t, err)
  1764  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1765  
  1766  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3, false)
  1767  	CheckNoError(t, resp)
  1768  
  1769  	assertPostList(t, &model.PostList{
  1770  		Order: []string{post3.Id, post2.Id, post1.Id, systemPost0.Id, systemPost1.Id},
  1771  		Posts: map[string]*model.Post{
  1772  			systemPost0.Id: systemPost0,
  1773  			systemPost1.Id: systemPost1,
  1774  			post1.Id:       post1,
  1775  			post2.Id:       post2,
  1776  			post3.Id:       post3,
  1777  		},
  1778  		NextPostId: post4.Id,
  1779  		PrevPostId: "",
  1780  	}, posts)
  1781  
  1782  	// Set channel member's last viewed before post6.
  1783  	channelMember, err = th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1784  	require.NoError(t, err)
  1785  	channelMember.LastViewedAt = post6.CreateAt - 1
  1786  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1787  	require.NoError(t, err)
  1788  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1789  
  1790  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3, false)
  1791  	CheckNoError(t, resp)
  1792  
  1793  	assertPostList(t, &model.PostList{
  1794  		Order: []string{post8.Id, post7.Id, post6.Id, post5.Id, post4.Id, post3.Id},
  1795  		Posts: map[string]*model.Post{
  1796  			post3.Id:  post3,
  1797  			post4.Id:  post4,
  1798  			post5.Id:  post5,
  1799  			post6.Id:  post6,
  1800  			post7.Id:  post7,
  1801  			post8.Id:  post8,
  1802  			post9.Id:  post9,
  1803  			post10.Id: post10,
  1804  		},
  1805  		NextPostId: post9.Id,
  1806  		PrevPostId: post2.Id,
  1807  	}, posts)
  1808  
  1809  	// Set channel member's last viewed before post10.
  1810  	channelMember, err = th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1811  	require.NoError(t, err)
  1812  	channelMember.LastViewedAt = post10.CreateAt - 1
  1813  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1814  	require.NoError(t, err)
  1815  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1816  
  1817  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3, false)
  1818  	CheckNoError(t, resp)
  1819  
  1820  	assertPostList(t, &model.PostList{
  1821  		Order: []string{post10.Id, post9.Id, post8.Id, post7.Id},
  1822  		Posts: map[string]*model.Post{
  1823  			post4.Id:  post4,
  1824  			post6.Id:  post6,
  1825  			post7.Id:  post7,
  1826  			post8.Id:  post8,
  1827  			post9.Id:  post9,
  1828  			post10.Id: post10,
  1829  		},
  1830  		NextPostId: "",
  1831  		PrevPostId: post6.Id,
  1832  	}, posts)
  1833  
  1834  	// Set channel member's last viewed equal to post10.
  1835  	channelMember, err = th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1836  	require.NoError(t, err)
  1837  	channelMember.LastViewedAt = post10.CreateAt
  1838  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1839  	require.NoError(t, err)
  1840  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1841  
  1842  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3, false)
  1843  	CheckNoError(t, resp)
  1844  
  1845  	assertPostList(t, &model.PostList{
  1846  		Order: []string{post10.Id, post9.Id, post8.Id},
  1847  		Posts: map[string]*model.Post{
  1848  			post4.Id:  post4,
  1849  			post6.Id:  post6,
  1850  			post7.Id:  post7,
  1851  			post8.Id:  post8,
  1852  			post9.Id:  post9,
  1853  			post10.Id: post10,
  1854  		},
  1855  		NextPostId: "",
  1856  		PrevPostId: post7.Id,
  1857  	}, posts)
  1858  
  1859  	// Set channel member's last viewed to just before a new reply to a previous thread, not
  1860  	// otherwise in the requested window.
  1861  	post11 := th.CreatePost()
  1862  	post12, resp := Client.CreatePost(&model.Post{
  1863  		ChannelId: channelId,
  1864  		Message:   model.NewId(),
  1865  		RootId:    post4.Id,
  1866  		ParentId:  post4.Id,
  1867  	})
  1868  	CheckNoError(t, resp)
  1869  	post13 := th.CreatePost()
  1870  
  1871  	postIdNames[post11.Id] = "post11"
  1872  	postIdNames[post12.Id] = "post12 (reply to post4)"
  1873  	postIdNames[post13.Id] = "post13"
  1874  
  1875  	channelMember, err = th.App.Srv().Store.Channel().GetMember(context.Background(), channelId, userId)
  1876  	require.NoError(t, err)
  1877  	channelMember.LastViewedAt = post12.CreateAt - 1
  1878  	_, err = th.App.Srv().Store.Channel().UpdateMember(channelMember)
  1879  	require.NoError(t, err)
  1880  	th.App.Srv().Store.Post().InvalidateLastPostTimeCache(channelId)
  1881  
  1882  	posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 1, 2, false)
  1883  	CheckNoError(t, resp)
  1884  
  1885  	assertPostList(t, &model.PostList{
  1886  		Order: []string{post13.Id, post12.Id, post11.Id},
  1887  		Posts: map[string]*model.Post{
  1888  			post4.Id:  post4,
  1889  			post6.Id:  post6,
  1890  			post7.Id:  post7,
  1891  			post8.Id:  post8,
  1892  			post9.Id:  post9,
  1893  			post10.Id: post10,
  1894  			post11.Id: post11,
  1895  			post12.Id: post12,
  1896  			post13.Id: post13,
  1897  		},
  1898  		NextPostId: "",
  1899  		PrevPostId: post10.Id,
  1900  	}, posts)
  1901  }
  1902  
  1903  func TestGetPost(t *testing.T) {
  1904  	th := Setup(t).InitBasic()
  1905  	defer th.TearDown()
  1906  	// TODO: migrate this entirely to the subtest's client
  1907  	// once the other methods are migrated too.
  1908  	Client := th.Client
  1909  
  1910  	var privatePost *model.Post
  1911  	th.TestForAllClients(t, func(t *testing.T, c *model.Client4) {
  1912  		t.Helper()
  1913  
  1914  		post, resp := c.GetPost(th.BasicPost.Id, "")
  1915  		CheckNoError(t, resp)
  1916  
  1917  		require.Equal(t, th.BasicPost.Id, post.Id, "post ids don't match")
  1918  
  1919  		post, resp = c.GetPost(th.BasicPost.Id, resp.Etag)
  1920  		CheckEtag(t, post, resp)
  1921  
  1922  		_, resp = c.GetPost("", "")
  1923  		CheckNotFoundStatus(t, resp)
  1924  
  1925  		_, resp = c.GetPost("junk", "")
  1926  		CheckBadRequestStatus(t, resp)
  1927  
  1928  		_, resp = c.GetPost(model.NewId(), "")
  1929  		CheckNotFoundStatus(t, resp)
  1930  
  1931  		Client.RemoveUserFromChannel(th.BasicChannel.Id, th.BasicUser.Id)
  1932  
  1933  		// Channel is public, should be able to read post
  1934  		_, resp = c.GetPost(th.BasicPost.Id, "")
  1935  		CheckNoError(t, resp)
  1936  
  1937  		privatePost = th.CreatePostWithClient(Client, th.BasicPrivateChannel)
  1938  
  1939  		_, resp = c.GetPost(privatePost.Id, "")
  1940  		CheckNoError(t, resp)
  1941  	})
  1942  
  1943  	Client.RemoveUserFromChannel(th.BasicPrivateChannel.Id, th.BasicUser.Id)
  1944  
  1945  	// Channel is private, should not be able to read post
  1946  	_, resp := Client.GetPost(privatePost.Id, "")
  1947  	CheckForbiddenStatus(t, resp)
  1948  
  1949  	// But local client should.
  1950  	_, resp = th.LocalClient.GetPost(privatePost.Id, "")
  1951  	CheckNoError(t, resp)
  1952  
  1953  	Client.Logout()
  1954  
  1955  	// Normal client should get unauthorized, but local client should get 404.
  1956  	_, resp = Client.GetPost(model.NewId(), "")
  1957  	CheckUnauthorizedStatus(t, resp)
  1958  
  1959  	_, resp = th.LocalClient.GetPost(model.NewId(), "")
  1960  	CheckNotFoundStatus(t, resp)
  1961  }
  1962  
  1963  func TestDeletePost(t *testing.T) {
  1964  	th := Setup(t).InitBasic()
  1965  	defer th.TearDown()
  1966  	Client := th.Client
  1967  
  1968  	_, resp := Client.DeletePost("")
  1969  	CheckNotFoundStatus(t, resp)
  1970  
  1971  	_, resp = Client.DeletePost("junk")
  1972  	CheckBadRequestStatus(t, resp)
  1973  
  1974  	_, resp = Client.DeletePost(th.BasicPost.Id)
  1975  	CheckForbiddenStatus(t, resp)
  1976  
  1977  	Client.Login(th.TeamAdminUser.Email, th.TeamAdminUser.Password)
  1978  	_, resp = Client.DeletePost(th.BasicPost.Id)
  1979  	CheckNoError(t, resp)
  1980  
  1981  	post := th.CreatePost()
  1982  	user := th.CreateUser()
  1983  
  1984  	Client.Logout()
  1985  	Client.Login(user.Email, user.Password)
  1986  
  1987  	_, resp = Client.DeletePost(post.Id)
  1988  	CheckForbiddenStatus(t, resp)
  1989  
  1990  	Client.Logout()
  1991  	_, resp = Client.DeletePost(model.NewId())
  1992  	CheckUnauthorizedStatus(t, resp)
  1993  
  1994  	status, resp := th.SystemAdminClient.DeletePost(post.Id)
  1995  	require.True(t, status, "post should return status OK")
  1996  	CheckNoError(t, resp)
  1997  }
  1998  
  1999  func TestDeletePostMessage(t *testing.T) {
  2000  	th := Setup(t).InitBasic()
  2001  	th.LinkUserToTeam(th.SystemAdminUser, th.BasicTeam)
  2002  	th.App.AddUserToChannel(th.SystemAdminUser, th.BasicChannel, false)
  2003  
  2004  	defer th.TearDown()
  2005  
  2006  	testCases := []struct {
  2007  		description string
  2008  		client      *model.Client4
  2009  		delete_by   interface{}
  2010  	}{
  2011  		{"Do not send delete_by to regular user", th.Client, nil},
  2012  		{"Send delete_by to system admin user", th.SystemAdminClient, th.SystemAdminUser.Id},
  2013  	}
  2014  
  2015  	for _, tc := range testCases {
  2016  		t.Run(tc.description, func(t *testing.T) {
  2017  			wsClient, err := th.CreateWebSocketClientWithClient(tc.client)
  2018  			require.Nil(t, err)
  2019  			defer wsClient.Close()
  2020  
  2021  			wsClient.Listen()
  2022  
  2023  			post := th.CreatePost()
  2024  
  2025  			status, resp := th.SystemAdminClient.DeletePost(post.Id)
  2026  			require.True(t, status, "post should return status OK")
  2027  			CheckNoError(t, resp)
  2028  
  2029  			timeout := time.After(5 * time.Second)
  2030  
  2031  			for {
  2032  				select {
  2033  				case ev := <-wsClient.EventChannel:
  2034  					if ev.EventType() == model.WEBSOCKET_EVENT_POST_DELETED {
  2035  						assert.Equal(t, tc.delete_by, ev.GetData()["delete_by"])
  2036  						return
  2037  					}
  2038  				case <-timeout:
  2039  					// We just skip the test instead of failing because waiting for more than 5 seconds
  2040  					// to get a response does not make sense, and it will unnecessarily slow down
  2041  					// the tests further in an already congested CI environment.
  2042  					t.Skip("timed out waiting for event")
  2043  				}
  2044  			}
  2045  		})
  2046  	}
  2047  }
  2048  
  2049  func TestGetPostThread(t *testing.T) {
  2050  	th := Setup(t).InitBasic()
  2051  	defer th.TearDown()
  2052  	Client := th.Client
  2053  
  2054  	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", RootId: th.BasicPost.Id}
  2055  	post, _ = Client.CreatePost(post)
  2056  
  2057  	list, resp := Client.GetPostThread(th.BasicPost.Id, "", false)
  2058  	CheckNoError(t, resp)
  2059  
  2060  	var list2 *model.PostList
  2061  	list2, resp = Client.GetPostThread(th.BasicPost.Id, resp.Etag, false)
  2062  	CheckEtag(t, list2, resp)
  2063  	require.Equal(t, th.BasicPost.Id, list.Order[0], "wrong order")
  2064  
  2065  	_, ok := list.Posts[th.BasicPost.Id]
  2066  	require.True(t, ok, "should have had post")
  2067  
  2068  	_, ok = list.Posts[post.Id]
  2069  	require.True(t, ok, "should have had post")
  2070  
  2071  	_, resp = Client.GetPostThread("junk", "", false)
  2072  	CheckBadRequestStatus(t, resp)
  2073  
  2074  	_, resp = Client.GetPostThread(model.NewId(), "", false)
  2075  	CheckNotFoundStatus(t, resp)
  2076  
  2077  	Client.RemoveUserFromChannel(th.BasicChannel.Id, th.BasicUser.Id)
  2078  
  2079  	// Channel is public, should be able to read post
  2080  	_, resp = Client.GetPostThread(th.BasicPost.Id, "", false)
  2081  	CheckNoError(t, resp)
  2082  
  2083  	privatePost := th.CreatePostWithClient(Client, th.BasicPrivateChannel)
  2084  
  2085  	_, resp = Client.GetPostThread(privatePost.Id, "", false)
  2086  	CheckNoError(t, resp)
  2087  
  2088  	Client.RemoveUserFromChannel(th.BasicPrivateChannel.Id, th.BasicUser.Id)
  2089  
  2090  	// Channel is private, should not be able to read post
  2091  	_, resp = Client.GetPostThread(privatePost.Id, "", false)
  2092  	CheckForbiddenStatus(t, resp)
  2093  
  2094  	Client.Logout()
  2095  	_, resp = Client.GetPostThread(model.NewId(), "", false)
  2096  	CheckUnauthorizedStatus(t, resp)
  2097  
  2098  	_, resp = th.SystemAdminClient.GetPostThread(th.BasicPost.Id, "", false)
  2099  	CheckNoError(t, resp)
  2100  }
  2101  
  2102  func TestSearchPosts(t *testing.T) {
  2103  	th := Setup(t).InitBasic()
  2104  	defer th.TearDown()
  2105  	experimentalViewArchivedChannels := *th.App.Config().TeamSettings.ExperimentalViewArchivedChannels
  2106  	defer func() {
  2107  		th.App.UpdateConfig(func(cfg *model.Config) {
  2108  			cfg.TeamSettings.ExperimentalViewArchivedChannels = &experimentalViewArchivedChannels
  2109  		})
  2110  	}()
  2111  	th.App.UpdateConfig(func(cfg *model.Config) {
  2112  		*cfg.TeamSettings.ExperimentalViewArchivedChannels = true
  2113  	})
  2114  
  2115  	th.LoginBasic()
  2116  	Client := th.Client
  2117  
  2118  	message := "search for post1"
  2119  	_ = th.CreateMessagePost(message)
  2120  
  2121  	message = "search for post2"
  2122  	post2 := th.CreateMessagePost(message)
  2123  
  2124  	message = "#hashtag search for post3"
  2125  	post3 := th.CreateMessagePost(message)
  2126  
  2127  	message = "hashtag for post4"
  2128  	_ = th.CreateMessagePost(message)
  2129  
  2130  	archivedChannel := th.CreatePublicChannel()
  2131  	_ = th.CreateMessagePostWithClient(th.Client, archivedChannel, "#hashtag for post3")
  2132  	th.Client.DeleteChannel(archivedChannel.Id)
  2133  
  2134  	terms := "search"
  2135  	isOrSearch := false
  2136  	timezoneOffset := 5
  2137  	searchParams := model.SearchParameter{
  2138  		Terms:          &terms,
  2139  		IsOrSearch:     &isOrSearch,
  2140  		TimeZoneOffset: &timezoneOffset,
  2141  	}
  2142  	posts, resp := Client.SearchPostsWithParams(th.BasicTeam.Id, &searchParams)
  2143  	CheckNoError(t, resp)
  2144  	require.Len(t, posts.Order, 3, "wrong search")
  2145  
  2146  	terms = "search"
  2147  	page := 0
  2148  	perPage := 2
  2149  	searchParams = model.SearchParameter{
  2150  		Terms:          &terms,
  2151  		IsOrSearch:     &isOrSearch,
  2152  		TimeZoneOffset: &timezoneOffset,
  2153  		Page:           &page,
  2154  		PerPage:        &perPage,
  2155  	}
  2156  	posts2, resp := Client.SearchPostsWithParams(th.BasicTeam.Id, &searchParams)
  2157  	CheckNoError(t, resp)
  2158  	// We don't support paging for DB search yet, modify this when we do.
  2159  	require.Len(t, posts2.Order, 3, "Wrong number of posts")
  2160  	assert.Equal(t, posts.Order[0], posts2.Order[0])
  2161  	assert.Equal(t, posts.Order[1], posts2.Order[1])
  2162  
  2163  	page = 1
  2164  	searchParams = model.SearchParameter{
  2165  		Terms:          &terms,
  2166  		IsOrSearch:     &isOrSearch,
  2167  		TimeZoneOffset: &timezoneOffset,
  2168  		Page:           &page,
  2169  		PerPage:        &perPage,
  2170  	}
  2171  	posts2, resp = Client.SearchPostsWithParams(th.BasicTeam.Id, &searchParams)
  2172  	CheckNoError(t, resp)
  2173  	// We don't support paging for DB search yet, modify this when we do.
  2174  	require.Empty(t, posts2.Order, "Wrong number of posts")
  2175  
  2176  	posts, resp = Client.SearchPosts(th.BasicTeam.Id, "search", false)
  2177  	CheckNoError(t, resp)
  2178  	require.Len(t, posts.Order, 3, "wrong search")
  2179  
  2180  	posts, resp = Client.SearchPosts(th.BasicTeam.Id, "post2", false)
  2181  	CheckNoError(t, resp)
  2182  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2183  	require.Equal(t, post2.Id, posts.Order[0], "wrong search")
  2184  
  2185  	posts, resp = Client.SearchPosts(th.BasicTeam.Id, "#hashtag", false)
  2186  	CheckNoError(t, resp)
  2187  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2188  	require.Equal(t, post3.Id, posts.Order[0], "wrong search")
  2189  
  2190  	terms = "#hashtag"
  2191  	includeDeletedChannels := true
  2192  	searchParams = model.SearchParameter{
  2193  		Terms:                  &terms,
  2194  		IsOrSearch:             &isOrSearch,
  2195  		TimeZoneOffset:         &timezoneOffset,
  2196  		IncludeDeletedChannels: &includeDeletedChannels,
  2197  	}
  2198  	posts, resp = Client.SearchPostsWithParams(th.BasicTeam.Id, &searchParams)
  2199  	CheckNoError(t, resp)
  2200  	require.Len(t, posts.Order, 2, "wrong search")
  2201  
  2202  	th.App.UpdateConfig(func(cfg *model.Config) {
  2203  		*cfg.TeamSettings.ExperimentalViewArchivedChannels = false
  2204  	})
  2205  
  2206  	posts, resp = Client.SearchPostsWithParams(th.BasicTeam.Id, &searchParams)
  2207  	CheckNoError(t, resp)
  2208  	require.Len(t, posts.Order, 1, "wrong search")
  2209  
  2210  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "*", false)
  2211  	require.Empty(t, posts.Order, "searching for just * shouldn't return any results")
  2212  
  2213  	posts, resp = Client.SearchPosts(th.BasicTeam.Id, "post1 post2", true)
  2214  	CheckNoError(t, resp)
  2215  	require.Len(t, posts.Order, 2, "wrong search results")
  2216  
  2217  	_, resp = Client.SearchPosts("junk", "#sgtitlereview", false)
  2218  	CheckBadRequestStatus(t, resp)
  2219  
  2220  	_, resp = Client.SearchPosts(model.NewId(), "#sgtitlereview", false)
  2221  	CheckForbiddenStatus(t, resp)
  2222  
  2223  	_, resp = Client.SearchPosts(th.BasicTeam.Id, "", false)
  2224  	CheckBadRequestStatus(t, resp)
  2225  
  2226  	Client.Logout()
  2227  	_, resp = Client.SearchPosts(th.BasicTeam.Id, "#sgtitlereview", false)
  2228  	CheckUnauthorizedStatus(t, resp)
  2229  }
  2230  
  2231  func TestSearchHashtagPosts(t *testing.T) {
  2232  	th := Setup(t).InitBasic()
  2233  	defer th.TearDown()
  2234  	th.LoginBasic()
  2235  	Client := th.Client
  2236  
  2237  	message := "#sgtitlereview with space"
  2238  	assert.NotNil(t, th.CreateMessagePost(message))
  2239  
  2240  	message = "#sgtitlereview\n with return"
  2241  	assert.NotNil(t, th.CreateMessagePost(message))
  2242  
  2243  	message = "no hashtag"
  2244  	assert.NotNil(t, th.CreateMessagePost(message))
  2245  
  2246  	posts, resp := Client.SearchPosts(th.BasicTeam.Id, "#sgtitlereview", false)
  2247  	CheckNoError(t, resp)
  2248  	require.Len(t, posts.Order, 2, "wrong search results")
  2249  
  2250  	Client.Logout()
  2251  	_, resp = Client.SearchPosts(th.BasicTeam.Id, "#sgtitlereview", false)
  2252  	CheckUnauthorizedStatus(t, resp)
  2253  }
  2254  
  2255  func TestSearchPostsInChannel(t *testing.T) {
  2256  	th := Setup(t).InitBasic()
  2257  	defer th.TearDown()
  2258  	th.LoginBasic()
  2259  	Client := th.Client
  2260  
  2261  	channel := th.CreatePublicChannel()
  2262  
  2263  	message := "sgtitlereview with space"
  2264  	_ = th.CreateMessagePost(message)
  2265  
  2266  	message = "sgtitlereview\n with return"
  2267  	_ = th.CreateMessagePostWithClient(Client, th.BasicChannel2, message)
  2268  
  2269  	message = "other message with no return"
  2270  	_ = th.CreateMessagePostWithClient(Client, th.BasicChannel2, message)
  2271  
  2272  	message = "other message with no return"
  2273  	_ = th.CreateMessagePostWithClient(Client, channel, message)
  2274  
  2275  	posts, _ := Client.SearchPosts(th.BasicTeam.Id, "channel:", false)
  2276  	require.Empty(t, posts.Order, "wrong number of posts for search 'channel:'")
  2277  
  2278  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "in:", false)
  2279  	require.Empty(t, posts.Order, "wrong number of posts for search 'in:'")
  2280  
  2281  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "channel:"+th.BasicChannel.Name, false)
  2282  	require.Lenf(t, posts.Order, 2, "wrong number of posts returned for search 'channel:%v'", th.BasicChannel.Name)
  2283  
  2284  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "in:"+th.BasicChannel2.Name, false)
  2285  	require.Lenf(t, posts.Order, 2, "wrong number of posts returned for search 'in:%v'", th.BasicChannel2.Name)
  2286  
  2287  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "channel:"+th.BasicChannel2.Name, false)
  2288  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'channel:%v'", th.BasicChannel2.Name)
  2289  
  2290  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "ChAnNeL:"+th.BasicChannel2.Name, false)
  2291  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'ChAnNeL:%v'", th.BasicChannel2.Name)
  2292  
  2293  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "sgtitlereview", false)
  2294  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'sgtitlereview'")
  2295  
  2296  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "sgtitlereview channel:"+th.BasicChannel.Name, false)
  2297  	require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview channel:%v'", th.BasicChannel.Name)
  2298  
  2299  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "sgtitlereview in: "+th.BasicChannel2.Name, false)
  2300  	require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview in: %v'", th.BasicChannel2.Name)
  2301  
  2302  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "sgtitlereview channel: "+th.BasicChannel2.Name, false)
  2303  	require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'sgtitlereview channel: %v'", th.BasicChannel2.Name)
  2304  
  2305  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "channel: "+th.BasicChannel2.Name+" channel: "+channel.Name, false)
  2306  	require.Lenf(t, posts.Order, 3, "wrong number of posts for 'channel: %v channel: %v'", th.BasicChannel2.Name, channel.Name)
  2307  }
  2308  
  2309  func TestSearchPostsFromUser(t *testing.T) {
  2310  	th := Setup(t).InitBasic()
  2311  	defer th.TearDown()
  2312  	Client := th.Client
  2313  
  2314  	th.LoginTeamAdmin()
  2315  	user := th.CreateUser()
  2316  	th.LinkUserToTeam(user, th.BasicTeam)
  2317  	th.App.AddUserToChannel(user, th.BasicChannel, false)
  2318  	th.App.AddUserToChannel(user, th.BasicChannel2, false)
  2319  
  2320  	message := "sgtitlereview with space"
  2321  	_ = th.CreateMessagePost(message)
  2322  
  2323  	Client.Logout()
  2324  	th.LoginBasic2()
  2325  
  2326  	message = "sgtitlereview\n with return"
  2327  	_ = th.CreateMessagePostWithClient(Client, th.BasicChannel2, message)
  2328  
  2329  	posts, _ := Client.SearchPosts(th.BasicTeam.Id, "from: "+th.TeamAdminUser.Username, false)
  2330  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.TeamAdminUser.Username)
  2331  
  2332  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
  2333  	require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v", th.BasicUser2.Username)
  2334  
  2335  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" sgtitlereview", false)
  2336  	require.Lenf(t, posts.Order, 1, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
  2337  
  2338  	message = "hullo"
  2339  	_ = th.CreateMessagePost(message)
  2340  
  2341  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" in:"+th.BasicChannel.Name, false)
  2342  	require.Len(t, posts.Order, 1, "wrong number of posts for search 'from: %v in:", th.BasicUser2.Username, th.BasicChannel.Name)
  2343  
  2344  	Client.Login(user.Email, user.Password)
  2345  
  2346  	// wait for the join/leave messages to be created for user3 since they're done asynchronously
  2347  	time.Sleep(100 * time.Millisecond)
  2348  
  2349  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username, false)
  2350  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v'", th.BasicUser2.Username)
  2351  
  2352  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username, false)
  2353  	require.Lenf(t, posts.Order, 2, "wrong number of posts for search 'from: %v from: %v'", th.BasicUser2.Username, user.Username)
  2354  
  2355  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name, false)
  2356  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2357  
  2358  	message = "coconut"
  2359  	_ = th.CreateMessagePostWithClient(Client, th.BasicChannel2, message)
  2360  
  2361  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "from: "+th.BasicUser2.Username+" from: "+user.Username+" in:"+th.BasicChannel2.Name+" coconut", false)
  2362  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2363  }
  2364  
  2365  func TestSearchPostsWithDateFlags(t *testing.T) {
  2366  	th := Setup(t).InitBasic()
  2367  	defer th.TearDown()
  2368  	th.LoginBasic()
  2369  	Client := th.Client
  2370  
  2371  	message := "sgtitlereview\n with return"
  2372  	createDate := time.Date(2018, 8, 1, 5, 0, 0, 0, time.UTC)
  2373  	_ = th.CreateMessagePostNoClient(th.BasicChannel, message, utils.MillisFromTime(createDate))
  2374  
  2375  	message = "other message with no return"
  2376  	createDate = time.Date(2018, 8, 2, 5, 0, 0, 0, time.UTC)
  2377  	_ = th.CreateMessagePostNoClient(th.BasicChannel, message, utils.MillisFromTime(createDate))
  2378  
  2379  	message = "other message with no return"
  2380  	createDate = time.Date(2018, 8, 3, 5, 0, 0, 0, time.UTC)
  2381  	_ = th.CreateMessagePostNoClient(th.BasicChannel, message, utils.MillisFromTime(createDate))
  2382  
  2383  	posts, _ := Client.SearchPosts(th.BasicTeam.Id, "return", false)
  2384  	require.Len(t, posts.Order, 3, "wrong number of posts")
  2385  
  2386  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "on:", false)
  2387  	require.Empty(t, posts.Order, "wrong number of posts")
  2388  
  2389  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "after:", false)
  2390  	require.Empty(t, posts.Order, "wrong number of posts")
  2391  
  2392  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "before:", false)
  2393  	require.Empty(t, posts.Order, "wrong number of posts")
  2394  
  2395  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "on:2018-08-01", false)
  2396  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2397  
  2398  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "after:2018-08-01", false)
  2399  	resultCount := 0
  2400  	for _, post := range posts.Posts {
  2401  		if post.UserId == th.BasicUser.Id {
  2402  			resultCount = resultCount + 1
  2403  		}
  2404  	}
  2405  	require.Equal(t, 2, resultCount, "wrong number of posts")
  2406  
  2407  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "before:2018-08-02", false)
  2408  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2409  
  2410  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "before:2018-08-03 after:2018-08-02", false)
  2411  	require.Empty(t, posts.Order, "wrong number of posts")
  2412  
  2413  	posts, _ = Client.SearchPosts(th.BasicTeam.Id, "before:2018-08-03 after:2018-08-01", false)
  2414  	require.Len(t, posts.Order, 1, "wrong number of posts")
  2415  }
  2416  
  2417  func TestGetFileInfosForPost(t *testing.T) {
  2418  	th := Setup(t).InitBasic()
  2419  	defer th.TearDown()
  2420  	Client := th.Client
  2421  
  2422  	fileIds := make([]string, 3)
  2423  	data, err := testutils.ReadTestFile("test.png")
  2424  	require.NoError(t, err)
  2425  	for i := 0; i < 3; i++ {
  2426  		fileResp, _ := Client.UploadFile(data, th.BasicChannel.Id, "test.png")
  2427  		fileIds[i] = fileResp.FileInfos[0].Id
  2428  	}
  2429  
  2430  	post := &model.Post{ChannelId: th.BasicChannel.Id, Message: "zz" + model.NewId() + "a", FileIds: fileIds}
  2431  	post, _ = Client.CreatePost(post)
  2432  
  2433  	infos, resp := Client.GetFileInfosForPost(post.Id, "")
  2434  	CheckNoError(t, resp)
  2435  
  2436  	require.Len(t, infos, 3, "missing file infos")
  2437  
  2438  	found := false
  2439  	for _, info := range infos {
  2440  		if info.Id == fileIds[0] {
  2441  			found = true
  2442  		}
  2443  	}
  2444  
  2445  	require.True(t, found, "missing file info")
  2446  
  2447  	infos, resp = Client.GetFileInfosForPost(post.Id, resp.Etag)
  2448  	CheckEtag(t, infos, resp)
  2449  
  2450  	infos, resp = Client.GetFileInfosForPost(th.BasicPost.Id, "")
  2451  	CheckNoError(t, resp)
  2452  
  2453  	require.Empty(t, infos, "should have no file infos")
  2454  
  2455  	_, resp = Client.GetFileInfosForPost("junk", "")
  2456  	CheckBadRequestStatus(t, resp)
  2457  
  2458  	_, resp = Client.GetFileInfosForPost(model.NewId(), "")
  2459  	CheckForbiddenStatus(t, resp)
  2460  
  2461  	Client.Logout()
  2462  	_, resp = Client.GetFileInfosForPost(model.NewId(), "")
  2463  	CheckUnauthorizedStatus(t, resp)
  2464  
  2465  	_, resp = th.SystemAdminClient.GetFileInfosForPost(th.BasicPost.Id, "")
  2466  	CheckNoError(t, resp)
  2467  }
  2468  
  2469  func TestSetChannelUnread(t *testing.T) {
  2470  	th := Setup(t).InitBasic()
  2471  	defer th.TearDown()
  2472  
  2473  	u1 := th.BasicUser
  2474  	u2 := th.BasicUser2
  2475  	s2, _ := th.App.GetSession(th.Client.AuthToken)
  2476  	th.Client.Login(u1.Email, u1.Password)
  2477  	c1 := th.BasicChannel
  2478  	c1toc2 := &model.ChannelView{ChannelId: th.BasicChannel2.Id, PrevChannelId: c1.Id}
  2479  	now := utils.MillisFromTime(time.Now())
  2480  	th.CreateMessagePostNoClient(c1, "AAA", now)
  2481  	p2 := th.CreateMessagePostNoClient(c1, "BBB", now+10)
  2482  	th.CreateMessagePostNoClient(c1, "CCC", now+20)
  2483  
  2484  	pp1 := th.CreateMessagePostNoClient(th.BasicPrivateChannel, "Sssh!", now)
  2485  	pp2 := th.CreateMessagePostNoClient(th.BasicPrivateChannel, "You Sssh!", now+10)
  2486  	require.NotNil(t, pp1)
  2487  	require.NotNil(t, pp2)
  2488  
  2489  	// Ensure that post have been read
  2490  	unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
  2491  	require.Nil(t, err)
  2492  	require.Equal(t, int64(4), unread.MsgCount)
  2493  	unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
  2494  	require.Nil(t, err)
  2495  	require.Equal(t, int64(4), unread.MsgCount)
  2496  	_, err = th.App.ViewChannel(c1toc2, u2.Id, s2.Id, false)
  2497  	require.Nil(t, err)
  2498  	unread, err = th.App.GetChannelUnread(c1.Id, u2.Id)
  2499  	require.Nil(t, err)
  2500  	require.Equal(t, int64(0), unread.MsgCount)
  2501  
  2502  	t.Run("Unread last one", func(t *testing.T) {
  2503  		r := th.Client.SetPostUnread(u1.Id, p2.Id, true)
  2504  		checkHTTPStatus(t, r, 200, false)
  2505  		unread, err := th.App.GetChannelUnread(c1.Id, u1.Id)
  2506  		require.Nil(t, err)
  2507  		assert.Equal(t, int64(2), unread.MsgCount)
  2508  	})
  2509  
  2510  	t.Run("Unread on a private channel", func(t *testing.T) {
  2511  		r := th.Client.SetPostUnread(u1.Id, pp2.Id, true)
  2512  		assert.Equal(t, 200, r.StatusCode)
  2513  		unread, err := th.App.GetChannelUnread(th.BasicPrivateChannel.Id, u1.Id)
  2514  		require.Nil(t, err)
  2515  		assert.Equal(t, int64(1), unread.MsgCount)
  2516  		r = th.Client.SetPostUnread(u1.Id, pp1.Id, true)
  2517  		assert.Equal(t, 200, r.StatusCode)
  2518  		unread, err = th.App.GetChannelUnread(th.BasicPrivateChannel.Id, u1.Id)
  2519  		require.Nil(t, err)
  2520  		assert.Equal(t, int64(2), unread.MsgCount)
  2521  	})
  2522  
  2523  	t.Run("Can't unread an imaginary post", func(t *testing.T) {
  2524  		r := th.Client.SetPostUnread(u1.Id, "invalid4ofngungryquinj976y", true)
  2525  		assert.Equal(t, http.StatusForbidden, r.StatusCode)
  2526  	})
  2527  
  2528  	// let's create another user to test permissions
  2529  	u3 := th.CreateUser()
  2530  	c3 := th.CreateClient()
  2531  	c3.Login(u3.Email, u3.Password)
  2532  
  2533  	t.Run("Can't unread channels you don't belong to", func(t *testing.T) {
  2534  		r := c3.SetPostUnread(u3.Id, pp1.Id, true)
  2535  		assert.Equal(t, http.StatusForbidden, r.StatusCode)
  2536  	})
  2537  
  2538  	t.Run("Can't unread users you don't have permission to edit", func(t *testing.T) {
  2539  		r := c3.SetPostUnread(u1.Id, pp1.Id, true)
  2540  		assert.Equal(t, http.StatusForbidden, r.StatusCode)
  2541  	})
  2542  
  2543  	t.Run("Can't unread if user is not logged in", func(t *testing.T) {
  2544  		th.Client.Logout()
  2545  		response := th.Client.SetPostUnread(u1.Id, p2.Id, true)
  2546  		checkHTTPStatus(t, response, http.StatusUnauthorized, true)
  2547  	})
  2548  }
  2549  
  2550  func TestSetPostUnreadWithoutCollapsedThreads(t *testing.T) {
  2551  	os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true")
  2552  	defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS")
  2553  	th := Setup(t).InitBasic()
  2554  	defer th.TearDown()
  2555  	th.App.UpdateConfig(func(cfg *model.Config) {
  2556  		*cfg.ServiceSettings.ThreadAutoFollow = true
  2557  		*cfg.ServiceSettings.CollapsedThreads = model.COLLAPSED_THREADS_DEFAULT_ON
  2558  	})
  2559  
  2560  	// user2: first root mention @user1
  2561  	//   - user1: hello
  2562  	//   - user2: mention @u1
  2563  	//   - user1: another repoy
  2564  	//   - user2: another mention @u1
  2565  	// user1: a root post
  2566  	// user2: Another root mention @u1
  2567  	user1Mention := " @" + th.BasicUser.Username
  2568  	rootPost1, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "first root mention" + user1Mention}, th.BasicChannel, false, false)
  2569  	require.Nil(t, appErr)
  2570  	_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hello"}, th.BasicChannel, false, false)
  2571  	require.Nil(t, appErr)
  2572  	replyPost1, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "mention" + user1Mention}, th.BasicChannel, false, false)
  2573  	require.Nil(t, appErr)
  2574  	_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another reply"}, th.BasicChannel, false, false)
  2575  	require.Nil(t, appErr)
  2576  	_, appErr = th.App.CreatePost(th.Context, &model.Post{RootId: rootPost1.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another mention" + user1Mention}, th.BasicChannel, false, false)
  2577  	require.Nil(t, appErr)
  2578  	_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "a root post"}, th.BasicChannel, false, false)
  2579  	require.Nil(t, appErr)
  2580  	_, appErr = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "another root mention" + user1Mention}, th.BasicChannel, false, false)
  2581  	require.Nil(t, appErr)
  2582  
  2583  	t.Run("Mark reply post as unread", func(t *testing.T) {
  2584  		resp := th.Client.SetPostUnread(th.BasicUser.Id, replyPost1.Id, false)
  2585  		CheckNoError(t, resp)
  2586  		channelUnread, appErr := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
  2587  		require.Nil(t, appErr)
  2588  
  2589  		require.Equal(t, int64(3), channelUnread.MentionCount)
  2590  		//  MentionCountRoot should be zero so that supported clients don't show a mention badge for the channel
  2591  		require.Equal(t, int64(0), channelUnread.MentionCountRoot)
  2592  
  2593  		require.Equal(t, int64(5), channelUnread.MsgCount)
  2594  		//  MentionCountRoot should be zero so that supported clients don't show the channel as unread
  2595  		require.Equal(t, channelUnread.MsgCountRoot, int64(0))
  2596  
  2597  		threadMembership, err := th.App.GetThreadMembershipForUser(th.BasicUser.Id, rootPost1.Id)
  2598  		require.Nil(t, err)
  2599  		thread, err := th.App.GetThreadForUser(th.BasicTeam.Id, threadMembership, false)
  2600  		require.Nil(t, err)
  2601  		require.Equal(t, int64(2), thread.UnreadMentions)
  2602  		require.Equal(t, int64(3), thread.UnreadReplies)
  2603  	})
  2604  
  2605  	t.Run("Mark root post as unread", func(t *testing.T) {
  2606  		resp := th.Client.SetPostUnread(th.BasicUser.Id, rootPost1.Id, false)
  2607  		CheckNoError(t, resp)
  2608  		channelUnread, appErr := th.App.GetChannelUnread(th.BasicChannel.Id, th.BasicUser.Id)
  2609  		require.Nil(t, appErr)
  2610  
  2611  		require.Equal(t, int64(4), channelUnread.MentionCount)
  2612  		require.Equal(t, int64(2), channelUnread.MentionCountRoot)
  2613  
  2614  		require.Equal(t, int64(7), channelUnread.MsgCount)
  2615  		require.Equal(t, int64(3), channelUnread.MsgCountRoot)
  2616  	})
  2617  }