mattermost-community-enterp.../channels/store/storetest/scheduled_post_store.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
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>
2025-12-17 23:59:07 +09:00

554 lines
19 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestScheduledPostStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("CreateScheduledPost", func(t *testing.T) { testCreateScheduledPost(t, rctx, ss, s) })
t.Run("GetPendingScheduledPosts", func(t *testing.T) { testGetScheduledPosts(t, rctx, ss, s) })
t.Run("PermanentlyDeleteScheduledPosts", func(t *testing.T) { testPermanentlyDeleteScheduledPosts(t, rctx, ss, s) })
t.Run("UpdatedScheduledPost", func(t *testing.T) { testUpdatedScheduledPost(t, rctx, ss, s) })
t.Run("UpdateOldScheduledPosts", func(t *testing.T) { testUpdateOldScheduledPosts(t, rctx, ss, s) })
t.Run("PermanentDeleteByUser", func(t *testing.T) { testPermanentDeleteScheduledPostsByUser(t, rctx, ss, s) })
}
func testCreateScheduledPost(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
channel := &model.Channel{
TeamId: "team_id_1",
Type: model.ChannelTypeOpen,
Name: "channel_name",
DisplayName: "Channel Name",
}
createdChannel, err := ss.Channel().Save(rctx, channel, 1000)
assert.NoError(t, err)
defer func() {
_ = ss.Channel().PermanentDelete(rctx, createdChannel.Id)
}()
t.Run("base case", func(t *testing.T) {
userId := model.NewId()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
defer func() {
_ = ss.ScheduledPost().PermanentlyDeleteScheduledPosts([]string{createdScheduledPost.Id})
}()
scheduledPostsFromDatabase, err := ss.ScheduledPost().GetScheduledPostsForUser(userId, "team_id_1")
assert.NoError(t, err)
require.Equal(t, 1, len(scheduledPostsFromDatabase))
assert.Equal(t, scheduledPost.Id, scheduledPostsFromDatabase[0].Id)
})
t.Run("scheduling in past SHOULD BE allowed", func(t *testing.T) {
// this is only allowed in store layer and user won't be able to do so as the checks
// in app layer would stop them.
userId := model.NewId()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() - 100000, // 100 seconds in the past
}
_, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
defer func() {
_ = ss.ScheduledPost().PermanentlyDeleteScheduledPosts([]string{scheduledPost.Id})
}()
})
}
func testGetScheduledPosts(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("should handle no scheduled posts exist", func(t *testing.T) {
apr2022 := time.Date(2100, time.April, 1, 1, 0, 0, 0, time.UTC)
afterTime := time.Date(2100, time.March, 1, 1, 0, 0, 0, time.UTC)
scheduledPosts, err := ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillisForTime(apr2022), model.GetMillisForTime(afterTime), "", 10)
assert.NoError(t, err)
assert.Equal(t, 0, len(scheduledPosts))
})
t.Run("base case", func(t *testing.T) {
// creating some sample scheduled posts
// Create a time object for 1 January 2100, 1 AM
jan2100 := time.Date(2100, time.January, 1, 1, 0, 0, 0, time.UTC)
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillisForTime(jan2100),
}
createdScheduledPost1, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost1)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost1.Id)
feb2100 := time.Date(2100, time.February, 1, 1, 0, 0, 0, time.UTC)
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillisForTime(feb2100),
}
createdScheduledPost2, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost2)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost2.Id)
mar2100 := time.Date(2100, time.March, 1, 1, 0, 0, 0, time.UTC)
scheduledPost3 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillisForTime(mar2100),
}
createdScheduledPost3, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost3)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost3.Id)
defer func() {
_ = ss.ScheduledPost().PermanentlyDeleteScheduledPosts([]string{
createdScheduledPost1.Id,
createdScheduledPost2.Id,
createdScheduledPost3.Id,
})
}()
apr2022 := time.Date(2100, time.April, 1, 1, 0, 0, 0, time.UTC)
afterTime := time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
scheduledPosts, err := ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillisForTime(apr2022), model.GetMillisForTime(afterTime), "", 10)
assert.NoError(t, err)
assert.Equal(t, 3, len(scheduledPosts))
mar2100midnight := time.Date(2100, time.March, 1, 0, 0, 0, 0, time.UTC)
afterTime = time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
scheduledPosts, err = ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillisForTime(mar2100midnight), model.GetMillisForTime(afterTime), "", 10)
assert.NoError(t, err)
assert.Equal(t, 2, len(scheduledPosts))
jan2100Midnight := time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
afterTime = time.Date(2099, time.December, 31, 0, 0, 0, 0, time.UTC)
scheduledPosts, err = ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillisForTime(jan2100Midnight), model.GetMillisForTime(afterTime), "", 10)
assert.NoError(t, err)
assert.Equal(t, 0, len(scheduledPosts))
})
}
func testPermanentlyDeleteScheduledPosts(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
scheduledPostIDs := []string{}
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
scheduledPostIDs = append(scheduledPostIDs, createdScheduledPost.Id)
scheduledPost = &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post 2",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err = ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
scheduledPostIDs = append(scheduledPostIDs, createdScheduledPost.Id)
scheduledPost = &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post 3",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err = ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
scheduledPostIDs = append(scheduledPostIDs, createdScheduledPost.Id)
scheduledPost = &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: model.NewId(),
ChannelId: model.NewId(),
Message: "this is a scheduled post 4",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err = ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
scheduledPostIDs = append(scheduledPostIDs, createdScheduledPost.Id)
// verify 4 scheduled posts exist
scheduledPosts, err := ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillis()+50000000, model.GetMillis()-100000000, "", 10)
assert.NoError(t, err)
assert.Equal(t, 4, len(scheduledPosts))
// now we'll delete all scheduled posts
err = ss.ScheduledPost().PermanentlyDeleteScheduledPosts(scheduledPostIDs)
assert.NoError(t, err)
// now there should be no posts
scheduledPosts, err = ss.ScheduledPost().GetPendingScheduledPosts(model.GetMillis()+50000000, model.GetMillis()-100000000, "", 10)
assert.NoError(t, err)
assert.Equal(t, 0, len(scheduledPosts))
}
func testUpdatedScheduledPost(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
channel := &model.Channel{
TeamId: "team_id_1",
Type: model.ChannelTypeOpen,
Name: "channel_name",
DisplayName: "Channel Name",
}
createdChannel, err := ss.Channel().Save(rctx, channel, 1000)
assert.NoError(t, err)
defer func() {
_ = ss.Channel().PermanentDelete(rctx, createdChannel.Id)
}()
t.Run("it should update only limited fields", func(t *testing.T) {
userId := model.NewId()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis(),
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
// now we'll update the scheduled post
updateTimestamp := model.GetMillis()
fileID1 := model.NewId()
fileID2 := model.NewId()
newScheduledAt := model.GetMillis()
newUserId := model.NewId()
updateSchedulePost := &model.ScheduledPost{
Id: createdScheduledPost.Id,
ScheduledAt: newScheduledAt,
ErrorCode: "test_error_code",
Draft: model.Draft{
CreateAt: model.GetMillis(),
Message: "updated message",
UpdateAt: updateTimestamp,
UserId: newUserId, // this should not update
ChannelId: model.NewId(), // this should not update
FileIds: []string{fileID1, fileID2},
Priority: model.StringInterface{
"priority": "urgent",
"requested_ack": false,
"persistent_notifications": false,
},
},
}
err = ss.ScheduledPost().UpdatedScheduledPost(updateSchedulePost)
assert.NoError(t, err)
// now we'll get it and verify that intended fields updated and other fields did not
userScheduledPosts, err := ss.ScheduledPost().GetScheduledPostsForUser(userId, channel.TeamId)
assert.NoError(t, err)
assert.Equal(t, 1, len(userScheduledPosts))
// fields that should have changed
assert.Equal(t, newScheduledAt, userScheduledPosts[0].ScheduledAt)
assert.Equal(t, "test_error_code", userScheduledPosts[0].ErrorCode)
assert.Equal(t, "updated message", userScheduledPosts[0].Message)
assert.Equal(t, 2, len(userScheduledPosts[0].FileIds))
assert.Equal(t, "urgent", userScheduledPosts[0].Priority["priority"])
assert.Equal(t, false, userScheduledPosts[0].Priority["requested_ack"])
assert.Equal(t, false, userScheduledPosts[0].Priority["persistent_notifications"])
// fields that should not have changed. Checking them against the original value
assert.Equal(t, createdScheduledPost.Id, userScheduledPosts[0].Id)
assert.Equal(t, userId, userScheduledPosts[0].UserId)
assert.Equal(t, channel.Id, userScheduledPosts[0].ChannelId)
})
t.Run("it should update old scheduled post", func(t *testing.T) {
userId := model.NewId()
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() - (24 * 60 * 60 * 1000), // 1 day in the past
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
// now we'll update the scheduled post
now := model.GetMillis()
scheduledPost.ErrorCode = model.ScheduledPostErrorUnknownError
err = ss.ScheduledPost().UpdatedScheduledPost(scheduledPost)
assert.NoError(t, err)
updatedScheduledPost, err := ss.ScheduledPost().Get(scheduledPost.Id)
assert.NoError(t, err)
assert.LessOrEqual(t, now, updatedScheduledPost.ProcessedAt)
assert.Equal(t, model.ScheduledPostErrorUnknownError, updatedScheduledPost.ErrorCode)
})
}
func testUpdateOldScheduledPosts(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
setupScheduledPosts := func(baseTime int64, userId, teamId string) func() {
channel := &model.Channel{
TeamId: teamId,
Type: model.ChannelTypeOpen,
Name: "channel_name",
DisplayName: "Channel Name",
}
createdChannel, err := ss.Channel().Save(rctx, channel, 1000)
assert.NoError(t, err)
// Scheduled post 1
scheduledPost1 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is a scheduled post",
},
ScheduledAt: baseTime + 86400000, // 1 day in the future
}
createdScheduledPost1, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost1)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost1.Id)
time.Sleep(100 * time.Millisecond)
// Scheduled post 2
scheduledPost2 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is second scheduled post",
},
ScheduledAt: baseTime + (2 * 86400000), // 2 days in the future
}
createdScheduledPost2, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost2)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost2.Id)
time.Sleep(100 * time.Millisecond)
// Scheduled post 3
scheduledPost3 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is third scheduled post",
},
ScheduledAt: baseTime + (3 * 86400000), // 3 days in the future
}
createdScheduledPost3, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost3)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost3.Id)
time.Sleep(100 * time.Millisecond)
// Scheduled post 4
scheduledPost4 := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: createdChannel.Id,
Message: "this is fourth scheduled post",
},
ScheduledAt: baseTime + (4 * 86400000), // 4 days in the future
}
createdScheduledPost4, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost4)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost4.Id)
return func() {
_ = ss.ScheduledPost().PermanentlyDeleteScheduledPosts([]string{
createdScheduledPost1.Id,
createdScheduledPost2.Id,
createdScheduledPost3.Id,
createdScheduledPost4.Id,
})
_ = ss.Channel().PermanentDelete(rctx, createdChannel.Id)
}
}
t.Run("should update only old scheduled posts", func(t *testing.T) {
now := model.GetMillis()
userId := model.NewId()
teamId := model.NewId()
cleanup := setupScheduledPosts(now, userId, teamId)
defer cleanup()
err := ss.ScheduledPost().UpdateOldScheduledPosts(now + 2.5*86400000) // marking all posts older than 2 days from now
assert.NoError(t, err)
scheduledPosts, err := ss.ScheduledPost().GetScheduledPostsForUser(userId, teamId)
assert.NoError(t, err)
assert.Equal(t, 4, len(scheduledPosts))
assert.Equal(t, model.ScheduledPostErrorUnableToSend, scheduledPosts[0].ErrorCode)
assert.Equal(t, model.ScheduledPostErrorUnableToSend, scheduledPosts[1].ErrorCode)
assert.Equal(t, "", scheduledPosts[2].ErrorCode)
assert.Equal(t, "", scheduledPosts[3].ErrorCode)
})
}
func testPermanentDeleteScheduledPostsByUser(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("should delete all scheduled posts for a given user", func(t *testing.T) {
userId := model.NewId()
teamId := model.NewId()
// Create a scheduled post for the user
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
// Delete scheduled posts for the user
err = ss.ScheduledPost().PermanentDeleteByUser(userId)
assert.NoError(t, err)
// Verify that no scheduled posts exist for the user
scheduledPosts, err := ss.ScheduledPost().GetScheduledPostsForUser(userId, teamId)
assert.NoError(t, err)
assert.Empty(t, scheduledPosts)
})
t.Run("should not fail if no scheduled posts exist for the user", func(t *testing.T) {
userId := model.NewId()
// Attempt to delete scheduled posts for a user with no scheduled posts
err := ss.ScheduledPost().PermanentDeleteByUser(userId)
assert.NoError(t, err)
})
t.Run("should handle multiple scheduled posts for the same user", func(t *testing.T) {
userId := model.NewId()
teamId := model.NewId()
// Create multiple scheduled posts for the user
for range 3 {
scheduledPost := &model.ScheduledPost{
Draft: model.Draft{
CreateAt: model.GetMillis(),
UserId: userId,
ChannelId: model.NewId(),
Message: "this is a scheduled post",
},
ScheduledAt: model.GetMillis() + 100000,
}
createdScheduledPost, err := ss.ScheduledPost().CreateScheduledPost(scheduledPost)
assert.NoError(t, err)
assert.NotEmpty(t, createdScheduledPost.Id)
}
// Delete scheduled posts for the user
err := ss.ScheduledPost().PermanentDeleteByUser(userId)
assert.NoError(t, err)
// Verify that no scheduled posts exist for the user
scheduledPosts, err := ss.ScheduledPost().GetScheduledPostsForUser(userId, teamId)
assert.NoError(t, err)
assert.Empty(t, scheduledPosts)
})
t.Run("should handle empty user id", func(t *testing.T) {
err := ss.ScheduledPost().PermanentDeleteByUser("")
assert.NoError(t, err)
})
t.Run("should handle non-existing user id", func(t *testing.T) {
nonExistingUserId := model.NewId()
err := ss.ScheduledPost().PermanentDeleteByUser(nonExistingUserId)
assert.NoError(t, err)
})
}