MM-11210 Add "GET /posts/unread" API to support landing on the last unread post (#11486)

* [MM-11210] Add API GET 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts/unread' for scrolling overhaul (#9108)

* Add API GET 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts/unread'

* add constants

* refactor GetPostSince and added more tests

* move constants to app package

* [MM-11528 &&  MM-11583] Add userId to in the "posts/unread" path and update test with time delay to fix intermittent failure (#9229)

* add userId to in the "posts/unread" path and update test with time delay to fix intermittent failure

* add limit before and after to query

* remove time delay on test and put pretermined value of Post.CreateAt

* Fix conflict

* [MM-11876] Add cursor to posts list such as next_post_id and previous_post_id (#9707)

* add cursor to posts list such as next_post_id and previous_post_id

add publish previous_post_id on WEBSOCKET_EVENT_POSTED and only get next or previous post IDs if necessary

revert change on adding previous_post_id in WEBSOCKET_EVENT_POSTED

add missing strings import

fix merge conflicts

* update per comment

* update per feedback

* corrected the logic in getting the next and previous post ID

* fix logic to determine next and post IDs, and rename function to have suffix of "Time"

* rearrange logics and add mote tests

* fix merge conflict

* fix missing message when using unread API (#10233)

* MM-15569 Fixes failing test on TestGetPostsForChannelAroundLastUnread (#11039)

* Fix missing posts when getting posts since

* revert changes to GetPostsSince

* migrate Post.GetPostAfterTime and Post.GetPostBeforeTime to sync by default

* revert change to cacheItem

* Fix post ID validation, build query on squirrel and only return post ID as necessary
Этот коммит содержится в:
Saturnino Abril
2019-07-04 05:23:04 +08:00
коммит произвёл Sudheer
родитель f56a8f5a99
Коммит b832985f1d
12 изменённых файлов: 1085 добавлений и 32 удалений

Просмотреть файл

@@ -9,6 +9,7 @@ import (
"strconv"
"time"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
@@ -22,6 +23,8 @@ func (api *API) InitPost() {
api.BaseRoutes.PostsForChannel.Handle("", api.ApiSessionRequired(getPostsForChannel)).Methods("GET")
api.BaseRoutes.PostsForUser.Handle("/flagged", api.ApiSessionRequired(getFlaggedPostsForUser)).Methods("GET")
api.BaseRoutes.ChannelForUser.Handle("/posts/unread", api.ApiSessionRequired(getPostsForChannelAroundLastUnread)).Methods("GET")
api.BaseRoutes.Team.Handle("/posts/search", api.ApiSessionRequired(searchPosts)).Methods("POST")
api.BaseRoutes.Post.Handle("", api.ApiSessionRequired(updatePost)).Methods("PUT")
api.BaseRoutes.Post.Handle("/patch", api.ApiSessionRequired(patchPost)).Methods("PUT")
@@ -109,12 +112,20 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
afterPost := r.URL.Query().Get("after")
beforePost := r.URL.Query().Get("before")
sinceString := r.URL.Query().Get("since")
if len(afterPost) > 0 && !model.IsValidId(afterPost) {
c.SetInvalidParam("after")
return
}
beforePost := r.URL.Query().Get("before")
if len(beforePost) > 0 && !model.IsValidId(beforePost) {
c.SetInvalidParam("before")
return
}
sinceString := r.URL.Query().Get("since")
var since int64
var parseError error
if len(sinceString) > 0 {
since, parseError = strconv.ParseInt(sinceString, 10, 64)
if parseError != nil {
@@ -123,7 +134,11 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, model.PERMISSION_READ_CHANNEL) {
channelId := c.Params.ChannelId
page := c.Params.Page
perPage := c.Params.PerPage
if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
@@ -133,31 +148,31 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
etag := ""
if since > 0 {
list, err = c.App.GetPostsSince(c.Params.ChannelId, since)
list, err = c.App.GetPostsSince(channelId, since)
} else if len(afterPost) > 0 {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts After", w, r) {
return
}
list, err = c.App.GetPostsAfterPost(c.Params.ChannelId, afterPost, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsAfterPost(channelId, afterPost, page, perPage)
} else if len(beforePost) > 0 {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts Before", w, r) {
return
}
list, err = c.App.GetPostsBeforePost(c.Params.ChannelId, beforePost, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsBeforePost(channelId, beforePost, page, perPage)
} else {
etag = c.App.GetPostsEtag(c.Params.ChannelId)
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts", w, r) {
return
}
list, err = c.App.GetPostsPage(c.Params.ChannelId, c.Params.Page, c.Params.PerPage)
list, err = c.App.GetPostsPage(channelId, page, perPage)
}
if err != nil {
@@ -169,7 +184,56 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
}
w.Write([]byte(c.App.PreparePostListForClient(list).ToJson()))
c.App.AddCursorIdsForPostList(list, afterPost, beforePost, since, page, perPage)
clientPostList := c.App.PreparePostListForClient(list)
w.Write([]byte(clientPostList.ToJson()))
}
func getPostsForChannelAroundLastUnread(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireUserId().RequireChannelId()
if c.Err != nil {
return
}
userId := c.Params.UserId
if !c.App.SessionHasPermissionToUser(c.App.Session, userId) {
c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
return
}
channelId := c.Params.ChannelId
if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_READ_CHANNEL) {
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
return
}
postList, err := c.App.GetPostsForChannelAroundLastUnread(channelId, userId, c.Params.LimitBefore, c.Params.LimitAfter)
if err != nil {
c.Err = err
return
}
etag := ""
if len(postList.Order) == 0 {
etag = c.App.GetPostsEtag(channelId)
if c.HandleEtag(etag, "Get Posts", w, r) {
return
}
postList, err = c.App.GetPostsPage(channelId, app.PAGE_DEFAULT, c.Params.LimitBefore)
}
postList.NextPostId = c.App.GetNextPostIdFromPostList(postList)
postList.PrevPostId = c.App.GetPrevPostIdFromPostList(postList)
clientPostList := c.App.PreparePostListForClient(postList)
if len(etag) > 0 {
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
}
w.Write([]byte(clientPostList.ToJson()))
}
func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) {

Просмотреть файл

@@ -16,6 +16,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
@@ -911,6 +912,13 @@ func TestGetPostsForChannel(t *testing.T) {
t.Log(posts.Posts)
t.Fatal("should return 2 posts")
}
// "since" query to return empty NextPostId and PrevPostId
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
found := make([]bool, 2)
for _, p := range posts.Posts {
@@ -945,6 +953,97 @@ func TestGetPostsForChannel(t *testing.T) {
_, resp = th.SystemAdminClient.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "")
CheckNoError(t, resp)
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
Client.Login(th.BasicUser.Email, th.BasicUser.Password)
th.CreatePost() // post6
post7 := th.CreatePost()
post8 := th.CreatePost()
th.CreatePost() // post9
post10 := th.CreatePost()
// get the system post IDs posted before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
systemPostId1 := posts.Order[1]
// similar to '/posts'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 12 || posts.Order[0] != post10.Id || posts.Order[11] != systemPostId1 {
t.Fatal("should return 12 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?per_page=3'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post10.Id || posts.Order[2] != post8.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post7.Id {
t.Fatal("should return post7.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=1'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post7.Id || posts.Order[2] != post5.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post8.Id {
t.Fatal("should return post8.Id as NextPostId")
}
if posts.PrevPostId != post4.Id {
t.Fatal("should return post4.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=2'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post4.Id || posts.Order[2] != post2.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post5.Id {
t.Fatal("should return post5.Id as NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should return post1.Id as PrevPostId")
}
// similar to '/posts?per_page=3&page=3'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 3, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post1.Id || posts.Order[2] != systemPostId1 {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post2.Id {
t.Fatal("should return post2.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?per_page=3&page=4'
posts, resp = Client.GetPostsForChannel(th.BasicChannel.Id, 4, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
}
func TestGetFlaggedPostsForUser(t *testing.T) {
@@ -1190,7 +1289,7 @@ func TestGetFlaggedPostsForUser(t *testing.T) {
CheckNoError(t, resp)
}
func TestGetPostsAfterAndBefore(t *testing.T) {
func TestGetPostsBefore(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
@@ -1223,24 +1322,208 @@ func TestGetPostsAfterAndBefore(t *testing.T) {
}
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post3.Id, 1, 1, "")
if posts.NextPostId != post3.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should match empty PrevPostId")
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post4.Id, 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("too many posts returned")
}
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, "junk", 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 0 {
t.Fatal("should have no posts")
if posts.Order[0] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post3.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 0, 100, "")
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, "junk", 1, 1, "")
CheckBadRequestStatus(t, resp)
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 0, 3, "")
CheckNoError(t, resp)
found = make([]bool, 2)
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.Order[2] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
// get the system post IDs posted before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
CheckNoError(t, resp)
systemPostId2 := posts.Order[0]
systemPostId1 := posts.Order[1]
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post5.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post1.Id {
t.Fatal("should match returned post")
}
if posts.Order[1] != systemPostId2 {
t.Fatal("should match returned post")
}
if posts.Order[2] != systemPostId1 {
t.Fatal("should match returned post")
}
if posts.NextPostId != post2.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return empty PrevPostId")
}
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
post6 := th.CreatePost()
th.CreatePost() // post7
post8 := th.CreatePost()
post9 := th.CreatePost()
th.CreatePost() // post10
// similar to '/posts?before=post9'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 10 || posts.Order[0] != post8.Id || posts.Order[9] != systemPostId1 {
t.Fatal("should return 10 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=post9&per_page=3'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post8.Id || posts.Order[2] != post6.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != post5.Id {
t.Fatal("should return post5.Id as PrevPostId")
}
// similar to '/posts?before=post9&per_page=3&page=1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post5.Id || posts.Order[2] != post3.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post6.Id {
t.Fatal("should return post6.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?before=post9&per_page=3&page=2'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post9.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post2.Id || posts.Order[2] != systemPostId2 {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post3.Id {
t.Fatal("should return post3.Id as NextPostId")
}
if posts.PrevPostId != systemPostId1 {
t.Fatal("should return systemPostId1 as PrevPostId")
}
// similar to '/posts?before=post1&per_page=3'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 2 || posts.Order[0] != systemPostId2 || posts.Order[1] != systemPostId1 {
t.Fatal("should return 2 posts and match order")
}
if posts.NextPostId != post1.Id {
t.Fatal("should return post1.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=systemPostId1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != systemPostId1 {
t.Fatal("should return systemPostId1 as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=systemPostId1&per_page=60&page=1'
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, systemPostId1, 1, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?before=non-existent-post'
nonExistentPostId := model.NewId()
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, nonExistentPostId, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != nonExistentPostId {
t.Fatal("should return nonExistentPostId as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
}
func TestGetPostsAfter(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
post1 := th.CreatePost()
post2 := th.CreatePost()
post3 := th.CreatePost()
post4 := th.CreatePost()
post5 := th.CreatePost()
posts, resp := Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 0, 100, "")
CheckNoError(t, resp)
found := make([]bool, 2)
for _, p := range posts.Posts {
if p.Id == post4.Id {
found[0] = true
@@ -1259,18 +1542,298 @@ func TestGetPostsAfterAndBefore(t *testing.T) {
}
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post3.Id, 1, 1, "")
if posts.NextPostId != "" {
t.Fatal("should match empty NextPostId")
}
if posts.PrevPostId != post3.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 1, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("too many posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post3.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, "junk", 1, 1, "")
CheckBadRequestStatus(t, resp)
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 0 {
t.Fatal("should have no posts")
if len(posts.Posts) != 3 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post4.Id {
t.Fatal("should match returned post")
}
if posts.Order[2] != post2.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != post5.Id {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post1.Id {
t.Fatal("should match PrevPostId")
}
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post1.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Posts) != 1 {
t.Fatal("should match length of posts returned")
}
if posts.Order[0] != post5.Id {
t.Fatal("should match returned post")
}
if posts.NextPostId != "" {
t.Fatal("should match NextPostId")
}
if posts.PrevPostId != post4.Id {
t.Fatal("should match PrevPostId")
}
// more tests for next_post_id, prev_post_id, and order
// There are 12 posts composed of first 2 system messages and 10 created posts
post6 := th.CreatePost()
th.CreatePost() // post7
post8 := th.CreatePost()
post9 := th.CreatePost()
post10 := th.CreatePost()
// similar to '/posts?after=post2'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 8 || posts.Order[0] != post10.Id || posts.Order[7] != post3.Id {
t.Fatal("should return 8 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 0, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post5.Id || posts.Order[2] != post3.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post6.Id {
t.Fatal("should return post6.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3&page=1'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 1, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post8.Id || posts.Order[2] != post6.Id {
t.Fatal("should return 3 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post9.Id as NextPostId")
}
if posts.PrevPostId != post5.Id {
t.Fatal("should return post5.Id as PrevPostId")
}
// similar to '/posts?after=post2&per_page=3&page=2'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post2.Id, 2, 3, "")
CheckNoError(t, resp)
if len(posts.Order) != 2 || posts.Order[0] != post10.Id || posts.Order[1] != post9.Id {
t.Fatal("should return 2 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post8.Id {
t.Fatal("should return post8.Id as PrevPostId")
}
// similar to '/posts?after=post10'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post10.Id {
t.Fatal("should return post10.Id as PrevPostId")
}
// similar to '/posts?after=post10&page=1'
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, post10.Id, 1, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// similar to '/posts?after=non-existent-post'
nonExistentPostId := model.NewId()
posts, resp = Client.GetPostsAfter(th.BasicChannel.Id, nonExistentPostId, 0, 60, "")
CheckNoError(t, resp)
if len(posts.Order) != 0 {
t.Fatal("should return 0 post")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != nonExistentPostId {
t.Fatal("should return nonExistentPostId as PrevPostId")
}
}
func TestGetPostsForChannelAroundLastUnread(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
userId := th.BasicUser.Id
channelId := th.BasicChannel.Id
// 12 posts = 2 systems posts + 10 created posts below
post1 := th.CreatePost()
post2 := th.CreatePost()
post3 := th.CreatePost()
post4 := th.CreatePost()
th.CreatePost() // post5
post6 := th.CreatePost()
post7 := th.CreatePost()
post8 := th.CreatePost()
post9 := th.CreatePost()
post10 := th.CreatePost()
// All returned posts are all read by the user, since it's created by the user itself.
posts, resp := Client.GetPostsAroundLastUnread(userId, channelId, 20, 20)
CheckNoError(t, resp)
if len(posts.Order) != 12 {
t.Fatal("Should return 12 posts only since there's no unread post")
}
// Set channel member's last viewed to 0.
// All returned posts are latest posts as if all previous posts were already read by the user.
channelMember, err := th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = 0
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 20, 20)
CheckNoError(t, resp)
if len(posts.Order) != 12 {
t.Fatal("Should return 12 posts only since there's no unread post")
}
// get the first system post generated before the created posts above
posts, resp = Client.GetPostsBefore(th.BasicChannel.Id, post1.Id, 0, 2, "")
CheckNoError(t, resp)
systemPostId1 := posts.Order[1]
// Set channel member's last viewed before post1.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post1.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 5 || posts.Order[0] != post3.Id || posts.Order[4] != systemPostId1 {
t.Fatal("Should return 5 posts and match order")
}
if posts.NextPostId != post4.Id {
t.Fatal("should return post4.Id as NextPostId")
}
if posts.PrevPostId != "" {
t.Fatal("should return an empty PrevPostId")
}
// Set channel member's last viewed before post6.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post6.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 6 || posts.Order[0] != post8.Id || posts.Order[5] != post3.Id {
t.Fatal("Should return 6 posts and match order")
}
if posts.NextPostId != post9.Id {
t.Fatal("should return post8.Id as NextPostId")
}
if posts.PrevPostId != post2.Id {
t.Fatal("should return post2.Id as PrevPostId")
}
// Set channel member's last viewed before post10.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post10.CreateAt - 1
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 4 || posts.Order[0] != post10.Id || posts.Order[3] != post7.Id {
t.Fatal("Should return 4 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post6.Id {
t.Fatal("should return post6.Id as PrevPostId")
}
// Set channel member's last viewed equal to post10.
channelMember, err = th.App.Srv.Store.Channel().GetMember(channelId, userId)
require.Nil(t, err)
channelMember.LastViewedAt = post10.CreateAt
_, err = th.App.Srv.Store.Channel().UpdateMember(channelMember)
require.Nil(t, err)
th.App.Srv.Store.Post().InvalidateLastPostTimeCache(channelId)
posts, resp = Client.GetPostsAroundLastUnread(userId, channelId, 3, 3)
CheckNoError(t, resp)
if len(posts.Order) != 3 || posts.Order[0] != post10.Id || posts.Order[2] != post8.Id {
t.Fatal("Should return 3 posts and match order")
}
if posts.NextPostId != "" {
t.Fatal("should return an empty NextPostId")
}
if posts.PrevPostId != post7.Id {
t.Fatal("should return post7.Id as PrevPostId")
}
}