mattermost-community-enterp.../channels/store/storetest/channel_bookmark.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

565 lines
18 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"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 find_bookmark(slice []*model.ChannelBookmarkWithFileInfo, id string) *model.ChannelBookmarkWithFileInfo {
for _, element := range slice {
if element.Id == id {
return element
}
}
return nil
}
func TestChannelBookmarkStore(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("SaveChannelBookmark", func(t *testing.T) { testSaveChannelBookmark(t, rctx, ss) })
t.Run("UpdateChannelBookmark", func(t *testing.T) { testUpdateChannelBookmark(t, rctx, ss) })
t.Run("UpdateSortOrderChannelBookmark", func(t *testing.T) { testUpdateSortOrderChannelBookmark(t, rctx, ss) })
t.Run("DeleteChannelBookmark", func(t *testing.T) { testDeleteChannelBookmark(t, rctx, ss) })
t.Run("GetChannelBookmark", func(t *testing.T) { testGetChannelBookmark(t, rctx, ss) })
}
func testSaveChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
channelID := model.NewId()
otherChannelID := model.NewId()
userID := model.NewId()
createAt := time.Now().Add(-1 * time.Minute)
deleteAt := createAt.Add(1 * time.Second)
bookmark1 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Link bookmark test",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
Emoji: ":smile:",
}
file := &model.FileInfo{
Id: model.NewId(),
ChannelId: channelID,
CreatorId: model.BookmarkFileOwner,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
}
bookmark2 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file bookmark test",
FileId: file.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
bookmark3 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file already attached",
FileId: file.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
file2 := &model.FileInfo{
Id: model.NewId(),
ChannelId: channelID,
CreatorId: userID,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
}
bookmark4 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file already attached to a post",
FileId: file2.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
deletedFile := &model.FileInfo{
Id: model.NewId(),
ChannelId: channelID,
CreatorId: model.BookmarkFileOwner,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
CreateAt: createAt.UnixMilli(),
UpdateAt: createAt.UnixMilli(),
DeleteAt: deleteAt.UnixMilli(),
}
bookmarkFileDeleted := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file deleted",
FileId: deletedFile.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
// another channel
anotherChannelFile := &model.FileInfo{
Id: model.NewId(),
ChannelId: otherChannelID,
CreatorId: model.BookmarkFileOwner,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
}
bookmarkFileAnotherChannel := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file another channel",
FileId: anotherChannelFile.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
_, err := ss.FileInfo().Save(rctx, file)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
_, err = ss.FileInfo().Save(rctx, file2)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, file2.Id)
err = ss.FileInfo().AttachToPost(rctx, file2.Id, model.NewId(), channelID, userID)
require.NoError(t, err)
_, err = ss.FileInfo().Save(rctx, deletedFile)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, deletedFile.Id)
_, err = ss.FileInfo().Save(rctx, anotherChannelFile)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, anotherChannelFile.Id)
t.Run("save bookmarks", func(t *testing.T) {
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
assert.NoError(t, err)
assert.NotEmpty(t, bookmarkResp.Id)
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
assert.Nil(t, bookmarkResp.FileInfo)
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
assert.NoError(t, err)
assert.NotEmpty(t, bookmarkResp.Id)
assert.Equal(t, bookmark2.ChannelId, bookmarkResp.ChannelId)
assert.NotNil(t, bookmarkResp.FileInfo)
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelID, 0)
assert.NoError(t, err)
assert.Len(t, bookmarks, 2)
_, err = ss.ChannelBookmark().Save(bookmark3.Clone(), true)
assert.Error(t, err) // Error as the file is attached to a bookmark
_, err = ss.ChannelBookmark().Save(bookmark4.Clone(), true)
assert.Error(t, err) // Error as the file is attached to a post
_, err = ss.ChannelBookmark().Save(bookmarkFileDeleted.Clone(), true)
assert.Error(t, err) // Error as the file is deleted
_, err = ss.ChannelBookmark().Save(bookmarkFileAnotherChannel.Clone(), true)
assert.Error(t, err) // Error as the file is from another channel
})
}
func testUpdateChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
channelID := model.NewId()
userID := model.NewId()
bookmark1 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Link bookmark test",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
}
t.Run("update bookmark", func(t *testing.T) {
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
assert.NoError(t, err)
now := model.GetMillis()
bookmark2 := bookmarkResp.ChannelBookmark.Clone()
bookmark2.DisplayName = "Updated display name"
bookmark2.Emoji = ":smile:"
bookmark2.LinkUrl = "https://mattermost.com/about"
time.Sleep(time.Millisecond * 250)
err = ss.ChannelBookmark().Update(bookmark2.Clone())
assert.NoError(t, err)
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelID, now)
assert.NoError(t, err)
assert.Len(t, bookmarks, 1)
b := find_bookmark(bookmarks, bookmark2.Id)
assert.NotNil(t, b)
assert.Equal(t, b.DisplayName, bookmark2.DisplayName)
assert.Equal(t, b.Type, model.ChannelBookmarkLink)
assert.NotEmpty(t, b.Emoji)
assert.Equal(t, b.CreateAt, bookmark2.CreateAt)
assert.Greater(t, b.UpdateAt, bookmark2.UpdateAt)
err = ss.ChannelBookmark().Update(bookmark1.Clone())
assert.Error(t, err)
bookmark3 := bookmark2.Clone()
bookmark3.Type = model.ChannelBookmarkFile
err = ss.ChannelBookmark().Update(bookmark3)
assert.Error(t, err)
})
}
func testUpdateSortOrderChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
channelID := model.NewId()
userID := model.NewId()
bookmark0 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Bookmark 0",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
Emoji: ":smile:",
}
file := &model.FileInfo{
Id: model.NewId(),
ChannelId: channelID,
CreatorId: model.BookmarkFileOwner,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
}
bookmark1 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Bookmark 1",
FileId: file.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
_, err := ss.FileInfo().Save(rctx, file)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
bookmark2 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Bookmark 2",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
}
bookmark3 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Bookmark 3",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
}
bookmark4 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Bookmark 4",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
}
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark0.Clone(), true)
assert.NoError(t, err)
bookmark0 = bookmarkResp.ChannelBookmark.Clone()
assert.NotEmpty(t, bookmarkResp.Id)
assert.Equal(t, bookmark0.ChannelId, bookmarkResp.ChannelId)
assert.Nil(t, bookmarkResp.FileInfo)
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark1.Clone(), true)
assert.NoError(t, err)
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
assert.NoError(t, err)
bookmark2 = bookmarkResp.ChannelBookmark.Clone()
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark3.Clone(), true)
assert.NoError(t, err)
bookmark3 = bookmarkResp.ChannelBookmark.Clone()
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark4.Clone(), true)
assert.NoError(t, err)
bookmark4 = bookmarkResp.ChannelBookmark.Clone()
t.Run("change order of bookmarks first to last", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelID, 4)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks last to first", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelID, 0)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks first to third", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelID, 2)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
// now reset order
ss.ChannelBookmark().UpdateSortOrder(bookmark0.Id, channelID, 0)
})
t.Run("change order of bookmarks second to third", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark1.Id, channelID, 2)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks third to second", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark1.Id, channelID, 1)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks last to previous last", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark4.Id, channelID, 3)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks last to second", func(t *testing.T) {
bookmarks, sortError := ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelID, 1)
assert.NoError(t, sortError)
assert.Equal(t, find_bookmark(bookmarks, bookmark0.Id).SortOrder, int64(0))
assert.Equal(t, find_bookmark(bookmarks, bookmark3.Id).SortOrder, int64(1))
assert.Equal(t, find_bookmark(bookmarks, bookmark1.Id).SortOrder, int64(2))
assert.Equal(t, find_bookmark(bookmarks, bookmark2.Id).SortOrder, int64(3))
assert.Equal(t, find_bookmark(bookmarks, bookmark4.Id).SortOrder, int64(4))
})
t.Run("change order of bookmarks error when new index is out of bounds", func(t *testing.T) {
var iiErr *store.ErrInvalidInput
_, err = ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelID, -1)
assert.Error(t, err)
assert.ErrorAs(t, err, &iiErr)
_, err = ss.ChannelBookmark().UpdateSortOrder(bookmark3.Id, channelID, 5)
assert.Error(t, err)
assert.ErrorAs(t, err, &iiErr)
})
t.Run("change order of bookmarks error when bookmark not found", func(t *testing.T) {
_, err = ss.ChannelBookmark().UpdateSortOrder(model.NewId(), channelID, 0)
assert.Error(t, err)
var nfErr *store.ErrNotFound
assert.ErrorAs(t, err, &nfErr)
})
}
func testDeleteChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
channelID := model.NewId()
userID := model.NewId()
bookmark1 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Link bookmark test",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
Emoji: ":smile:",
}
file := &model.FileInfo{
Id: model.NewId(),
ChannelId: channelID,
CreatorId: model.BookmarkFileOwner,
Path: "somepath",
ThumbnailPath: "thumbpath",
PreviewPath: "prevPath",
Name: "test file",
Extension: "png",
MimeType: "images/png",
Size: 873182,
Width: 3076,
Height: 2200,
HasPreviewImage: true,
}
bookmark2 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "file bookmark test",
FileId: file.Id,
Type: model.ChannelBookmarkFile,
Emoji: ":smile:",
}
_, err := ss.FileInfo().Save(rctx, file)
require.NoError(t, err)
defer ss.FileInfo().PermanentDelete(rctx, file.Id)
t.Run("delete bookmark", func(t *testing.T) {
now := model.GetMillis()
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
assert.NoError(t, err)
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
assert.NotEmpty(t, bookmarkResp.Id)
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
assert.Nil(t, bookmarkResp.FileInfo)
bookmarkResp, err = ss.ChannelBookmark().Save(bookmark2.Clone(), true)
assert.NoError(t, err)
bookmark2 = bookmarkResp.ChannelBookmark.Clone()
err = ss.ChannelBookmark().Delete(bookmark2.Id, true)
assert.NoError(t, err)
_, err = ss.FileInfo().Get(file.Id)
assert.Error(t, err)
var nfErr *store.ErrNotFound
assert.ErrorAs(t, err, &nfErr)
bookmarks, err := ss.ChannelBookmark().GetBookmarksForChannelSince(channelID, now)
assert.NoError(t, err)
assert.Len(t, bookmarks, 2) // we have two as the deleted record also gets returned for sync'ing purposes
b := find_bookmark(bookmarks, bookmark2.Id)
assert.NotNil(t, b)
assert.Equal(t, bookmarks[0].Type, model.ChannelBookmarkLink)
})
}
func testGetChannelBookmark(t *testing.T, rctx request.CTX, ss store.Store) {
channelID := model.NewId()
userID := model.NewId()
bookmark1 := &model.ChannelBookmark{
ChannelId: channelID,
OwnerId: userID,
DisplayName: "Link bookmark test",
LinkUrl: "https://mattermost.com",
Type: model.ChannelBookmarkLink,
Emoji: ":smile:",
}
t.Run("get bookmark", func(t *testing.T) {
bookmarkResp, err := ss.ChannelBookmark().Save(bookmark1.Clone(), true)
assert.NoError(t, err)
bookmark1 = bookmarkResp.ChannelBookmark.Clone()
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, false)
assert.NoError(t, err)
assert.NotEmpty(t, bookmarkResp.Id)
assert.Equal(t, bookmark1.ChannelId, bookmarkResp.ChannelId)
assert.Nil(t, bookmarkResp.FileInfo)
err = ss.ChannelBookmark().Delete(bookmark1.Id, true)
assert.NoError(t, err)
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, false)
assert.Error(t, err)
assert.Nil(t, bookmarkResp)
bookmarkResp, err = ss.ChannelBookmark().Get(bookmark1.Id, true)
assert.NoError(t, err)
assert.NotNil(t, bookmarkResp)
})
}