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>
422 lines
15 KiB
Go
422 lines
15 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package storetest
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
)
|
|
|
|
func TestPostAcknowledgementsStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
|
|
t.Run("Save", func(t *testing.T) { testPostAcknowledgementsStoreSave(t, rctx, ss) })
|
|
t.Run("GetForPost", func(t *testing.T) { testPostAcknowledgementsStoreGetForPost(t, rctx, ss) })
|
|
t.Run("GetForPosts", func(t *testing.T) { testPostAcknowledgementsStoreGetForPosts(t, rctx, ss) })
|
|
t.Run("BatchSave", func(t *testing.T) { testPostAcknowledgementsStoreBatchSave(t, rctx, ss) })
|
|
t.Run("BatchDelete", func(t *testing.T) { testPostAcknowledgementsStoreBatchDelete(t, rctx, ss) })
|
|
}
|
|
|
|
func testPostAcknowledgementsStoreSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userID1 := model.NewId()
|
|
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
p1.Metadata = &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
RequestedAck: model.NewPointer(true),
|
|
PersistentNotifications: model.NewPointer(false),
|
|
},
|
|
}
|
|
post, err := ss.Post().Save(rctx, &p1)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("consecutive saves should just update the acknowledged at", func(t *testing.T) {
|
|
ack := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
_, err := ss.PostAcknowledgement().SaveWithModel(ack)
|
|
require.NoError(t, err)
|
|
|
|
ack = &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
_, err = ss.PostAcknowledgement().SaveWithModel(ack)
|
|
require.NoError(t, err)
|
|
|
|
ack1 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack1, err = ss.PostAcknowledgement().SaveWithModel(ack1)
|
|
require.NoError(t, err)
|
|
|
|
acknowledgements, err := ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack1})
|
|
})
|
|
|
|
t.Run("saving should update the update at of the post", func(t *testing.T) {
|
|
oldUpdateAt := post.UpdateAt
|
|
ack := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
_, err := ss.PostAcknowledgement().SaveWithModel(ack)
|
|
require.NoError(t, err)
|
|
|
|
post, err = ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
require.Greater(t, post.UpdateAt, oldUpdateAt)
|
|
})
|
|
}
|
|
|
|
func testPostAcknowledgementsStoreGetForPost(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userID1 := model.NewId()
|
|
userID2 := model.NewId()
|
|
userID3 := model.NewId()
|
|
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
p1.Metadata = &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
RequestedAck: model.NewPointer(true),
|
|
PersistentNotifications: model.NewPointer(false),
|
|
},
|
|
}
|
|
_, err := ss.Post().Save(rctx, &p1)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("get acknowledgements for post", func(t *testing.T) {
|
|
ack1 := &model.PostAcknowledgement{PostId: p1.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: p1.ChannelId}
|
|
ack1, err := ss.PostAcknowledgement().SaveWithModel(ack1)
|
|
require.NoError(t, err)
|
|
ack2 := &model.PostAcknowledgement{PostId: p1.Id, UserId: userID2, AcknowledgedAt: 0, ChannelId: p1.ChannelId}
|
|
ack2, err = ss.PostAcknowledgement().SaveWithModel(ack2)
|
|
require.NoError(t, err)
|
|
ack3 := &model.PostAcknowledgement{PostId: p1.Id, UserId: userID3, AcknowledgedAt: 0, ChannelId: p1.ChannelId}
|
|
ack3, err = ss.PostAcknowledgement().SaveWithModel(ack3)
|
|
require.NoError(t, err)
|
|
|
|
acknowledgements, err := ss.PostAcknowledgement().GetForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack1, ack2, ack3})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack1)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack2, ack3})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack2)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack3})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack3)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPost(p1.Id)
|
|
require.NoError(t, err)
|
|
require.Empty(t, acknowledgements)
|
|
})
|
|
}
|
|
|
|
func testPostAcknowledgementsStoreGetForPosts(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userID1 := model.NewId()
|
|
userID2 := model.NewId()
|
|
userID3 := model.NewId()
|
|
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
p1.Metadata = &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer("important"),
|
|
RequestedAck: model.NewPointer(true),
|
|
PersistentNotifications: model.NewPointer(false),
|
|
},
|
|
}
|
|
p2 := model.Post{}
|
|
p2.ChannelId = model.NewId()
|
|
p2.UserId = model.NewId()
|
|
p2.Message = NewTestID()
|
|
p2.Metadata = &model.PostMetadata{
|
|
Priority: &model.PostPriority{
|
|
Priority: model.NewPointer(""),
|
|
RequestedAck: model.NewPointer(true),
|
|
PersistentNotifications: model.NewPointer(false),
|
|
},
|
|
}
|
|
_, errIdx, err := ss.Post().SaveMultiple(rctx, []*model.Post{&p1, &p2})
|
|
require.NoError(t, err)
|
|
require.Equal(t, -1, errIdx)
|
|
|
|
t.Run("get acknowledgements for post", func(t *testing.T) {
|
|
ack1 := &model.PostAcknowledgement{PostId: p1.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: p1.ChannelId}
|
|
ack1, err := ss.PostAcknowledgement().SaveWithModel(ack1)
|
|
require.NoError(t, err)
|
|
ack2 := &model.PostAcknowledgement{PostId: p1.Id, UserId: userID2, AcknowledgedAt: 0, ChannelId: p1.ChannelId}
|
|
ack2, err = ss.PostAcknowledgement().SaveWithModel(ack2)
|
|
require.NoError(t, err)
|
|
ack3 := &model.PostAcknowledgement{PostId: p2.Id, UserId: userID2, AcknowledgedAt: 0, ChannelId: p2.ChannelId}
|
|
ack3, err = ss.PostAcknowledgement().SaveWithModel(ack3)
|
|
require.NoError(t, err)
|
|
ack4 := &model.PostAcknowledgement{PostId: p2.Id, UserId: userID3, AcknowledgedAt: 0, ChannelId: p2.ChannelId}
|
|
ack4, err = ss.PostAcknowledgement().SaveWithModel(ack4)
|
|
require.NoError(t, err)
|
|
|
|
acknowledgements, err := ss.PostAcknowledgement().GetForPosts([]string{p1.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack1, ack2})
|
|
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p2.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack3, ack4})
|
|
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p1.Id, p2.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack1, ack2, ack3, ack4})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack1)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p1.Id, p2.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack2, ack3, ack4})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack2)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p1.Id, p2.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack3, ack4})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack3)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p1.Id, p2.Id})
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, acknowledgements, []*model.PostAcknowledgement{ack4})
|
|
|
|
err = ss.PostAcknowledgement().Delete(ack4)
|
|
require.NoError(t, err)
|
|
acknowledgements, err = ss.PostAcknowledgement().GetForPosts([]string{p1.Id, p2.Id})
|
|
require.NoError(t, err)
|
|
require.Empty(t, acknowledgements)
|
|
})
|
|
}
|
|
|
|
func testPostAcknowledgementsStoreBatchSave(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userID1 := model.NewId()
|
|
userID2 := model.NewId()
|
|
userID3 := model.NewId()
|
|
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
post, err := ss.Post().Save(rctx, &p1)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("batch save acknowledgements for a post", func(t *testing.T) {
|
|
// Create a batch of acknowledgements
|
|
acks := []*model.PostAcknowledgement{
|
|
{
|
|
PostId: post.Id,
|
|
UserId: userID1,
|
|
AcknowledgedAt: model.GetMillis(),
|
|
},
|
|
{
|
|
PostId: post.Id,
|
|
UserId: userID2,
|
|
AcknowledgedAt: model.GetMillis(),
|
|
},
|
|
{
|
|
PostId: post.Id,
|
|
UserId: userID3,
|
|
AcknowledgedAt: model.GetMillis(),
|
|
},
|
|
}
|
|
|
|
// Save the batch
|
|
savedAcks, err := ss.PostAcknowledgement().BatchSave(acks)
|
|
require.NoError(t, err)
|
|
require.Len(t, savedAcks, 3)
|
|
|
|
// Verify all were saved correctly
|
|
retrievedAcks, err := ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, retrievedAcks, 3)
|
|
|
|
// Verify all users are in the saved acknowledgements
|
|
userIDMap := make(map[string]bool)
|
|
for _, ack := range retrievedAcks {
|
|
userIDMap[ack.UserId] = true
|
|
require.Equal(t, post.Id, ack.PostId)
|
|
require.Greater(t, ack.AcknowledgedAt, int64(0))
|
|
}
|
|
|
|
require.True(t, userIDMap[userID1])
|
|
require.True(t, userIDMap[userID2])
|
|
require.True(t, userIDMap[userID3])
|
|
})
|
|
|
|
t.Run("batch save empty list of acknowledgements", func(t *testing.T) {
|
|
// Create an empty batch of acknowledgements
|
|
acks := []*model.PostAcknowledgement{}
|
|
|
|
// Save the empty batch
|
|
savedAcks, err := ss.PostAcknowledgement().BatchSave(acks)
|
|
require.NoError(t, err)
|
|
require.Empty(t, savedAcks)
|
|
})
|
|
|
|
t.Run("batch save should update existing acknowledgements", func(t *testing.T) {
|
|
// First, delete all existing acknowledgements
|
|
acks, err := ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, err)
|
|
|
|
for _, ack := range acks {
|
|
err = ss.PostAcknowledgement().Delete(ack)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create initial acknowledgement
|
|
ack := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: model.GetMillis(), ChannelId: post.ChannelId}
|
|
ack, err = ss.PostAcknowledgement().SaveWithModel(ack)
|
|
require.NoError(t, err)
|
|
|
|
initialAckTime := ack.AcknowledgedAt
|
|
|
|
// Create a batch with updated timestamp
|
|
newTimestamp := model.GetMillis() + 1000
|
|
updatedAcks := []*model.PostAcknowledgement{
|
|
{
|
|
PostId: post.Id,
|
|
UserId: userID1,
|
|
AcknowledgedAt: newTimestamp,
|
|
},
|
|
}
|
|
|
|
// Batch save should update the existing acknowledgement
|
|
savedAcks, err := ss.PostAcknowledgement().BatchSave(updatedAcks)
|
|
require.NoError(t, err)
|
|
require.Len(t, savedAcks, 1)
|
|
require.Equal(t, newTimestamp, savedAcks[0].AcknowledgedAt)
|
|
require.Greater(t, savedAcks[0].AcknowledgedAt, initialAckTime)
|
|
|
|
// Verify the acknowledgement was updated
|
|
retrievedAcks, err := ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, err)
|
|
require.Len(t, retrievedAcks, 1)
|
|
require.Equal(t, newTimestamp, retrievedAcks[0].AcknowledgedAt)
|
|
})
|
|
|
|
t.Run("batch save should update post's update_at", func(t *testing.T) {
|
|
// First, check the current post update timestamp
|
|
currentPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
oldUpdateAt := currentPost.UpdateAt
|
|
|
|
// Create a batch of new acknowledgements
|
|
acks := []*model.PostAcknowledgement{
|
|
{
|
|
PostId: post.Id,
|
|
UserId: model.NewId(),
|
|
AcknowledgedAt: model.GetMillis(),
|
|
},
|
|
}
|
|
|
|
// Save the batch
|
|
_, err = ss.PostAcknowledgement().BatchSave(acks)
|
|
require.NoError(t, err)
|
|
|
|
// Verify post's update_at was updated
|
|
updatedPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
require.Greater(t, updatedPost.UpdateAt, oldUpdateAt)
|
|
})
|
|
}
|
|
|
|
func testPostAcknowledgementsStoreBatchDelete(t *testing.T, rctx request.CTX, ss store.Store) {
|
|
userID1 := model.NewId()
|
|
userID2 := model.NewId()
|
|
userID3 := model.NewId()
|
|
|
|
p1 := model.Post{}
|
|
p1.ChannelId = model.NewId()
|
|
p1.UserId = model.NewId()
|
|
p1.Message = NewTestID()
|
|
post, err := ss.Post().Save(rctx, &p1)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("batch delete all acknowledgements for a post", func(t *testing.T) {
|
|
// Create multiple acknowledgements
|
|
ack1 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack1, err = ss.PostAcknowledgement().SaveWithModel(ack1)
|
|
require.NoError(t, err)
|
|
ack2 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID2, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack2, err = ss.PostAcknowledgement().SaveWithModel(ack2)
|
|
require.NoError(t, err)
|
|
ack3 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID3, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack3, err = ss.PostAcknowledgement().SaveWithModel(ack3)
|
|
require.NoError(t, err)
|
|
|
|
// Verify acknowledgements were created
|
|
acks, pErr := ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, pErr)
|
|
require.Len(t, acks, 3)
|
|
|
|
// Delete all acknowledgements in batch
|
|
err = ss.PostAcknowledgement().BatchDelete([]*model.PostAcknowledgement{ack1, ack2, ack3})
|
|
require.NoError(t, err)
|
|
|
|
// Verify all acknowledgements were deleted
|
|
acks, err = ss.PostAcknowledgement().GetForPost(post.Id)
|
|
require.NoError(t, err)
|
|
require.Empty(t, acks)
|
|
})
|
|
|
|
t.Run("batch delete should update post's update_at", func(t *testing.T) {
|
|
// Create acknowledgements
|
|
ack1 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID1, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack1, err = ss.PostAcknowledgement().SaveWithModel(ack1)
|
|
require.NoError(t, err)
|
|
ack2 := &model.PostAcknowledgement{PostId: post.Id, UserId: userID2, AcknowledgedAt: 0, ChannelId: post.ChannelId}
|
|
ack2, err = ss.PostAcknowledgement().SaveWithModel(ack2)
|
|
require.NoError(t, err)
|
|
|
|
// Get current post update timestamp
|
|
currentPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
oldUpdateAt := currentPost.UpdateAt
|
|
|
|
// Delete acknowledgements in batch
|
|
err = ss.PostAcknowledgement().BatchDelete([]*model.PostAcknowledgement{ack1, ack2})
|
|
require.NoError(t, err)
|
|
|
|
// Verify post's update_at was updated
|
|
updatedPost, err := ss.Post().GetSingle(rctx, post.Id, false)
|
|
require.NoError(t, err)
|
|
require.Greater(t, updatedPost.UpdateAt, oldUpdateAt)
|
|
})
|
|
|
|
t.Run("batch delete with empty list should not error", func(t *testing.T) {
|
|
// Delete with empty list should not error
|
|
err := ss.PostAcknowledgement().BatchDelete([]*model.PostAcknowledgement{})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("batch delete with non-existent acknowledgements should not error", func(t *testing.T) {
|
|
// Create non-existent acknowledgements
|
|
nonExistentAck := &model.PostAcknowledgement{
|
|
PostId: model.NewId(),
|
|
UserId: model.NewId(),
|
|
AcknowledgedAt: model.GetMillis(),
|
|
}
|
|
|
|
// Delete non-existent acknowledgement should not error
|
|
err := ss.PostAcknowledgement().BatchDelete([]*model.PostAcknowledgement{nonExistentAck})
|
|
require.NoError(t, err)
|
|
})
|
|
}
|