[MM 12464] Include DM/GM Channels and Their Posts in the Bulk Export (#10421)

* transplant the existing PR into the working tree

* start addressing review comments

* move existing direct channel export code into this branch

* modify channel exporter to use squirell and populate members in two steps

* use squirrel to build sql queries for channel and dm/gm export methods

* remove debug helpers and use Username instead of UserId

* unit test for DM Channel exporter

* add more unit tests for channel export

* add test for DM/GM post export

* checkpoint with failing test for postgres

* use getQueryBuilder to make sure squirrel uses the correct formatting
for each database

* add a test for post export

* fix shadowed vars that broke the build

* address review comments and add tests to support it

* address review comments and add a mlog call

* s/Info/Debug/

* address review comments in post_store

* address review comments in channel_store

* address review comments in export

* address review comment in post_store: drop GroupBy

* address review comment on supplier: move getQueryBuilder to sqlstore

* address review comments: explicit TearDown

* address review comments: improve test coverage

* address review comments: make sure public and private channels are excluded

* address review comments: improve test coverage

* address review comments: make sure Channels table gets truncated after
each test

* more cleanups and better assertions

* wrap PostStore in a StoreTestWithSqlSupplier

* last minute changes: improve post export test coverage and check members

* address review comments: make sure all posts have their channel
members set

* address review comments: make sure all posts have their ChannelMembers
exported correctly

* gofmt fix

* sort channels so it's possible to assert on index
Этот коммит содержится в:
Fernando Correa Neto
2019-03-15 12:28:43 -03:00
коммит произвёл Jesús Espino
родитель eb49713c96
Коммит 9abd4dd7dc
17 изменённых файлов: 925 добавлений и 22 удалений

Просмотреть файл

@@ -11,6 +11,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -70,10 +71,19 @@ func (a *App) BulkExport(writer io.Writer, file string, pathToEmojiDir string, d
if err := a.ExportAllPosts(writer); err != nil { if err := a.ExportAllPosts(writer); err != nil {
return err return err
} }
if err := a.ExportCustomEmoji(writer, file, pathToEmojiDir, dirNameToExportEmoji); err != nil { if err := a.ExportCustomEmoji(writer, file, pathToEmojiDir, dirNameToExportEmoji); err != nil {
return err return err
} }
if err := a.ExportAllDirectChannels(writer); err != nil {
return err
}
if err := a.ExportAllDirectPosts(writer); err != nil {
return err
}
return nil return nil
} }
@@ -503,3 +513,83 @@ func (a *App) copyEmojiImages(emojiId string, emojiImagePath string, pathToDir s
return nil return nil
} }
func (a *App) ExportAllDirectChannels(writer io.Writer) *model.AppError {
afterId := strings.Repeat("0", 26)
for {
result := <-a.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, afterId)
if result.Err != nil {
return result.Err
}
channels := result.Data.([]*model.DirectChannelForExport)
if len(channels) == 0 {
break
}
for _, channel := range channels {
afterId = channel.Id
// Skip deleted.
if channel.DeleteAt != 0 {
continue
}
// There's no import support for single member channels yet.
if len(*channel.Members) == 1 {
mlog.Debug("Bulk export for direct channels containing a single member is not supported.")
continue
}
channelLine := ImportLineFromDirectChannel(channel)
if err := a.ExportWriteLine(writer, channelLine); err != nil {
return err
}
}
}
return nil
}
func (a *App) ExportAllDirectPosts(writer io.Writer) *model.AppError {
afterId := strings.Repeat("0", 26)
for {
result := <-a.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, afterId)
if result.Err != nil {
return result.Err
}
posts := result.Data.([]*model.DirectPostForExport)
if len(posts) == 0 {
break
}
for _, post := range posts {
afterId = post.Id
// Skip deleted.
if post.DeleteAt != 0 {
continue
}
// There's no import support for single member channels yet.
if len(*post.ChannelMembers) == 1 {
mlog.Debug("Bulk export for posts containing a single member is not supported.")
continue
}
// Do the Replies.
replies, err := a.buildPostReplies(post.Id)
if err != nil {
return err
}
postLine := ImportLineForDirectPost(post)
postLine.DirectPost.Replies = replies
if err := a.ExportWriteLine(writer, postLine); err != nil {
return err
}
}
}
return nil
}

Просмотреть файл

@@ -38,6 +38,16 @@ func ImportLineFromChannel(channel *model.ChannelForExport) *LineImportData {
} }
} }
func ImportLineFromDirectChannel(channel *model.DirectChannelForExport) *LineImportData {
return &LineImportData{
Type: "direct_channel",
DirectChannel: &DirectChannelImportData{
Header: &channel.Header,
Members: channel.Members,
},
}
}
func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *LineImportData { func ImportLineFromUser(user *model.User, exportedPrefs map[string]*string) *LineImportData {
// Bulk Importer doesn't accept "empty string" for AuthService. // Bulk Importer doesn't accept "empty string" for AuthService.
var authService *string var authService *string
@@ -141,6 +151,18 @@ func ImportLineForPost(post *model.PostForExport) *LineImportData {
} }
} }
func ImportLineForDirectPost(post *model.DirectPostForExport) *LineImportData {
return &LineImportData{
Type: "direct_post",
DirectPost: &DirectPostImportData{
ChannelMembers: post.ChannelMembers,
User: &post.User,
Message: &post.Message,
CreateAt: &post.CreateAt,
},
}
}
func ImportReplyFromPost(post *model.ReplyForExport) *ReplyImportData { func ImportReplyFromPost(post *model.ReplyForExport) *ReplyImportData {
return &ReplyImportData{ return &ReplyImportData{
User: &post.Username, User: &post.Username,

Просмотреть файл

@@ -3,6 +3,7 @@ package app
import ( import (
"bytes" "bytes"
"os" "os"
"sort"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -201,16 +202,252 @@ func TestExportAllUsers(t *testing.T) {
// Checking whether deactivated users were included in bulk export // Checking whether deactivated users were included in bulk export
deletedUsers1, err := th1.App.GetUsers(&model.UserGetOptions{ deletedUsers1, err := th1.App.GetUsers(&model.UserGetOptions{
Inactive: true, Inactive: true,
Page: 0, Page: 0,
PerPage: 10, PerPage: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
deletedUsers2, err := th1.App.GetUsers(&model.UserGetOptions{ deletedUsers2, err := th1.App.GetUsers(&model.UserGetOptions{
Inactive: true, Inactive: true,
Page: 0, Page: 0,
PerPage: 10, PerPage: 10,
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, len(deletedUsers1), len(deletedUsers2)) assert.Equal(t, len(deletedUsers1), len(deletedUsers2))
assert.ElementsMatch(t, deletedUsers1, deletedUsers2) assert.ElementsMatch(t, deletedUsers1, deletedUsers2)
} }
func TestExportDMChannel(t *testing.T) {
th1 := Setup(t).InitBasic()
// DM Channel
th1.CreateDmChannel(th1.BasicUser2)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
result := <-th1.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels := result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 1, len(channels))
th1.TearDown()
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(channels))
// import the exported channel
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
// Ensure the Members of the imported DM channel is the same was from the exported
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 1, len(channels))
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[0].Members)
}
func TestExportDMChannelToSelf(t *testing.T) {
th1 := Setup(t).InitBasic()
defer th1.TearDown()
// DM Channel with self (me channel)
th1.CreateDmChannel(th1.BasicUser)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
result := <-th1.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels := result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 1, len(channels))
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(channels))
// import the exported channel
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
// Ensure no channels were imported
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(channels))
}
func TestExportGMChannel(t *testing.T) {
th1 := Setup(t).InitBasic()
user1 := th1.CreateUser()
th1.LinkUserToTeam(user1, th1.BasicTeam)
user2 := th1.CreateUser()
th1.LinkUserToTeam(user2, th1.BasicTeam)
// GM Channel
th1.CreateGroupChannel(user1, user2)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
result := <-th1.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels := result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 1, len(channels))
th1.TearDown()
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(channels))
}
func TestExportGMandDMChannels(t *testing.T) {
th1 := Setup(t).InitBasic()
// DM Channel
th1.CreateDmChannel(th1.BasicUser2)
user1 := th1.CreateUser()
th1.LinkUserToTeam(user1, th1.BasicTeam)
user2 := th1.CreateUser()
th1.LinkUserToTeam(user2, th1.BasicTeam)
// GM Channel
th1.CreateGroupChannel(user1, user2)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
result := <-th1.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels := result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 2, len(channels))
th1.TearDown()
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(channels))
// import the exported channel
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
// Ensure the Members of the imported GM channel is the same was from the exported
result = <-th2.App.Srv.Store.Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
channels = result.Data.([]*model.DirectChannelForExport)
// Adding some deteminism so its possible to assert on slice index
sort.Slice(channels, func(i, j int) bool { return channels[i].CreateAt > channels[j].CreateAt })
assert.Equal(t, 2, len(channels))
assert.ElementsMatch(t, []string{th1.BasicUser.Username, user1.Username, user2.Username}, *channels[0].Members)
assert.ElementsMatch(t, []string{th1.BasicUser.Username, th1.BasicUser2.Username}, *channels[1].Members)
}
func TestExportDMandGMPost(t *testing.T) {
th1 := Setup(t).InitBasic()
// DM Channel
dmChannel := th1.CreateDmChannel(th1.BasicUser2)
dmMembers := []string{th1.BasicUser.Username, th1.BasicUser2.Username}
user1 := th1.CreateUser()
th1.LinkUserToTeam(user1, th1.BasicTeam)
user2 := th1.CreateUser()
th1.LinkUserToTeam(user2, th1.BasicTeam)
// GM Channel
gmChannel := th1.CreateGroupChannel(user1, user2)
gmMembers := []string{th1.BasicUser.Username, user1.Username, user2.Username}
// DM posts
th1.CreatePost(dmChannel)
th1.CreatePost(dmChannel)
// GM posts
th1.CreatePost(gmChannel)
th1.CreatePost(gmChannel)
result := <-th1.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts := result.Data.([]*model.DirectPostForExport)
assert.Equal(t, 4, len(posts))
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
th1.TearDown()
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts = result.Data.([]*model.DirectPostForExport)
assert.Equal(t, 0, len(posts))
// import the exported posts
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
result = <-th2.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts = result.Data.([]*model.DirectPostForExport)
// Adding some deteminism so its possible to assert on slice index
sort.Slice(posts, func(i, j int) bool { return posts[i].CreateAt > posts[j].CreateAt })
assert.Equal(t, 4, len(posts))
assert.ElementsMatch(t, gmMembers, *posts[0].ChannelMembers)
assert.ElementsMatch(t, gmMembers, *posts[1].ChannelMembers)
assert.ElementsMatch(t, dmMembers, *posts[2].ChannelMembers)
assert.ElementsMatch(t, dmMembers, *posts[3].ChannelMembers)
}
func TestExportDMPostWithSelf(t *testing.T) {
th1 := Setup(t).InitBasic()
// DM Channel with self (me channel)
dmChannel := th1.CreateDmChannel(th1.BasicUser)
th1.CreatePost(dmChannel)
var b bytes.Buffer
err := th1.App.BulkExport(&b, "somefile", "somePath", "someDir")
require.Nil(t, err)
result := <-th1.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts := result.Data.([]*model.DirectPostForExport)
assert.Equal(t, 1, len(posts))
th1.TearDown()
th2 := Setup(t)
defer th2.TearDown()
result = <-th2.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts = result.Data.([]*model.DirectPostForExport)
assert.Equal(t, 0, len(posts))
// import the exported posts
err, i := th2.App.BulkImport(&b, false, 5)
assert.Nil(t, err)
assert.Equal(t, 0, i)
result = <-th2.App.Srv.Store.Post().GetDirectPostParentsForExportAfter(1000, "0000000")
posts = result.Data.([]*model.DirectPostForExport)
assert.Equal(t, 0, len(posts))
}

Просмотреть файл

@@ -73,6 +73,11 @@ type ChannelForExport struct {
SchemeName *string SchemeName *string
} }
type DirectChannelForExport struct {
Channel
Members *[]string
}
func (o *Channel) DeepCopy() *Channel { func (o *Channel) DeepCopy() *Channel {
copy := *o copy := *o
if copy.SchemeId != nil { if copy.SchemeId != nil {

Просмотреть файл

@@ -50,6 +50,7 @@ type ChannelMembers []ChannelMember
type ChannelMemberForExport struct { type ChannelMemberForExport struct {
ChannelMember ChannelMember
ChannelName string ChannelName string
Username string
} }
func (o *ChannelMembers) ToJson() string { func (o *ChannelMembers) ToJson() string {

Просмотреть файл

@@ -122,6 +122,12 @@ type PostForExport struct {
ReplyCount int ReplyCount int
} }
type DirectPostForExport struct {
Post
User string
ChannelMembers *[]string
}
type ReplyForExport struct { type ReplyForExport struct {
Post Post
Username string Username string

Просмотреть файл

@@ -14,6 +14,7 @@ import (
"github.com/mattermost/gorp" "github.com/mattermost/gorp"
"github.com/pkg/errors" "github.com/pkg/errors"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
@@ -2502,3 +2503,66 @@ func (s SqlChannelStore) GetChannelMembersForExport(userId string, teamId string
result.Data = members result.Data = members
}) })
} }
func (s SqlChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
var directChannelsForExport []*model.DirectChannelForExport
query := s.getQueryBuilder().
Select("Channels.*").
From("Channels").
Where(sq.And{
sq.Gt{"Channels.Id": afterId},
sq.Eq{"Channels.DeleteAt": int(0)},
sq.Eq{"Channels.Type": []string{"D", "G"}},
}).
OrderBy("Channels.Id").
Limit(uint64(limit))
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
if _, err = s.GetReplica().Select(&directChannelsForExport, queryString, args...); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
var channelIds []string
for _, channel := range directChannelsForExport {
channelIds = append(channelIds, channel.Id)
}
query = s.getQueryBuilder().
Select("*").
From("ChannelMembers cm").
Join("Users u ON ( u.Id = cm.UserId )").
Where(sq.And{
sq.Eq{"cm.ChannelId": channelIds},
sq.Eq{"u.DeleteAt": int(0)},
})
queryString, args, err = query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
var channelMembers []*model.ChannelMemberForExport
if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
result.Err = model.NewAppError("SqlTeamStore.GetAllDirectChannelsForExportAfter", "store.sql_channel.get_all_direct.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Populate each channel with its members
dmChannelsMap := make(map[string]*model.DirectChannelForExport)
for _, channel := range directChannelsForExport {
channel.Members = &[]string{}
dmChannelsMap[channel.Id] = channel
}
for _, member := range channelMembers {
members := dmChannelsMap[member.ChannelId].Members
*members = append(*members, member.Username)
}
result.Data = directChannelsForExport
})
}

Просмотреть файл

@@ -11,6 +11,7 @@ import (
"strings" "strings"
"sync" "sync"
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
@@ -1366,3 +1367,77 @@ func (s *SqlPostStore) GetRepliesForExport(parentId string) store.StoreChannel {
} }
}) })
} }
func (s *SqlPostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
query := s.getQueryBuilder().
Select("p.*", "Users.Username as User").
From("Posts p").
Join("Channels ON p.ChannelId = Channels.Id").
Join("Users ON p.UserId = Users.Id").
Where(sq.And{
sq.Gt{"p.Id": afterId},
sq.Eq{"p.ParentId": string("")},
sq.Eq{"p.DeleteAt": int(0)},
sq.Eq{"Channels.DeleteAt": int(0)},
sq.Eq{"Users.DeleteAt": int(0)},
sq.Eq{"Channels.Type": []string{"D", "G"}},
}).
OrderBy("p.Id").
Limit(uint64(limit))
queryString, args, err := query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
var posts []*model.DirectPostForExport
if _, err = s.GetReplica().Select(&posts, queryString, args...); err != nil {
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
}
var channelIds []string
for _, post := range posts {
channelIds = append(channelIds, post.ChannelId)
}
query = s.getQueryBuilder().
Select("*").
From("ChannelMembers cm").
Join("Users u ON ( u.Id = cm.UserId )").
Where(sq.Eq{
"cm.ChannelId": channelIds,
})
queryString, args, err = query.ToSql()
if err != nil {
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
return
}
var channelMembers []*model.ChannelMemberForExport
if _, err := s.GetReplica().Select(&channelMembers, queryString, args...); err != nil {
result.Err = model.NewAppError("SqlPostStore.GetDirectPostParentsForExportAfter", "store.sql_post.get_direct_posts.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Build a map of channels and their posts
postsChannelMap := make(map[string][]*model.DirectPostForExport)
for _, post := range posts {
post.ChannelMembers = &[]string{}
postsChannelMap[post.ChannelId] = append(postsChannelMap[post.ChannelId], post)
}
// Build a map of channels and their members
channelMembersMap := make(map[string][]string)
for _, member := range channelMembers {
channelMembersMap[member.ChannelId] = append(channelMembersMap[member.ChannelId], member.Username)
}
// Populate each post ChannelMembers extracting it from the channelMembersMap
for channelId := range channelMembersMap {
for _, post := range postsChannelMap[channelId] {
*post.ChannelMembers = channelMembersMap[channelId]
}
}
result.Data = posts
})
}

Просмотреть файл

@@ -10,5 +10,5 @@ import (
) )
func TestPostStore(t *testing.T) { func TestPostStore(t *testing.T) {
StoreTest(t, storetest.TestPostStore) StoreTestWithSqlSupplier(t, storetest.TestPostStore)
} }

Просмотреть файл

@@ -4,10 +4,10 @@
package sqlstore package sqlstore
import ( import (
sq "github.com/Masterminds/squirrel"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/mattermost/gorp" "github.com/mattermost/gorp"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
) )
@@ -98,4 +98,5 @@ type SqlStore interface {
TermsOfService() store.TermsOfServiceStore TermsOfService() store.TermsOfServiceStore
UserTermsOfService() store.UserTermsOfServiceStore UserTermsOfService() store.UserTermsOfServiceStore
LinkMetadata() store.LinkMetadataStore LinkMetadata() store.LinkMetadataStore
getQueryBuilder() sq.StatementBuilderType
} }

Просмотреть файл

@@ -15,6 +15,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
sq "github.com/Masterminds/squirrel"
"github.com/dyatlov/go-opengraph/opengraph" "github.com/dyatlov/go-opengraph/opengraph"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/lib/pq" "github.com/lib/pq"
@@ -1051,6 +1052,14 @@ func (ss *SqlSupplier) DropAllTables() {
ss.master.TruncateTables() ss.master.TruncateTables()
} }
func (ss *SqlSupplier) getQueryBuilder() sq.StatementBuilderType {
builder := sq.StatementBuilder.PlaceholderFormat(sq.Question)
if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
builder = builder.PlaceholderFormat(sq.Dollar)
}
return builder
}
type mattermConverter struct{} type mattermConverter struct{}
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) { func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {

Просмотреть файл

@@ -67,15 +67,11 @@ func NewSqlUserStore(sqlStore SqlStore, metrics einterfaces.MetricsInterface) st
metrics: metrics, metrics: metrics,
} }
us.usersQuery = sq. us.usersQuery = us.getQueryBuilder().
Select("u.*", "b.UserId IS NOT NULL AS IsBot"). Select("u.*", "b.UserId IS NOT NULL AS IsBot").
From("Users u"). From("Users u").
LeftJoin("Bots b ON ( b.UserId = u.Id )") LeftJoin("Bots b ON ( b.UserId = u.Id )")
if us.DriverName() == model.DATABASE_DRIVER_POSTGRES {
us.usersQuery = us.usersQuery.PlaceholderFormat(sq.Dollar)
}
for _, db := range sqlStore.GetAllConns() { for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.User{}, "Users").SetKeys(false, "Id") table := db.AddTableWithName(model.User{}, "Users").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26) table.ColMap("Id").SetMaxSize(26)

Просмотреть файл

@@ -187,6 +187,7 @@ type ChannelStore interface {
ClearAllCustomRoleAssignments() StoreChannel ClearAllCustomRoleAssignments() StoreChannel
MigratePublicChannels() error MigratePublicChannels() error
GetAllChannelsForExportAfter(limit int, afterId string) StoreChannel GetAllChannelsForExportAfter(limit int, afterId string) StoreChannel
GetAllDirectChannelsForExportAfter(limit int, afterId string) StoreChannel
GetChannelMembersForExport(userId string, teamId string) StoreChannel GetChannelMembersForExport(userId string, teamId string) StoreChannel
RemoveAllDeactivatedMembers(channelId string) StoreChannel RemoveAllDeactivatedMembers(channelId string) StoreChannel
} }
@@ -229,6 +230,7 @@ type PostStore interface {
GetMaxPostSize() StoreChannel GetMaxPostSize() StoreChannel
GetParentsForExportAfter(limit int, afterId string) StoreChannel GetParentsForExportAfter(limit int, afterId string) StoreChannel
GetRepliesForExport(parentId string) StoreChannel GetRepliesForExport(parentId string) StoreChannel
GetDirectPostParentsForExportAfter(limit int, afterId string) StoreChannel
} }
type UserStore interface { type UserStore interface {

Просмотреть файл

@@ -36,11 +36,11 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
createDefaultRoles(t, ss) createDefaultRoles(t, ss)
t.Run("Save", func(t *testing.T) { testChannelStoreSave(t, ss) }) t.Run("Save", func(t *testing.T) { testChannelStoreSave(t, ss) })
t.Run("SaveDirectChannel", func(t *testing.T) { testChannelStoreSaveDirectChannel(t, ss) }) t.Run("SaveDirectChannel", func(t *testing.T) { testChannelStoreSaveDirectChannel(t, ss, s) })
t.Run("CreateDirectChannel", func(t *testing.T) { testChannelStoreCreateDirectChannel(t, ss) }) t.Run("CreateDirectChannel", func(t *testing.T) { testChannelStoreCreateDirectChannel(t, ss) })
t.Run("Update", func(t *testing.T) { testChannelStoreUpdate(t, ss) }) t.Run("Update", func(t *testing.T) { testChannelStoreUpdate(t, ss) })
t.Run("GetChannelUnread", func(t *testing.T) { testGetChannelUnread(t, ss) }) t.Run("GetChannelUnread", func(t *testing.T) { testGetChannelUnread(t, ss) })
t.Run("Get", func(t *testing.T) { testChannelStoreGet(t, ss) }) t.Run("Get", func(t *testing.T) { testChannelStoreGet(t, ss, s) })
t.Run("GetForPost", func(t *testing.T) { testChannelStoreGetForPost(t, ss) }) t.Run("GetForPost", func(t *testing.T) { testChannelStoreGetForPost(t, ss) })
t.Run("Restore", func(t *testing.T) { testChannelStoreRestore(t, ss) }) t.Run("Restore", func(t *testing.T) { testChannelStoreRestore(t, ss) })
t.Run("Delete", func(t *testing.T) { testChannelStoreDelete(t, ss) }) t.Run("Delete", func(t *testing.T) { testChannelStoreDelete(t, ss) })
@@ -51,7 +51,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("ChannelMemberStore", func(t *testing.T) { testChannelMemberStore(t, ss) }) t.Run("ChannelMemberStore", func(t *testing.T) { testChannelMemberStore(t, ss) })
t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) }) t.Run("ChannelDeleteMemberStore", func(t *testing.T) { testChannelDeleteMemberStore(t, ss) })
t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) }) t.Run("GetChannels", func(t *testing.T) { testChannelStoreGetChannels(t, ss) })
t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss) }) t.Run("GetAllChannels", func(t *testing.T) { testChannelStoreGetAllChannels(t, ss, s) })
t.Run("GetMoreChannels", func(t *testing.T) { testChannelStoreGetMoreChannels(t, ss) }) t.Run("GetMoreChannels", func(t *testing.T) { testChannelStoreGetMoreChannels(t, ss) })
t.Run("GetPublicChannelsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsForTeam(t, ss) }) t.Run("GetPublicChannelsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsForTeam(t, ss) })
t.Run("GetPublicChannelsByIdsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsByIdsForTeam(t, ss) }) t.Run("GetPublicChannelsByIdsForTeam", func(t *testing.T) { testChannelStoreGetPublicChannelsByIdsForTeam(t, ss) })
@@ -67,7 +67,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("SearchMore", func(t *testing.T) { testChannelStoreSearchMore(t, ss) }) t.Run("SearchMore", func(t *testing.T) { testChannelStoreSearchMore(t, ss) })
t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) }) t.Run("SearchInTeam", func(t *testing.T) { testChannelStoreSearchInTeam(t, ss) })
t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) }) t.Run("SearchAllChannels", func(t *testing.T) { testChannelStoreSearchAllChannels(t, ss) })
t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss) }) t.Run("AutocompleteInTeamForSearch", func(t *testing.T) { testChannelStoreAutocompleteInTeamForSearch(t, ss, s) })
t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) }) t.Run("GetMembersByIds", func(t *testing.T) { testChannelStoreGetMembersByIds(t, ss) })
t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) }) t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) })
t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) }) t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) })
@@ -80,6 +80,9 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("GetAllChannelsForExportAfter", func(t *testing.T) { testChannelStoreGetAllChannelsForExportAfter(t, ss) }) t.Run("GetAllChannelsForExportAfter", func(t *testing.T) { testChannelStoreGetAllChannelsForExportAfter(t, ss) })
t.Run("GetChannelMembersForExport", func(t *testing.T) { testChannelStoreGetChannelMembersForExport(t, ss) }) t.Run("GetChannelMembersForExport", func(t *testing.T) { testChannelStoreGetChannelMembersForExport(t, ss) })
t.Run("RemoveAllDeactivatedMembers", func(t *testing.T) { testChannelStoreRemoveAllDeactivatedMembers(t, ss) }) t.Run("RemoveAllDeactivatedMembers", func(t *testing.T) { testChannelStoreRemoveAllDeactivatedMembers(t, ss) })
t.Run("ExportAllDirectChannels", func(t *testing.T) { testChannelStoreExportAllDirectChannels(t, ss, s) })
t.Run("ExportAllDirectChannelsExcludePrivateAndPublic", func(t *testing.T) { testChannelStoreExportAllDirectChannelsExcludePrivateAndPublic(t, ss, s) })
t.Run("ExportAllDirectChannelsDeletedChannel", func(t *testing.T) { testChannelStoreExportAllDirectChannelsDeletedChannel(t, ss, s) })
} }
func testChannelStoreSave(t *testing.T, ss store.Store) { func testChannelStoreSave(t *testing.T, ss store.Store) {
@@ -112,7 +115,7 @@ func testChannelStoreSave(t *testing.T, ss store.Store) {
} }
} }
func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store) { func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId() teamId := model.NewId()
o1 := model.Channel{} o1 := model.Channel{}
@@ -194,6 +197,8 @@ func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store) {
t.Fatal("should have saved just 1 member") t.Fatal("should have saved just 1 member")
} }
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
} }
func testChannelStoreCreateDirectChannel(t *testing.T, ss store.Store) { func testChannelStoreCreateDirectChannel(t *testing.T, ss store.Store) {
@@ -340,7 +345,7 @@ func testGetChannelUnread(t *testing.T, ss store.Store) {
} }
} }
func testChannelStoreGet(t *testing.T, ss store.Store) { func testChannelStoreGet(t *testing.T, ss store.Store, s SqlSupplier) {
o1 := model.Channel{} o1 := model.Channel{}
o1.TeamId = model.NewId() o1.TeamId = model.NewId()
o1.DisplayName = "Name" o1.DisplayName = "Name"
@@ -423,6 +428,8 @@ func testChannelStoreGet(t *testing.T, ss store.Store) {
t.Fatal("too little") t.Fatal("too little")
} }
} }
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
} }
func testChannelStoreGetForPost(t *testing.T, ss store.Store) { func testChannelStoreGetForPost(t *testing.T, ss store.Store) {
@@ -962,7 +969,7 @@ func testChannelStoreGetChannels(t *testing.T, ss store.Store) {
ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId) ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId)
} }
func testChannelStoreGetAllChannels(t *testing.T, ss store.Store) { func testChannelStoreGetAllChannels(t *testing.T, ss store.Store, s SqlSupplier) {
cleanupChannels(t, ss) cleanupChannels(t, ss)
t1 := model.Team{} t1 := model.Team{}
@@ -1035,6 +1042,9 @@ func testChannelStoreGetAllChannels(t *testing.T, ss store.Store) {
assert.Len(t, *list, 1) assert.Len(t, *list, 1)
assert.Equal(t, (*list)[0].Id, c1.Id) assert.Equal(t, (*list)[0].Id, c1.Id)
assert.Equal(t, (*list)[0].TeamDisplayName, "Name") assert.Equal(t, (*list)[0].TeamDisplayName, "Name")
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
} }
func testChannelStoreGetMoreChannels(t *testing.T, ss store.Store) { func testChannelStoreGetMoreChannels(t *testing.T, ss store.Store) {
@@ -2306,7 +2316,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) {
} }
} }
func testChannelStoreAutocompleteInTeamForSearch(t *testing.T, ss store.Store) { func testChannelStoreAutocompleteInTeamForSearch(t *testing.T, ss store.Store, s SqlSupplier) {
u1 := &model.User{} u1 := &model.User{}
u1.Email = MakeEmail() u1.Email = MakeEmail()
u1.Username = "user1" + model.NewId() u1.Username = "user1" + model.NewId()
@@ -2419,6 +2429,9 @@ func testChannelStoreAutocompleteInTeamForSearch(t *testing.T, ss store.Store) {
require.Len(t, *channels, 2) require.Len(t, *channels, 2)
}) })
} }
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
} }
func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) { func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) {
@@ -3158,3 +3171,160 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store) {
assert.Len(t, *d2, 1) assert.Len(t, *d2, 1)
assert.Equal(t, (*d2)[0].UserId, u3.Id) assert.Equal(t, (*d2)[0].UserId, u3.Id)
} }
func testChannelStoreExportAllDirectChannels(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "Name" + model.NewId()
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
userIds := []string{model.NewId(), model.NewId(), model.NewId()}
o2 := model.Channel{}
o2.Name = model.GetGroupNameFromUserIds(userIds)
o2.DisplayName = "GroupChannel" + model.NewId()
o2.Name = "zz" + model.NewId() + "b"
o2.Type = model.CHANNEL_GROUP
store.Must(ss.Channel().Save(&o2, -1))
u1 := &model.User{}
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
<-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
r1 := <-ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 2, len(d1))
assert.ElementsMatch(t, []string{o1.DisplayName, o2.DisplayName}, []string{d1[0].DisplayName, d1[1].DisplayName})
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}
func testChannelStoreExportAllDirectChannelsExcludePrivateAndPublic(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "The Direct Channel" + model.NewId()
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
o2 := model.Channel{}
o2.TeamId = teamId
o2.DisplayName = "Channel2" + model.NewId()
o2.Name = "zz" + model.NewId() + "b"
o2.Type = model.CHANNEL_OPEN
store.Must(ss.Channel().Save(&o2, -1))
o3 := model.Channel{}
o3.TeamId = teamId
o3.DisplayName = "Channel3" + model.NewId()
o3.Name = "zz" + model.NewId() + "b"
o3.Type = model.CHANNEL_PRIVATE
store.Must(ss.Channel().Save(&o3, -1))
u1 := &model.User{}
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
<-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
r1 := <-ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 1, len(d1))
assert.Equal(t, o1.DisplayName, d1[0].DisplayName)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}
func testChannelStoreExportAllDirectChannelsDeletedChannel(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "Different Name" + model.NewId()
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
u1 := &model.User{}
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
result := <-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
o1.DeleteAt = 1
result = <-ss.Channel().SetDeleteAt(o1.Id, 1, 1)
assert.Nil(t, result.Err)
r1 := <-ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectChannelForExport)
assert.Equal(t, 0, len(d1))
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}

Просмотреть файл

@@ -226,6 +226,22 @@ func (_m *ChannelStore) GetAllChannelsForExportAfter(limit int, afterId string)
return r0 return r0
} }
// GetAllDirectChannelsForExportAfter provides a mock function with given fields: limit, afterId
func (_m *ChannelStore) GetAllDirectChannelsForExportAfter(limit int, afterId string) store.StoreChannel {
ret := _m.Called(limit, afterId)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int, string) store.StoreChannel); ok {
r0 = rf(limit, afterId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}
// GetByName provides a mock function with given fields: team_id, name, allowFromCache // GetByName provides a mock function with given fields: team_id, name, allowFromCache
func (_m *ChannelStore) GetByName(team_id string, name string, allowFromCache bool) store.StoreChannel { func (_m *ChannelStore) GetByName(team_id string, name string, allowFromCache bool) store.StoreChannel {
ret := _m.Called(team_id, name, allowFromCache) ret := _m.Called(team_id, name, allowFromCache)

Просмотреть файл

@@ -470,3 +470,18 @@ func (_m *PostStore) Update(newPost *model.Post, oldPost *model.Post) store.Stor
return r0 return r0
} }
func (_m *PostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) store.StoreChannel {
ret := _m.Called(limit, afterId)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(int, string) store.StoreChannel); ok {
r0 = rf(limit, afterId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)
}
}
return r0
}

Просмотреть файл

@@ -5,6 +5,7 @@ package storetest
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -16,7 +17,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestPostStore(t *testing.T, ss store.Store) { func TestPostStore(t *testing.T, ss store.Store, s SqlSupplier) {
t.Run("Save", func(t *testing.T) { testPostStoreSave(t, ss) }) t.Run("Save", func(t *testing.T) { testPostStoreSave(t, ss) })
t.Run("SaveAndUpdateChannelMsgCounts", func(t *testing.T) { testPostStoreSaveChannelMsgCounts(t, ss) }) t.Run("SaveAndUpdateChannelMsgCounts", func(t *testing.T) { testPostStoreSaveChannelMsgCounts(t, ss) })
t.Run("Get", func(t *testing.T) { testPostStoreGet(t, ss) }) t.Run("Get", func(t *testing.T) { testPostStoreGet(t, ss) })
@@ -35,7 +36,7 @@ func TestPostStore(t *testing.T, ss store.Store) {
t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) }) t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) })
t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) }) t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) })
t.Run("PostCountsByDay", func(t *testing.T) { testPostCountsByDay(t, ss) }) t.Run("PostCountsByDay", func(t *testing.T) { testPostCountsByDay(t, ss) })
t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss) }) t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss, s) })
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) }) t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) })
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) }) t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) })
t.Run("GetPostsCreatedAt", func(t *testing.T) { testPostStoreGetPostsCreatedAt(t, ss) }) t.Run("GetPostsCreatedAt", func(t *testing.T) { testPostStoreGetPostsCreatedAt(t, ss) })
@@ -47,6 +48,9 @@ func TestPostStore(t *testing.T, ss store.Store) {
t.Run("TestGetMaxPostSize", func(t *testing.T) { testGetMaxPostSize(t, ss) }) t.Run("TestGetMaxPostSize", func(t *testing.T) { testGetMaxPostSize(t, ss) })
t.Run("GetParentsForExportAfter", func(t *testing.T) { testPostStoreGetParentsForExportAfter(t, ss) }) t.Run("GetParentsForExportAfter", func(t *testing.T) { testPostStoreGetParentsForExportAfter(t, ss) })
t.Run("GetRepliesForExport", func(t *testing.T) { testPostStoreGetRepliesForExport(t, ss) }) t.Run("GetRepliesForExport", func(t *testing.T) { testPostStoreGetRepliesForExport(t, ss) })
t.Run("GetDirectPostParentsForExportAfter", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfter(t, ss, s) })
t.Run("GetDirectPostParentsForExportAfterDeleted", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterDeleted(t, ss, s) })
t.Run("GetDirectPostParentsForExportAfterBatched", func(t *testing.T) { testPostStoreGetDirectPostParentsForExportAfterBatched(t, ss, s) })
} }
func testPostStoreSave(t *testing.T, ss store.Store) { func testPostStoreSave(t *testing.T, ss store.Store) {
@@ -1192,7 +1196,7 @@ func testPostCountsByDay(t *testing.T, ss store.Store) {
} }
} }
func testPostStoreGetFlaggedPostsForTeam(t *testing.T, ss store.Store) { func testPostStoreGetFlaggedPostsForTeam(t *testing.T, ss store.Store, s SqlSupplier) {
c1 := &model.Channel{} c1 := &model.Channel{}
c1.TeamId = model.NewId() c1.TeamId = model.NewId()
c1.DisplayName = "Channel1" c1.DisplayName = "Channel1"
@@ -1365,6 +1369,9 @@ func testPostStoreGetFlaggedPostsForTeam(t *testing.T, ss store.Store) {
if len(r4.Order) != 3 { if len(r4.Order) != 3 {
t.Fatal("should have 3 posts") t.Fatal("should have 3 posts")
} }
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
} }
func testPostStoreGetFlaggedPosts(t *testing.T, ss store.Store) { func testPostStoreGetFlaggedPosts(t *testing.T, ss store.Store) {
@@ -1972,3 +1979,190 @@ func testPostStoreGetRepliesForExport(t *testing.T, ss store.Store) {
assert.Equal(t, reply1.Username, u1.Username) assert.Equal(t, reply1.Username, u1.Username)
} }
func testPostStoreGetDirectPostParentsForExportAfter(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "Name"
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
u1 := &model.User{}
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
<-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
p1 := &model.Post{}
p1.ChannelId = o1.Id
p1.UserId = u1.Id
p1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
p1.CreateAt = 1000
p1 = (<-ss.Post().Save(p1)).Data.(*model.Post)
r1 := <-ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectPostForExport)
assert.Equal(t, p1.Message, d1[0].Message)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}
func testPostStoreGetDirectPostParentsForExportAfterDeleted(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "Name"
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
u1 := &model.User{}
u1.DeleteAt = 1
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.DeleteAt = 1
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
<-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
o1.DeleteAt = 1
result := <-ss.Channel().SetDeleteAt(o1.Id, 1, 1)
assert.Nil(t, result.Err)
p1 := &model.Post{}
p1.ChannelId = o1.Id
p1.UserId = u1.Id
p1.Message = "zz" + model.NewId() + "BBBBBBBBBBBB"
p1.CreateAt = 1000
p1 = (<-ss.Post().Save(p1)).Data.(*model.Post)
o1a := &model.Post{}
*o1a = *p1
o1a.DeleteAt = 1
o1a.Message = p1.Message + "BBBBBBBBBB"
if result := <-ss.Post().Update(o1a, p1); result.Err != nil {
t.Fatal(result.Err)
}
r1 := <-ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectPostForExport)
assert.Equal(t, 0, len(d1))
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}
func testPostStoreGetDirectPostParentsForExportAfterBatched(t *testing.T, ss store.Store, s SqlSupplier) {
teamId := model.NewId()
o1 := model.Channel{}
o1.TeamId = teamId
o1.DisplayName = "Name"
o1.Name = "zz" + model.NewId() + "b"
o1.Type = model.CHANNEL_DIRECT
var postIds []string
for i := 0; i < 150; i++ {
u1 := &model.User{}
u1.Email = MakeEmail()
u1.Nickname = model.NewId()
store.Must(ss.User().Save(u1))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u1.Id}, -1))
u2 := &model.User{}
u2.Email = MakeEmail()
u2.Nickname = model.NewId()
store.Must(ss.User().Save(u2))
store.Must(ss.Team().SaveMember(&model.TeamMember{TeamId: model.NewId(), UserId: u2.Id}, -1))
m1 := model.ChannelMember{}
m1.ChannelId = o1.Id
m1.UserId = u1.Id
m1.NotifyProps = model.GetDefaultChannelNotifyProps()
m2 := model.ChannelMember{}
m2.ChannelId = o1.Id
m2.UserId = u2.Id
m2.NotifyProps = model.GetDefaultChannelNotifyProps()
<-ss.Channel().SaveDirectChannel(&o1, &m1, &m2)
p1 := &model.Post{}
p1.ChannelId = o1.Id
p1.UserId = u1.Id
p1.Message = "zz" + model.NewId() + "AAAAAAAAAAA"
p1.CreateAt = 1000
p1 = (<-ss.Post().Save(p1)).Data.(*model.Post)
postIds = append(postIds, p1.Id)
}
sort.Slice(postIds, func(i, j int) bool { return postIds[i] < postIds[j] })
// Get all posts
r1 := <-ss.Post().GetDirectPostParentsForExportAfter(10000, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 := r1.Data.([]*model.DirectPostForExport)
assert.Equal(t, len(postIds), len(d1))
var exportedPostIds []string
for i := range d1 {
exportedPostIds = append(exportedPostIds, d1[i].Id)
}
sort.Slice(exportedPostIds, func(i, j int) bool { return exportedPostIds[i] < exportedPostIds[j] })
assert.ElementsMatch(t, postIds, exportedPostIds)
// Get 100
r1 = <-ss.Post().GetDirectPostParentsForExportAfter(100, strings.Repeat("0", 26))
assert.Nil(t, r1.Err)
d1 = r1.Data.([]*model.DirectPostForExport)
assert.Equal(t, 100, len(d1))
exportedPostIds = []string{}
for i := range d1 {
exportedPostIds = append(exportedPostIds, d1[i].Id)
}
sort.Slice(exportedPostIds, func(i, j int) bool { return exportedPostIds[i] < exportedPostIds[j] })
assert.ElementsMatch(t, postIds[:100], exportedPostIds)
// Manually truncate Channels table until testlib can handle cleanups
s.GetMaster().Exec("TRUNCATE Channels")
}