* Create scheduled post api (#27920)

* Added migration files for Postgres

* Added migrations for MySQL

* Added store method

* Added API and store tests

* Renamed migration after syncing with master

* Added app layer tests

* API is ready

* API is ready

* API is ready

* Renamed migration after syncing with master

* Updated migration list

* Fixed retry layer tests

* Allowed posts with empty messages

* Review fixes

* Reverted an incorrect change

* Renamed migration and fixed ID assignment

* CI

* Send post button changes (#28019)

* added Split button

* WIP

* Added core menu options

* WIP

* WIP

* WIP

* Handled displaying error in creating scheduled post

* lint fixes

* webapp i18n fix

* Review fixes

* Fixed a webapp test

* A few more fixes

* Removed a duplicate comment

* Scheduled post job (#28088)

* Added the job function

* Added query for fetching scheduled posts for pricessing

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* Reafactoring of scheduled post job

* Lint fixes

* Updated i18n files

* FInishing touches

* Added tests for GetScheduledPosts

* Added tests for PermanentlyDeleteScheduledPosts

* Updated all layer

* Some changes as discussed with team

* Added tests for UpdatedScheduledPost

* Code review refactoring

* Added job test

* MM-60120 - Custom time selection (#28120)

* Added a common date time picker modal and used it for post reminder

* Added a common date time picker modal and used it for post reminderggp

* Added modal for custom schedule time and fixed TZ issue

* WIP

* Removed event from useSubmit hook

* Removed event from useSubmit hook

* Added timezone handling

* fixed type error

* Updated i18n strings

* Minor cleanup

* updated snapshots

* review fixes

* Handled event

* Supported for having a DM thread open in RHS while in a regular channel

* Review fixes

* MM-60136 - Scheduled messages tab (#28133)

* WIP

* WIP

* Created Tabs and Tab wrapper with added styling

* Added API to get scheduled posts

* WIP

* Displated scheduled post count

* i18n fix

* Added tests

* Handled asetting active tab absed on URL:

* Reverted unintended change

* Added API to client ad OpenAPI specs

* Renamed file

* Adding fileinfo to schedule posts

* Partial review fixes

* Made get scheduled post API return posts by teamID

* review fixes

* Moved scheduled post redux code to MM-redux package

* Usedd selector factory

* WIP:

* WIP:

* Lint fix

* Fixed an incorrect openapi spec file

* Removed redundent permission check

* Clreaed scheduled post data on logout

* Removed unused i18n string:

* lint fix

* Render scheduled posts (#28208)

* WIP

* WIP

* Created Tabs and Tab wrapper with added styling

* Added API to get scheduled posts

* WIP

* Displated scheduled post count

* i18n fix

* Added tests

* Handled asetting active tab absed on URL:

* Reverted unintended change

* Added API to client ad OpenAPI specs

* Renamed file

* Created common component for draft list item

* WIP

* WIP

* Adding fileinfo to schedule posts

* Basic rendering

* Added count badge to tabs

* WIP

* Made the Drafts LHS iteam appear if no drafts exist but scheduled posts do

* Fixed icon size

* Partial review fixes

* Made get scheduled post API return posts by teamID

* Handled initial vs team switch load

* Displayed scheduled date in panel header

* Added error message and error indiocator

* WIP

* review fixes

* WIP Adding error reason tag

* Added error codes

* Moved scheduled post redux code to MM-redux package

* Usedd selector factory

* WIP:

* WIP:

* Lint fix

* Fixed an incorrect openapi spec file

* Removed redundent permission check

* Clreaed scheduled post data on logout

* Removed unused i18n string:

* lint fix

* Opened rescheduling modal

* Updated graphic for empty state of schduled post list

* Added delete scheduled post option and modal

* Badge and timezone fix

* WIP:

* Added send now confirmation modal

* lint

* Webapp i18n fix

* Fixed webapp test

* Fixed a bug where DM/GM scheduled posts weren't immideatly showing up in UI

* Minor fixes

* WIP

* Review fixes

* Review fixes

* Optimisations

* Fixed reducer name

* Moment optimizatin

* Updated route check

* MM-60144 - added API to update a scheduled post (#28248)

* WIP

* Added api and ap layer for update scheduled post ̛̦̄

* Added API to OpenAI specs, Go client and TS client

* removed permissio check

* Added tests

* Fixed tests

* Added PreUpdate method on scheduled post model

* MM-60131 - Reschedule post integration (#28281)

* Handled rescheduling post in webapp

* Added error handling

* MM-60146 - Delete scheduled post api (#28265)

* WIP

* Added api and ap layer for update scheduled post ̛̦̄

* Added API to OpenAI specs, Go client and TS client

* removed permissio check

* Added tests

* Fixed tests

* Added PreUpdate method on scheduled post model

* Added delete scheduled post API

* Added API to Go client and OpenAPI specs

* Added API to TS client

* Added tests

* CI

* Rmeoved two incorrect code comments

* MM-60653 - Integrated delete scheduled post API (#28296)

* Integrated delete scheduled apost API

* Lint fix

* Review fixes

* Excluded draft checks from scheduled posts (#28370)

* Excluded draft checks from scheduled posts

* Added a removed todo

* MM-60125 - Scheduled post channel indicator (#28320)

* Integrated delete scheduled apost API

* Lint fix

* Added state for storing scheduled posts by channel ID

* Refactored redux store to store scheudled posts by ID, thens tore IDs everywhere

* Refactored redux store to store scheudled posts by ID, thens tore IDs everywhere

* WIP

* Added scheduled post indiocator

* Handled single and multiple scheudled posts

* Review fixes

* Fixed styling and handled center channel, RHS and threads view

* Lint fix

* i18n fix

* Fixed a cycling dependency

* Lint fix

* Added some more comments

* Updated styling

* Review fixes

* Added common component for remote user time and scheduled post indicator

* Updated scheduled post count

* Minor change

* Moved CSS code around

* Fixed a bug where files in scheduled post didn't show up until refresh (#28359)

---------

Co-authored-by: Daniel Espino García <larkox@gmail.com>

* Scheduled post config (#28485)

* Added config

* Added config on server and webapp side

* Added config check in server and webapp

* Added license check

* Added license check

* Added placeholder help text

* Added license check to job

* Fixed job test

* Review fixes

* Updated English text

* Review fixes

* MM-60118 - Added index on ScheduledPosts table (#28579)

* Added index

* Updated indexes

* Scheduled posts misc fixes (#28625)

* Added detailed logging for scheduled post job

* Limited scheduled posts processing to 24 hours

* Marked old scheduled posts as unable to send

* Added t5ests

* converted some logs to trace level

* Fixed a bug causing error message to show up on deleting a scheduled post in a deleted thread (#28630)

* Fixed scheduled posts link in RHS (#28659)

* Fixed scheduled posts link in RHS

* Review fixes

* Fix permission name in scheduled posts by team (#28580)

* Fix permission name

* fix wording

---------

Co-authored-by: Mattermost Build <build@mattermost.com>

* FIxed width of generic modal header to fix browser channel modal (#28639)

* Only consider error-free scheduled posts for indicator in channel and RHS (#28683)

* Show only errro free scheudled posts in post box indicator

* Fixed a bug to handle no scheduled posts

* Fixed draft and scheudled post UI in mobile view (#28680)

* MM-60873 and MM-60872 - Fixed a bug with updating scheduled posts (#28656)

* Fixed a bug with updating scheduled posts

* Better selectors

* MOved shceuled post message length validation to app layer

* MM-60732 - Scheduled posts channel link now takes you to the first scheduled post in channel/thread in list (#28768)

* Ordered scheudle dposts by schgeudled at nad create at

* Ordered in client

* Added scroll to target

* Removed classname prop

* Fixed tests

* Added doc

* Import fix

* MM-60961 - Fixed a bug where API used incoming create at date for scheduled post (#28703)

* Fixed a bug where API used incoming create at date for scheduled post

* Stopped sending created at value for scheduled post

* MM-60785 - Fixed a bug where scheduled posts of channel we are no longer member of didn't show up (#28637)

* Fixed a bug where scheduled posts of channel we are no longer member of didn't show up

* Added a comment

* CI

* Used data loader to optimise laoding missing channels

* Minor refactoring

* MM-60963 - Added common checks for post and scheduled posts (#28713)

* Added commen checks for post and scheuled posts

* Sanitised scheduled posts

* Fixed tests

* Splitted post checks into app and context functions

* Added checks on scheduiled posts job as well:

* i18n fix

* Fixed a test

* Renamed a func

* removed duplicate check

* Scheduled posts UI fixes (#28828)

* Fixed send button and time picker borders

* Fixed center alignment of time picker

* Removed on for today and tomorrow

* Lint fix

* Date time modal hover state fix

* Badge fix

* Fixed a mnerge issue

* Scheduled Post send now and add schedule on draft (#28851)

* Added send now option on scheduled posts

* Minor refactoring

* WIP

* WIP

* WIP

* Lint fix

* i18n fix

* Snapshot update

* Review fixes

* Scheduled post inline editing (#28893)

* Added send now option on scheduled posts

* Minor refactoring

* WIP

* WIP

* WIP

* Lint fix

* i18n fix

* Snapshot update

* Displayed editing component in scheduled post

* Added handling for updating scheduled post

* Handle events

* Fixed escape key issue in scheudled post editing

* Fixes

* Displayed error message for editing error

* Don't show mention warning

* Handled dev mode (#28918)

* MInor fixes

* client fix

* Fixes

* CI

* Removed dev mode behaviour temperorily (#29008)

---------

Co-authored-by: Daniel Espino García <larkox@gmail.com>
Co-authored-by: Eva Sarafianou <eva.sarafianou@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Harshil Sharma
2024-11-04 11:39:35 +05:30
коммит произвёл GitHub
родитель aaf9234c8e
Коммит e281b3f37e
135 изменённых файлов: 7792 добавлений и 684 удалений

663
server/channels/app/scheduled_post_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,663 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"net/http"
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/require"
)
func TestSaveScheduledPost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
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)
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)
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)
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)
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)
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)
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)
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)
require.NotNil(t, appErr)
require.Nil(t, createdScheduledPost)
})
}
func TestGetUserTeamScheduledPosts(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
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)
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)
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)
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)
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)
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)
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)
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)
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)
})
}
func TestUpdateScheduledPost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
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)
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)
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)
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)
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)
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)
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)
})
}
func TestDeleteScheduledPost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
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)
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)
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)
})
}