Files
team/server/channels/app/scheduled_post_test.go
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

870 lines
30 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"encoding/json"
"net/http"
"testing"
"time"
"github.com/hanzoteam/server/server/public/model"
"github.com/stretchr/testify/require"
)
func TestSaveScheduledPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
user1ConnID := model.NewId()
t.Run("base case", func(t *testing.T) {
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
})
t.Run("cannot save invalid scheduled post", func(t *testing.T) {
scheduledPost := &model.ScheduledPost{
// a completely empty scheduled post
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
t.Run("cannot save post scheduled in the past", func(t *testing.T) {
userId := model.NewId()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: "channel_id",
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() - 100000, // 100 seconds in the past
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
t.Run("cannot scheduled post in a channel you don't belong to", func(t *testing.T) {
userId := model.NewId()
// we didn't create any channel member entry, so the user doesn't
// belong to the channel
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: "channel_id",
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
t.Run("cannot schedule post in an archived channel", func(t *testing.T) {
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
err = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
t.Run("can scheduled multiple posts in the same channel", func(t *testing.T) {
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
scheduledPost.Message = "this is a second scheduled post"
scheduledPost.Id = model.NewId()
createdScheduledPost, appErr = th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
})
t.Run("cannot save an empty post", func(t *testing.T) {
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
t.Run("cannot save scheduled post in restricted DM", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageTeam
})
// Create a DM channel between two users who don't share a team
dmChannel := th.CreateDmChannel(t, th.BasicUser2)
// Ensure the two users do not share a team
teams, err := th.App.GetTeamsForUser(th.BasicUser.Id)
require.Nil(t, err)
for _, team := range teams {
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.SystemAdminUser.Id)
require.Nil(t, teamErr)
}
teams, err = th.App.GetTeamsForUser(th.BasicUser2.Id)
require.Nil(t, err)
for _, team := range teams {
teamErr := th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser2.Id, th.SystemAdminUser.Id)
require.Nil(t, teamErr)
}
// Create separate teams for each user
team1 := th.CreateTeam(t)
team2 := th.CreateTeam(t)
th.LinkUserToTeam(t, th.BasicUser, team1)
th.LinkUserToTeam(t, th.BasicUser2, team2)
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: dmChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 1000,
}
_, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Equal(t, "app.save_scheduled_post.restricted_dm.error", appErr.Id)
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
// Reset config
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.RestrictDirectMessage = model.DirectMessageAny
})
})
}
func TestGetUserTeamScheduledPosts(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
user1ConnID := model.NewId()
t.Run("should get created scheduled posts", func(t *testing.T) {
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost1)
// this wait is to ensure scheduled post 2 and 1 have some time gap between the two
// to ensure a deterministic ordering
time.Sleep(1 * time.Second)
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a second scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost2, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost2, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost2)
defer func() {
_ = th.Server.Store().ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPost1.Id, createdScheduledPost2.Id})
}()
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId)
require.Nil(t, appErr)
require.Equal(t, 2, len(retrievedScheduledPosts))
// more recently created scheduled post appears first in the list
require.Equal(t, createdScheduledPost1.Id, retrievedScheduledPosts[0].Id)
require.Equal(t, createdScheduledPost2.Id, retrievedScheduledPosts[1].Id)
})
t.Run("should handle no scheduled posts", func(t *testing.T) {
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId)
require.Nil(t, appErr)
require.Equal(t, 0, len(retrievedScheduledPosts))
})
t.Run("should restrict to specified teams and DM/GMs", func(t *testing.T) {
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost1)
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a second scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost2, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost2, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost2)
defer func() {
_ = th.Server.Store().ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPost1.Id, createdScheduledPost2.Id})
}()
// create a dummy team
secondTeam := th.CreateTeam(t)
_, appErr = th.App.JoinUserToTeam(th.Context, secondTeam, th.BasicUser, th.BasicUser.Id)
require.Nil(t, appErr)
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, secondTeam.Id)
require.Nil(t, appErr)
require.Equal(t, 0, len(retrievedScheduledPosts))
})
t.Run("should not return scheduled posts from DMs and GMs when teamId is specified", func(t *testing.T) {
// start a DM between BasicUser1 and BasicUser2
dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
// create a GM. Since a GM needs at least 3 users, we'll create a third user first
thirdUser := th.CreateUser(t)
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, thirdUser, thirdUser.Id)
require.Nil(t, appErr)
gm, appErr := th.App.CreateGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, thirdUser.Id}, th.BasicUser.Id)
require.Nil(t, appErr)
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: dm.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost1)
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: gm.Id,
Message: "this is a second scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost2, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost2, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost2)
defer func() {
_ = th.Server.Store().ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPost1.Id, createdScheduledPost2.Id})
}()
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId)
require.Nil(t, appErr)
require.Equal(t, 0, len(retrievedScheduledPosts))
})
t.Run("should return scheduled posts from DMs and GMs when teamId is empty", func(t *testing.T) {
// start a DM between BasicUser1 and BasicUser2
dm, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, th.BasicUser2.Id)
require.Nil(t, appErr)
// create a GM. Since a GM needs at least 3 users, we'll create a third user first
thirdUser := th.CreateUser(t)
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, thirdUser, thirdUser.Id)
require.Nil(t, appErr)
gm, appErr := th.App.CreateGroupChannel(th.Context, []string{th.BasicUser.Id, th.BasicUser2.Id, thirdUser.Id}, th.BasicUser.Id)
require.Nil(t, appErr)
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: dm.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost1)
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis() + 100,
UserId: th.BasicUser.Id,
ChannelId: gm.Id,
Message: "this is a second scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost2, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost2, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost2)
defer func() {
_ = th.Server.Store().ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPost1.Id, createdScheduledPost2.Id})
}()
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, "")
require.Nil(t, appErr)
require.Equal(t, 2, len(retrievedScheduledPosts))
// more recently created scheduled post appears first in the list
require.Equal(t, createdScheduledPost1.Id, retrievedScheduledPosts[0].Id)
require.Equal(t, createdScheduledPost2.Id, retrievedScheduledPosts[1].Id)
})
t.Run("should not be able to fetch scheduled posts for team user doesn't belong to", func(t *testing.T) {
// create a dummy team
team := th.CreateTeam(t)
_, appErr := th.App.JoinUserToTeam(th.Context, team, th.BasicUser, th.BasicUser.Id)
require.Nil(t, appErr)
// create a channel in this team
channel := th.CreateChannel(t, team)
// create scheduled post
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost1)
// verify we are able to fetch this scheduled post
retrievedScheduledPosts, appErr := th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, team.Id)
require.Nil(t, appErr)
require.Equal(t, 1, len(retrievedScheduledPosts))
appErr = th.App.RemoveUserFromTeam(th.Context, team.Id, th.BasicUser.Id, th.BasicUser.Id)
require.Nil(t, appErr)
// now we should not be able to fetch this scheduled post
retrievedScheduledPosts, appErr = th.App.GetUserTeamScheduledPosts(th.Context, th.BasicUser.Id, th.BasicChannel.TeamId)
require.Nil(t, appErr)
require.Equal(t, 0, len(retrievedScheduledPosts))
})
}
func TestUpdateScheduledPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
user1ConnID := model.NewId()
t.Run("base case", func(t *testing.T) {
// first we'll create a scheduled post
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
// now we'll try updating it
newScheduledAtTime := model.GetMillis() + 9999999
createdScheduledPost.ScheduledAt = newScheduledAtTime
createdScheduledPost.Message = "Updated Message!!!"
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, userId, createdScheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, updatedScheduledPost)
require.Equal(t, newScheduledAtTime, updatedScheduledPost.ScheduledAt)
require.Equal(t, "Updated Message!!!", updatedScheduledPost.Message)
})
t.Run("should only allow updating limited fields", func(t *testing.T) {
// first we'll create a scheduled post
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
// now we'll try updating it
newUpdatedAt := model.GetMillis() + 1000000
createdScheduledPost.UpdateAt = newUpdatedAt // this should be overridden by the actual update time
createdScheduledPost.Message = "Updated Message" // this will update
newChannelId := model.NewId()
createdScheduledPost.ChannelId = newChannelId // this won't update
newCreateAt := model.GetMillis() + 5000000
createdScheduledPost.CreateAt = newCreateAt // this won't update
createdScheduledPost.FileIds = []string{model.NewId(), model.NewId()}
createdScheduledPost.ErrorCode = model.ScheduledPostErrorUnknownError
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, userId, createdScheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
require.NotEqual(t, newUpdatedAt, updatedScheduledPost.UpdateAt)
require.Equal(t, "Updated Message", updatedScheduledPost.Message)
require.NotEqual(t, newChannelId, updatedScheduledPost.ChannelId)
require.NotEqual(t, newCreateAt, updatedScheduledPost.CreateAt)
require.Equal(t, 2, len(updatedScheduledPost.FileIds))
require.Empty(t, createdScheduledPost.ErrorCode)
require.Empty(t, updatedScheduledPost.ErrorCode)
require.Zero(t, updatedScheduledPost.ProcessedAt)
})
t.Run("should be able to update scheduled posts for channels user does not belong to", func(t *testing.T) {
channel := th.CreateChannel(t, th.BasicTeam)
th.AddUserToChannel(t, th.BasicUser, channel)
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: channel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
// now user will leave the channel
appErr = th.RemoveUserFromChannel(t, th.BasicUser, channel)
require.Nil(t, appErr)
createdScheduledPost.Message = "Updated message"
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, th.BasicUser.Id, createdScheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, updatedScheduledPost)
require.Equal(t, updatedScheduledPost.Message, "Updated message")
})
t.Run("should not be able to update a non existing scheduled post", func(t *testing.T) {
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a scheduled post",
},
Id: model.NewId(),
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, th.BasicUser.Id, scheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Nil(t, updatedScheduledPost)
})
t.Run("burn on read scheduled post - verify type is preserved on create and update", func(t *testing.T) {
// Create a scheduled post with burn on read type
userId := model.NewId()
channel, err := th.GetSqlStore().Channel().Save(th.Context, &model.Channel{
Name: model.NewId(),
DisplayName: "Channel",
Type: model.ChannelTypeOpen,
}, 1000)
require.NoError(t, err)
_, err = th.GetSqlStore().Channel().SaveMember(th.Context, &model.ChannelMember{
ChannelId: channel.Id,
UserId: userId,
NotifyProps: model.GetDefaultChannelNotifyProps(),
SchemeGuest: false,
SchemeUser: true,
})
require.NoError(t, err)
defer func() {
_ = th.GetSqlStore().Channel().Delete(channel.Id, model.GetMillis())
_ = th.GetSqlStore().Channel().RemoveMember(th.Context, channel.Id, userId)
}()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: channel.Id,
Message: "this is a burn on read scheduled post",
Type: model.PostTypeBurnOnRead,
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
require.Equal(t, model.PostTypeBurnOnRead, createdScheduledPost.Type)
fetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(createdScheduledPost.Id)
require.NoError(t, err)
require.NotNil(t, fetchedScheduledPost)
require.Equal(t, model.PostTypeBurnOnRead, fetchedScheduledPost.Type)
createdScheduledPost.Message = "Updated burn on read message"
createdScheduledPost.ScheduledAt = model.GetMillis() + 200000
// Try to change the type - it should NOT change
createdScheduledPost.Type = ""
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, userId, createdScheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, updatedScheduledPost)
// Verify the type is NOT changed - it should still be burn on read
require.Equal(t, model.PostTypeBurnOnRead, updatedScheduledPost.Type)
require.Equal(t, "Updated burn on read message", updatedScheduledPost.Message)
// Fetch again from store to verify the type is still burn on read in the database
reFetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(createdScheduledPost.Id)
require.NoError(t, err)
require.NotNil(t, reFetchedScheduledPost)
require.Equal(t, model.PostTypeBurnOnRead, reFetchedScheduledPost.Type)
// Try another update with a different type value - verify the type is still NOT changed
existingPost, err := th.Server.Store().ScheduledPost().Get(createdScheduledPost.Id)
require.NoError(t, err)
existingPost.Message = "Another update attempt"
existingPost.ScheduledAt = model.GetMillis() + 300000
// Try to change the type to a different value - verify it doesn't change
existingPost.Type = "some_other_type"
updatedScheduledPost2, appErr := th.App.UpdateScheduledPost(th.Context, userId, existingPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, updatedScheduledPost2)
// Verify the type remains burn on read even when we try to change it to a different value
require.Equal(t, model.PostTypeBurnOnRead, updatedScheduledPost2.Type)
// Final verification from store
finalFetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(createdScheduledPost.Id)
require.NoError(t, err)
require.NotNil(t, finalFetchedScheduledPost)
require.Equal(t, model.PostTypeBurnOnRead, finalFetchedScheduledPost.Type)
})
}
func TestDeleteScheduledPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
user1ConnID := model.NewId()
t.Run("base case", func(t *testing.T) {
// first we'll create a scheduled post
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
fetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(scheduledPost.Id)
require.NoError(t, err)
require.NotNil(t, fetchedScheduledPost)
require.Equal(t, createdScheduledPost.Id, fetchedScheduledPost.Id)
require.Equal(t, createdScheduledPost.Message, fetchedScheduledPost.Message)
// now we'll delete it
var deletedScheduledPost *model.ScheduledPost
deletedScheduledPost, appErr = th.App.DeleteScheduledPost(th.Context, th.BasicUser.Id, scheduledPost.Id, "connection_id")
require.Nil(t, appErr)
require.NotNil(t, deletedScheduledPost)
require.Equal(t, scheduledPost.Id, deletedScheduledPost.Id)
require.Equal(t, scheduledPost.Message, deletedScheduledPost.Message)
// try to fetch it again
reFetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(scheduledPost.Id)
require.Error(t, err) // This will produce error as the row doesn't exist
require.Nil(t, reFetchedScheduledPost)
})
t.Run("should producer error when deleting non existing scheduled post", func(t *testing.T) {
var deletedScheduledPost *model.ScheduledPost
deletedScheduledPost, appErr := th.App.DeleteScheduledPost(th.Context, th.BasicUser.Id, model.NewId(), "connection_id")
require.NotNil(t, appErr)
require.Nil(t, deletedScheduledPost)
})
}
func TestPublishScheduledPostEvent(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
userID := th.BasicUser.Id
messages, closeWS := connectFakeWebSocket(t, th, userID, "", []model.WebsocketEventType{model.WebsocketScheduledPostCreated})
defer closeWS()
t.Run("should publish ws event when scheduledPost is valid", func(t *testing.T) {
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userID,
ChannelId: th.BasicChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000,
}
th.App.PublishScheduledPostEvent(th.Context, model.WebsocketScheduledPostCreated, scheduledPost, "fake_connection_id")
received := <-messages
require.Equal(t, model.WebsocketScheduledPostCreated, received.EventType())
require.Equal(t, userID, received.GetBroadcast().UserId)
scheduledPostJSON, err := json.Marshal(scheduledPost)
require.NoError(t, err)
require.Equal(t, string(scheduledPostJSON), received.GetData()["scheduledPost"])
})
t.Run("should handle nil scheduledPost scenario", func(t *testing.T) {
// Drain any existing messages
drained := false
for !drained {
select {
case <-messages:
default:
drained = true
}
}
th.App.PublishScheduledPostEvent(th.Context, model.WebsocketScheduledPostCreated, nil, "fake_connection_id")
select {
case msg := <-messages:
t.Errorf("Expected no message, but got one: %+v", msg)
case <-time.After(100 * time.Millisecond):
// there was no message sent to the channel, so test is successful
}
})
}