Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
864 lines
30 KiB
Go
864 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/mattermost/mattermost/server/public/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSaveScheduledPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
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(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()
|
|
team2 := th.CreateTeam()
|
|
th.LinkUserToTeam(th.BasicUser, team1)
|
|
th.LinkUserToTeam(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()
|
|
defer th.TearDown()
|
|
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()
|
|
_, 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()
|
|
_, 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()
|
|
_, 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()
|
|
_, 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(th.Context, 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()
|
|
defer th.TearDown()
|
|
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 ot be allowed to updated a scheduled post not belonging to the user", 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, th.BasicUser2.Id, createdScheduledPost, user1ConnID)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusForbidden, appErr.StatusCode)
|
|
require.Nil(t, updatedScheduledPost)
|
|
})
|
|
|
|
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.Equal(t, model.ScheduledPostErrorUnknownError, createdScheduledPost.ErrorCode)
|
|
})
|
|
|
|
t.Run("should be able to update scheduled posts for channels user does not belong to", func(t *testing.T) {
|
|
channel := th.CreateChannel(th.Context, th.BasicTeam)
|
|
th.AddUserToChannel(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(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)
|
|
})
|
|
}
|
|
|
|
func TestDeleteScheduledPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic()
|
|
defer th.TearDown()
|
|
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 not allow deleting someone else's scheduled post", 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.BasicUser2.Id, scheduledPost.Id, "connection_id")
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, deletedScheduledPost)
|
|
|
|
// try to fetch it again
|
|
reFetchedScheduledPost, err := th.Server.Store().ScheduledPost().Get(scheduledPost.Id)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, reFetchedScheduledPost)
|
|
require.Equal(t, createdScheduledPost.Id, reFetchedScheduledPost.Id)
|
|
require.Equal(t, createdScheduledPost.Message, reFetchedScheduledPost.Message)
|
|
})
|
|
|
|
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()
|
|
defer th.TearDown()
|
|
|
|
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
|
|
}
|
|
})
|
|
}
|