expose additional channel metadata (#4539)

builds out more details for channel messages and replies

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [ ] 🌻 Feature

#### Issue(s)

* #3988

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-10-26 12:03:44 -06:00 committed by GitHub
parent eb188e0514
commit 1470776f3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 633 additions and 153 deletions

View File

@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SharePoint backup would fail if any site had an empty display name - SharePoint backup would fail if any site had an empty display name
- Fix a bug with exports hanging post completion - Fix a bug with exports hanging post completion
### Changed
- Item Details formatting in Groups and Teams backups. Pre-release users will need to run new backups to avoid data corruption.
## [v0.14.2] (beta) - 2023-10-17 ## [v0.14.2] (beta) - 2023-10-17
### Added ### Added

View File

@ -90,10 +90,14 @@ func streamItems(
type ( type (
minimumChannelMessage struct { minimumChannelMessage struct {
// TODO(keepers): remove attachmentNames when better formatting
// of attachments within the content body is implemented.
AttachmentNames []string `json:"attachmentNames"`
Content string `json:"content"` Content string `json:"content"`
CreatedDateTime time.Time `json:"createdDateTime"` CreatedDateTime time.Time `json:"createdDateTime"`
From string `json:"from"` From string `json:"from"`
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"` LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
Subject string `json:"subject"`
} }
minimumChannelMessageAndReplies struct { minimumChannelMessageAndReplies struct {
@ -155,9 +159,11 @@ func makeMinimumChannelMesasge(item models.ChatMessageable) minimumChannelMessag
} }
return minimumChannelMessage{ return minimumChannelMessage{
AttachmentNames: api.GetChatMessageAttachmentNames(item),
Content: content, Content: content,
CreatedDateTime: ptr.Val(item.GetCreatedDateTime()), CreatedDateTime: ptr.Val(item.GetCreatedDateTime()),
From: api.GetChatMessageFrom(item), From: api.GetChatMessageFrom(item),
LastModifiedDateTime: ptr.Val(item.GetLastModifiedDateTime()), LastModifiedDateTime: ptr.Val(item.GetLastModifiedDateTime()),
Subject: ptr.Val(item.GetSubject()),
} }
} }

View File

@ -37,25 +37,33 @@ func NewGroupsLocationIDer(
// GroupsInfo describes a groups item // GroupsInfo describes a groups item
type GroupsInfo struct { type GroupsInfo struct {
Created time.Time `json:"created,omitempty"` ItemType ItemType `json:"itemType,omitempty"`
ItemName string `json:"itemName,omitempty"` Modified time.Time `json:"modified,omitempty"`
ItemType ItemType `json:"itemType,omitempty"`
Modified time.Time `json:"modified,omitempty"`
Owner string `json:"owner,omitempty"`
ParentPath string `json:"parentPath,omitempty"`
Size int64 `json:"size,omitempty"`
// Channels Specific // Channels Specific
LastReplyAt time.Time `json:"lastResponseAt,omitempty"` Message ChannelMessageInfo `json:"message"`
MessageCreator string `json:"messageCreator,omitempty"` LastReply ChannelMessageInfo `json:"lastReply"`
MessagePreview string `json:"messagePreview,omitempty"`
ReplyCount int `json:"replyCount,omitempty"`
// SharePoint specific // SharePoint specific
DriveName string `json:"driveName,omitempty"` Created time.Time `json:"created,omitempty"`
DriveID string `json:"driveID,omitempty"` DriveName string `json:"driveName,omitempty"`
SiteID string `json:"siteID,omitempty"` DriveID string `json:"driveID,omitempty"`
WebURL string `json:"webURL,omitempty"` ItemName string `json:"itemName,omitempty"`
Owner string `json:"owner,omitempty"`
ParentPath string `json:"parentPath,omitempty"`
SiteID string `json:"siteID,omitempty"`
Size int64 `json:"size,omitempty"`
WebURL string `json:"webURL,omitempty"`
}
type ChannelMessageInfo struct {
AttachmentNames []string `json:"attachmentNames,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
Creator string `json:"creator,omitempty"`
Preview string `json:"preview,omitempty"`
ReplyCount int `json:"replyCount"`
Size int64 `json:"size,omitempty"`
Subject string `json:"subject,omitempty"`
} }
// Headers returns the human-readable names of properties in a SharePointInfo // Headers returns the human-readable names of properties in a SharePointInfo
@ -65,7 +73,7 @@ func (i GroupsInfo) Headers() []string {
case SharePointLibrary: case SharePointLibrary:
return []string{"ItemName", "Library", "ParentPath", "Size", "Owner", "Created", "Modified"} return []string{"ItemName", "Library", "ParentPath", "Size", "Owner", "Created", "Modified"}
case GroupsChannelMessage: case GroupsChannelMessage:
return []string{"Message", "Channel", "Replies", "Creator", "Created", "Last Reply"} return []string{"Message", "Channel", "Subject", "Replies", "Creator", "Created", "Last Reply"}
} }
return []string{} return []string{}
@ -86,17 +94,18 @@ func (i GroupsInfo) Values() []string {
dttm.FormatToTabularDisplay(i.Modified), dttm.FormatToTabularDisplay(i.Modified),
} }
case GroupsChannelMessage: case GroupsChannelMessage:
lastReply := dttm.FormatToTabularDisplay(i.LastReplyAt) lastReply := dttm.FormatToTabularDisplay(i.LastReply.CreatedAt)
if i.LastReplyAt.Equal(time.Time{}) { if i.LastReply.CreatedAt.IsZero() {
lastReply = "" lastReply = ""
} }
return []string{ return []string{
i.MessagePreview, i.Message.Preview,
i.ParentPath, i.ParentPath,
strconv.Itoa(i.ReplyCount), i.Message.Subject,
i.MessageCreator, strconv.Itoa(i.Message.ReplyCount),
dttm.FormatToTabularDisplay(i.Created), i.Message.Creator,
dttm.FormatToTabularDisplay(i.Message.CreatedAt),
lastReply, lastReply,
} }
} }

View File

@ -0,0 +1,58 @@
package details_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details"
)
type GroupsUnitSuite struct {
tester.Suite
}
func TestGroupsUnitSuite(t *testing.T) {
suite.Run(t, &GroupsUnitSuite{Suite: tester.NewUnitSuite(t)})
}
func (suite *GroupsUnitSuite) TestGroupsPrintable() {
t := suite.T()
now := time.Now()
then := now.Add(time.Minute)
gi := details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
ParentPath: "parentPath",
Message: details.ChannelMessageInfo{
Preview: "preview",
ReplyCount: 1,
Creator: "creator",
CreatedAt: now,
Subject: "subject",
},
LastReply: details.ChannelMessageInfo{
CreatedAt: then,
},
}
expectVs := []string{
"preview",
"parentPath",
"subject",
"1",
"creator",
dttm.FormatToTabularDisplay(now),
dttm.FormatToTabularDisplay(then),
}
hs := gi.Headers()
vs := gi.Values()
assert.Equal(t, len(hs), len(vs))
assert.Equal(t, expectVs, vs)
}

View File

@ -3,7 +3,6 @@ package selectors
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
@ -826,15 +825,15 @@ func (s GroupsScope) matchesInfo(dii details.ItemInfo) bool {
case GroupsInfoLibraryItemModifiedAfter, GroupsInfoLibraryItemModifiedBefore: case GroupsInfoLibraryItemModifiedAfter, GroupsInfoLibraryItemModifiedBefore:
i = dttm.Format(info.Modified) i = dttm.Format(info.Modified)
case GroupsInfoChannelMessageCreator: case GroupsInfoChannelMessageCreator:
i = info.MessageCreator i = info.Message.Creator
case GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore: case GroupsInfoChannelMessageCreatedAfter, GroupsInfoChannelMessageCreatedBefore:
i = dttm.Format(info.Created) i = dttm.Format(info.Message.CreatedAt)
case GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore: case GroupsInfoChannelMessageLastReplyAfter, GroupsInfoChannelMessageLastReplyBefore:
if info.LastReplyAt.Equal(time.Time{}) { if info.LastReply.CreatedAt.IsZero() {
return false return false
} }
i = dttm.Format(info.LastReplyAt) i = dttm.Format(info.LastReply.CreatedAt)
} }
return s.Matches(infoCat, i) && int(info.ItemType) == acceptableItemType return s.Matches(infoCat, i) && int(info.ItemType) == acceptableItemType

View File

@ -370,53 +370,67 @@ func (suite *GroupsSelectorSuite) TestGroupsScope_MatchesInfo() {
dspl = details.SharePointLibrary dspl = details.SharePointLibrary
) )
type expectation func(t assert.TestingT, value bool, msg string, args ...any) bool
table := []struct { table := []struct {
name string name string
itemType details.ItemType itemType details.ItemType
creator string creator string
scope []GroupsScope scope []GroupsScope
expect assert.BoolAssertionFunc expect expectation
}{ }{
{"file create after the epoch", dspl, user, sel.CreatedAfter(dttm.Format(epoch)), assert.True}, {"file create after the epoch", dspl, user, sel.CreatedAfter(dttm.Format(epoch)), assert.Truef},
{"file create after the epoch wrong type", dgcm, user, sel.CreatedAfter(dttm.Format(epoch)), assert.False}, {"file create after the epoch wrong type", dgcm, user, sel.CreatedAfter(dttm.Format(epoch)), assert.Falsef},
{"file create after now", dspl, user, sel.CreatedAfter(dttm.Format(now)), assert.False}, {"file create after now", dspl, user, sel.CreatedAfter(dttm.Format(now)), assert.Falsef},
{"file create after later", dspl, user, sel.CreatedAfter(dttm.Format(future)), assert.False}, {"file create after later", dspl, user, sel.CreatedAfter(dttm.Format(future)), assert.Falsef},
{"file create before future", dspl, user, sel.CreatedBefore(dttm.Format(future)), assert.True}, {"file create before future", dspl, user, sel.CreatedBefore(dttm.Format(future)), assert.Truef},
{"file create before future wrong type", dgcm, user, sel.CreatedBefore(dttm.Format(future)), assert.False}, {"file create before future wrong type", dgcm, user, sel.CreatedBefore(dttm.Format(future)), assert.Falsef},
{"file create before now", dspl, user, sel.CreatedBefore(dttm.Format(now)), assert.False}, {"file create before now", dspl, user, sel.CreatedBefore(dttm.Format(now)), assert.Falsef},
{"file create before modification", dspl, user, sel.CreatedBefore(dttm.Format(mod)), assert.True}, {"file create before modification", dspl, user, sel.CreatedBefore(dttm.Format(mod)), assert.Truef},
{"file create before epoch", dspl, user, sel.CreatedBefore(dttm.Format(now)), assert.False}, {"file create before epoch", dspl, user, sel.CreatedBefore(dttm.Format(now)), assert.Falsef},
{"file modified after the epoch", dspl, user, sel.ModifiedAfter(dttm.Format(epoch)), assert.True}, {"file modified after the epoch", dspl, user, sel.ModifiedAfter(dttm.Format(epoch)), assert.Truef},
{"file modified after now", dspl, user, sel.ModifiedAfter(dttm.Format(now)), assert.True}, {"file modified after now", dspl, user, sel.ModifiedAfter(dttm.Format(now)), assert.Truef},
{"file modified after later", dspl, user, sel.ModifiedAfter(dttm.Format(future)), assert.False}, {"file modified after later", dspl, user, sel.ModifiedAfter(dttm.Format(future)), assert.Falsef},
{"file modified before future", dspl, user, sel.ModifiedBefore(dttm.Format(future)), assert.True}, {"file modified before future", dspl, user, sel.ModifiedBefore(dttm.Format(future)), assert.Truef},
{"file modified before now", dspl, user, sel.ModifiedBefore(dttm.Format(now)), assert.False}, {"file modified before now", dspl, user, sel.ModifiedBefore(dttm.Format(now)), assert.Falsef},
{"file modified before epoch", dspl, user, sel.ModifiedBefore(dttm.Format(now)), assert.False}, {"file modified before epoch", dspl, user, sel.ModifiedBefore(dttm.Format(now)), assert.Falsef},
{"in library", dspl, user, sel.Library("included-library"), assert.True}, {"in library", dspl, user, sel.Library("included-library"), assert.Truef},
{"not in library", dspl, user, sel.Library("not-included-library"), assert.False}, {"not in library", dspl, user, sel.Library("not-included-library"), assert.Falsef},
{"site id", dspl, user, sel.Site("site1"), assert.True}, {"site id", dspl, user, sel.Site("site1"), assert.Truef},
{"web url", dspl, user, sel.Site(user), assert.True}, {"web url", dspl, user, sel.Site(user), assert.Truef},
{"library id", dspl, user, sel.Library("1234"), assert.True}, {"library id", dspl, user, sel.Library("1234"), assert.Truef},
{"not library id", dspl, user, sel.Library("abcd"), assert.False}, {"not library id", dspl, user, sel.Library("abcd"), assert.Falsef},
{"channel message created by", dgcm, user, sel.MessageCreator(user), assert.True}, {"channel message created by", dgcm, user, sel.MessageCreator(user), assert.Truef},
{"channel message not created by", dgcm, user, sel.MessageCreator(host), assert.False}, {"channel message not created by", dgcm, user, sel.MessageCreator(host), assert.Falsef},
{"chan msg create after the epoch", dgcm, user, sel.MessageCreatedAfter(dttm.Format(epoch)), assert.True}, {"chan msg create after the epoch", dgcm, user, sel.MessageCreatedAfter(dttm.Format(epoch)), assert.Truef},
{"chan msg create after the epoch wrong type", dspl, user, sel.MessageCreatedAfter(dttm.Format(epoch)), assert.False}, {
{"chan msg create after now", dgcm, user, sel.MessageCreatedAfter(dttm.Format(now)), assert.False}, "chan msg create after the epoch wrong type",
{"chan msg create after later", dgcm, user, sel.MessageCreatedAfter(dttm.Format(future)), assert.False}, dspl,
{"chan msg create before future", dgcm, user, sel.MessageCreatedBefore(dttm.Format(future)), assert.True}, user,
{"chan msg create before future wrong type", dspl, user, sel.MessageCreatedBefore(dttm.Format(future)), assert.False}, sel.MessageCreatedAfter(dttm.Format(epoch)),
{"chan msg create before now", dgcm, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.False}, assert.Falsef,
{"chan msg create before reply", dgcm, user, sel.MessageCreatedBefore(dttm.Format(mod)), assert.True}, },
{"chan msg create before reply wrong type", dspl, user, sel.MessageCreatedBefore(dttm.Format(mod)), assert.False}, {"chan msg create after now", dgcm, user, sel.MessageCreatedAfter(dttm.Format(now)), assert.Falsef},
{"chan msg create before epoch", dgcm, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.False}, {"chan msg create after later", dgcm, user, sel.MessageCreatedAfter(dttm.Format(future)), assert.Falsef},
{"chan msg last reply after the epoch", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(epoch)), assert.True}, {"chan msg create before future", dgcm, user, sel.MessageCreatedBefore(dttm.Format(future)), assert.Truef},
{"chan msg last reply after now", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(now)), assert.True}, {
{"chan msg last reply after later", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(future)), assert.False}, "chan msg create before future wrong type",
{"chan msg last reply before future", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(future)), assert.True}, dspl,
{"chan msg last reply before now", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.False}, user,
{"chan msg last reply before epoch", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.False}, sel.MessageCreatedBefore(dttm.Format(future)),
assert.Falsef,
},
{"chan msg create before now", dgcm, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.Falsef},
{"chan msg create before reply", dgcm, user, sel.MessageCreatedBefore(dttm.Format(mod)), assert.Truef},
{"chan msg create before reply wrong type", dspl, user, sel.MessageCreatedBefore(dttm.Format(mod)), assert.Falsef},
{"chan msg create before epoch", dgcm, user, sel.MessageCreatedBefore(dttm.Format(now)), assert.Falsef},
{"chan msg last reply after the epoch", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(epoch)), assert.Truef},
{"chan msg last reply after now", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(now)), assert.Truef},
{"chan msg last reply after later", dgcm, user, sel.MessageLastReplyAfter(dttm.Format(future)), assert.Falsef},
{"chan msg last reply before future", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(future)), assert.Truef},
{"chan msg last reply before now", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.Falsef},
{"chan msg last reply before epoch", dgcm, user, sel.MessageLastReplyBefore(dttm.Format(now)), assert.Falsef},
} }
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
@ -424,21 +438,31 @@ func (suite *GroupsSelectorSuite) TestGroupsScope_MatchesInfo() {
itemInfo := details.ItemInfo{ itemInfo := details.ItemInfo{
Groups: &details.GroupsInfo{ Groups: &details.GroupsInfo{
ItemType: test.itemType, ItemType: test.itemType,
WebURL: test.creator, Created: now,
MessageCreator: test.creator, WebURL: test.creator,
Created: now, Modified: mod,
Modified: mod, DriveName: "included-library",
LastReplyAt: mod, DriveID: "1234",
DriveName: "included-library", SiteID: "site1",
DriveID: "1234", Message: details.ChannelMessageInfo{
SiteID: "site1", Creator: test.creator,
CreatedAt: now,
},
LastReply: details.ChannelMessageInfo{
CreatedAt: mod,
},
}, },
} }
scopes := setScopesToDefault(test.scope) scopes := setScopesToDefault(test.scope)
for _, scope := range scopes { for _, scope := range scopes {
test.expect(t, scope.matchesInfo(itemInfo)) test.expect(
t,
scope.matchesInfo(itemInfo),
"not matching:\nscope:\n\t%+v\ninfo:\n\t%+v",
scope,
itemInfo.Groups)
} }
}) })
} }

View File

@ -141,38 +141,58 @@ func channelMessageInfo(
msg models.ChatMessageable, msg models.ChatMessageable,
) *details.GroupsInfo { ) *details.GroupsInfo {
var ( var (
lastReply time.Time lastReply models.ChatMessageable
modTime = ptr.OrNow(msg.GetLastModifiedDateTime()) lastReplyAt time.Time
content string modTime = ptr.OrNow(msg.GetLastModifiedDateTime())
) )
for _, r := range msg.GetReplies() { replies := msg.GetReplies()
for _, r := range replies {
cdt := ptr.Val(r.GetCreatedDateTime()) cdt := ptr.Val(r.GetCreatedDateTime())
if cdt.After(lastReply) { if cdt.After(lastReplyAt) {
lastReply = cdt lastReply = r
lastReplyAt = ptr.Val(r.GetCreatedDateTime())
} }
} }
// if the message hasn't been modified since before the most recent // if the message hasn't been modified since before the most recent
// reply, set the modified time to the most recent reply. This ensures // reply, set the modified time to the most recent reply. This ensures
// we update the message contents to match changes in replies. // we update the message contents to match changes in replies.
if modTime.Before(lastReply) { if modTime.Before(lastReplyAt) {
modTime = lastReply modTime = lastReplyAt
} }
if msg.GetBody() != nil { preview, contentLen := GetChatMessageContentPreview(msg)
content = ptr.Val(msg.GetBody().GetContent())
message := details.ChannelMessageInfo{
AttachmentNames: GetChatMessageAttachmentNames(msg),
CreatedAt: ptr.Val(msg.GetCreatedDateTime()),
Creator: GetChatMessageFrom(msg),
Preview: preview,
ReplyCount: len(replies),
Size: contentLen,
Subject: ptr.Val(msg.GetSubject()),
}
var lr details.ChannelMessageInfo
if lastReply != nil {
preview, contentLen = GetChatMessageContentPreview(lastReply)
lr = details.ChannelMessageInfo{
AttachmentNames: GetChatMessageAttachmentNames(lastReply),
CreatedAt: ptr.Val(lastReply.GetCreatedDateTime()),
Creator: GetChatMessageFrom(lastReply),
Preview: preview,
Size: contentLen,
}
} }
return &details.GroupsInfo{ return &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: ptr.Val(msg.GetCreatedDateTime()), Modified: modTime,
LastReplyAt: lastReply, Message: message,
Modified: modTime, LastReply: lr,
MessageCreator: GetChatMessageFrom(msg),
MessagePreview: str.Preview(content, 128),
ReplyCount: len(msg.GetReplies()),
Size: int64(len(content)),
} }
} }
@ -212,3 +232,25 @@ func GetChatMessageFrom(msg models.ChatMessageable) string {
return "" return ""
} }
func GetChatMessageContentPreview(msg models.ChatMessageable) (string, int64) {
var content string
if msg.GetBody() != nil {
content = ptr.Val(msg.GetBody().GetContent())
}
return str.Preview(content, 128), int64(len(content))
}
func GetChatMessageAttachmentNames(msg models.ChatMessageable) []string {
names := make([]string, 0, len(msg.GetAttachments()))
for _, a := range msg.GetAttachments() {
if name := ptr.Val(a.GetName()); len(name) > 0 {
names = append(names, name)
}
}
return names
}

View File

@ -106,14 +106,16 @@ func testEnumerateChannelMessageReplies(
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
var ( var (
lastReply time.Time lastReply models.ChatMessageable
replyIDs = map[string]struct{}{} lastReplyAt time.Time
replyIDs = map[string]struct{}{}
) )
for _, r := range replies { for _, r := range replies {
cdt := ptr.Val(r.GetCreatedDateTime()) cdt := ptr.Val(r.GetCreatedDateTime())
if cdt.After(lastReply) { if cdt.After(lastReplyAt) {
lastReply = cdt lastReply = r
lastReplyAt = cdt
} }
replyIDs[ptr.Val(r.GetId())] = struct{}{} replyIDs[ptr.Val(r.GetId())] = struct{}{}
@ -122,10 +124,20 @@ func testEnumerateChannelMessageReplies(
assert.Equal(t, messageID, ptr.Val(msg.GetId())) assert.Equal(t, messageID, ptr.Val(msg.GetId()))
assert.Equal(t, channelID, ptr.Val(msg.GetChannelIdentity().GetChannelId())) assert.Equal(t, channelID, ptr.Val(msg.GetChannelIdentity().GetChannelId()))
assert.Equal(t, groupID, ptr.Val(msg.GetChannelIdentity().GetTeamId())) assert.Equal(t, groupID, ptr.Val(msg.GetChannelIdentity().GetTeamId()))
assert.Equal(t, len(replies), info.ReplyCount) // message
assert.Equal(t, msg.GetFrom().GetUser().GetDisplayName(), info.MessageCreator) assert.Equal(t, len(msg.GetAttachments()), len(info.Message.AttachmentNames))
assert.Equal(t, lastReply, info.LastReplyAt) assert.Equal(t, len(replies), info.Message.ReplyCount)
assert.Equal(t, str.Preview(ptr.Val(msg.GetBody().GetContent()), 128), info.MessagePreview) assert.Equal(t, lastReplyAt, info.Message.CreatedAt)
assert.Equal(t, msg.GetFrom().GetUser().GetDisplayName(), info.Message.Creator)
assert.Equal(t, str.Preview(ptr.Val(msg.GetBody().GetContent()), 128), info.Message.Preview)
assert.Equal(t, len(ptr.Val(msg.GetBody().GetContent())), info.Message.Size)
// last reply
assert.Equal(t, len(lastReply.GetAttachments()), len(info.LastReply.AttachmentNames))
assert.Zero(t, info.LastReply.ReplyCount)
assert.Equal(t, lastReplyAt, info.LastReply.CreatedAt)
assert.Equal(t, lastReply.GetFrom().GetUser().GetDisplayName(), info.LastReply.Creator)
assert.Equal(t, str.Preview(ptr.Val(lastReply.GetBody().GetContent()), 128), info.LastReply.Preview)
assert.Equal(t, len(ptr.Val(lastReply.GetBody().GetContent())), info.LastReply.Size)
msgReplyIDs := map[string]struct{}{} msgReplyIDs := map[string]struct{}{}

View File

@ -17,7 +17,7 @@ type ChannelsAPIUnitSuite struct {
tester.Suite tester.Suite
} }
func TestChannelsAPIUnitSuitee(t *testing.T) { func TestChannelsAPIUnitSuite(t *testing.T) {
suite.Run(t, &ChannelsAPIUnitSuite{Suite: tester.NewUnitSuite(t)}) suite.Run(t, &ChannelsAPIUnitSuite{Suite: tester.NewUnitSuite(t)})
} }
@ -26,12 +26,36 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
initial = time.Now().Add(-24 * time.Hour) initial = time.Now().Add(-24 * time.Hour)
mid = time.Now().Add(-1 * time.Hour) mid = time.Now().Add(-1 * time.Hour)
curr = time.Now() curr = time.Now()
)
content = "content" var (
body = models.NewItemBody() content = "content"
body = models.NewItemBody()
replyContent = "replycontent"
replyBody = models.NewItemBody()
) )
body.SetContent(ptr.To(content)) body.SetContent(ptr.To(content))
replyBody.SetContent(ptr.To(replyContent))
var (
attach1 = models.NewChatMessageAttachment()
attach2 = models.NewChatMessageAttachment()
replyAttach1 = models.NewChatMessageAttachment()
replyAttach2 = models.NewChatMessageAttachment()
)
attach1.SetName(ptr.To("attach1.ment"))
attach2.SetName(ptr.To("attach2.ment"))
replyAttach1.SetName(ptr.To("replyattach1.ment"))
replyAttach2.SetName(ptr.To("replyattach2.ment"))
var (
attachments = []models.ChatMessageAttachmentable{attach1, attach2}
replyAttachments = []models.ChatMessageAttachmentable{replyAttach1, replyAttach2}
expectAttachNames = []string{"attach1.ment", "attach2.ment"}
expectReplyAttachNames = []string{"replyattach1.ment", "replyattach2.ment"}
)
tests := []struct { tests := []struct {
name string name string
@ -43,6 +67,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg := models.NewChatMessage() msg := models.NewChatMessage()
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user")) iden.SetDisplayName(ptr.To("user"))
@ -53,12 +78,52 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: initial,
Modified: initial, LastReply: details.ChannelMessageInfo{},
LastReplyAt: time.Time{}, Message: details.ChannelMessageInfo{
ReplyCount: 0, AttachmentNames: []string{},
MessageCreator: "user", CreatedAt: initial,
Creator: "user",
ReplyCount: 0,
Preview: "",
Size: 0,
Subject: "subject",
},
}
return msg, i
},
},
{
name: "No Subject",
msgAndInfo: func() (models.ChatMessageable, *details.GroupsInfo) {
msg := models.NewChatMessage()
msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body)
iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user"))
from := models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
msg.SetFrom(from)
i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
Modified: initial,
LastReply: details.ChannelMessageInfo{},
Message: details.ChannelMessageInfo{
AttachmentNames: []string{},
CreatedAt: initial,
Creator: "user",
ReplyCount: 0,
Preview: content,
Size: int64(len(content)),
Subject: "",
},
} }
return msg, i return msg, i
@ -71,6 +136,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body) msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user")) iden.SetDisplayName(ptr.To("user"))
@ -81,14 +147,18 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: initial,
Modified: initial, LastReply: details.ChannelMessageInfo{},
LastReplyAt: time.Time{}, Message: details.ChannelMessageInfo{
ReplyCount: 0, AttachmentNames: []string{},
MessageCreator: "user", CreatedAt: initial,
Size: int64(len(content)), Creator: "user",
MessagePreview: content, ReplyCount: 0,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
} }
return msg, i return msg, i
@ -101,6 +171,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body) msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("app")) iden.SetDisplayName(ptr.To("app"))
@ -111,14 +182,18 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: initial,
Modified: initial, LastReply: details.ChannelMessageInfo{},
LastReplyAt: time.Time{}, Message: details.ChannelMessageInfo{
ReplyCount: 0, AttachmentNames: []string{},
MessageCreator: "app", CreatedAt: initial,
Size: int64(len(content)), Creator: "app",
MessagePreview: content, ReplyCount: 0,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
} }
return msg, i return msg, i
@ -131,6 +206,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body) msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("device")) iden.SetDisplayName(ptr.To("device"))
@ -141,14 +217,54 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: initial,
Modified: initial, LastReply: details.ChannelMessageInfo{},
LastReplyAt: time.Time{}, Message: details.ChannelMessageInfo{
ReplyCount: 0, AttachmentNames: []string{},
MessageCreator: "device", CreatedAt: initial,
Size: int64(len(content)), Creator: "device",
MessagePreview: content, ReplyCount: 0,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
}
return msg, i
},
},
{
name: "No Replies - with attachments",
msgAndInfo: func() (models.ChatMessageable, *details.GroupsInfo) {
msg := models.NewChatMessage()
msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
msg.SetAttachments(attachments)
iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user"))
from := models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
msg.SetFrom(from)
i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
Modified: initial,
LastReply: details.ChannelMessageInfo{},
Message: details.ChannelMessageInfo{
AttachmentNames: expectAttachNames,
CreatedAt: initial,
Creator: "user",
ReplyCount: 0,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
} }
return msg, i return msg, i
@ -161,6 +277,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body) msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user")) iden.SetDisplayName(ptr.To("user"))
@ -170,21 +287,42 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
// reply
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("replyuser"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply := models.NewChatMessage() reply := models.NewChatMessage()
reply.SetCreatedDateTime(&curr) reply.SetCreatedDateTime(&curr)
reply.SetLastModifiedDateTime(&curr) reply.SetLastModifiedDateTime(&curr)
reply.SetFrom(from)
reply.SetBody(replyBody)
msg.SetReplies([]models.ChatMessageable{reply}) msg.SetReplies([]models.ChatMessageable{reply})
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: curr,
Modified: curr, LastReply: details.ChannelMessageInfo{
LastReplyAt: curr, AttachmentNames: []string{},
ReplyCount: 1, CreatedAt: curr,
MessageCreator: "user", Creator: "replyuser",
Size: int64(len(content)), ReplyCount: 0,
MessagePreview: content, Preview: replyContent,
Size: int64(len(replyContent)),
},
Message: details.ChannelMessageInfo{
AttachmentNames: []string{},
CreatedAt: initial,
Creator: "user",
ReplyCount: 1,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
} }
return msg, i return msg, i
@ -197,6 +335,7 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetCreatedDateTime(&initial) msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial) msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body) msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity() iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user")) iden.SetDisplayName(ptr.To("user"))
@ -206,25 +345,197 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
msg.SetFrom(from) msg.SetFrom(from)
// replies
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply1user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply1 := models.NewChatMessage() reply1 := models.NewChatMessage()
reply1.SetCreatedDateTime(&mid) reply1.SetCreatedDateTime(&mid)
reply1.SetLastModifiedDateTime(&mid) reply1.SetLastModifiedDateTime(&mid)
reply1.SetFrom(from)
reply1.SetBody(replyBody)
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply2user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply2 := models.NewChatMessage() reply2 := models.NewChatMessage()
reply2.SetCreatedDateTime(&curr) reply2.SetCreatedDateTime(&curr)
reply2.SetLastModifiedDateTime(&curr) reply2.SetLastModifiedDateTime(&curr)
reply2.SetFrom(from)
reply2.SetBody(replyBody)
msg.SetReplies([]models.ChatMessageable{reply1, reply2}) msg.SetReplies([]models.ChatMessageable{reply1, reply2})
i := &details.GroupsInfo{ i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage, ItemType: details.GroupsChannelMessage,
Created: initial, Modified: curr,
Modified: curr, LastReply: details.ChannelMessageInfo{
LastReplyAt: curr, AttachmentNames: []string{},
ReplyCount: 2, CreatedAt: curr,
MessageCreator: "user", Creator: "reply2user",
Size: int64(len(content)), ReplyCount: 0,
MessagePreview: content, Preview: replyContent,
Size: int64(len(replyContent)),
},
Message: details.ChannelMessageInfo{
AttachmentNames: []string{},
CreatedAt: initial,
Creator: "user",
ReplyCount: 2,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
}
return msg, i
},
},
{
name: "Many Replies - not last has attachments",
msgAndInfo: func() (models.ChatMessageable, *details.GroupsInfo) {
msg := models.NewChatMessage()
msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user"))
from := models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
msg.SetFrom(from)
// replies
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply1user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply1 := models.NewChatMessage()
reply1.SetCreatedDateTime(&mid)
reply1.SetLastModifiedDateTime(&mid)
reply1.SetFrom(from)
reply1.SetBody(replyBody)
reply1.SetAttachments(replyAttachments)
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply2user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply2 := models.NewChatMessage()
reply2.SetCreatedDateTime(&curr)
reply2.SetLastModifiedDateTime(&curr)
reply2.SetFrom(from)
reply2.SetBody(replyBody)
msg.SetReplies([]models.ChatMessageable{reply1, reply2})
i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
Modified: curr,
LastReply: details.ChannelMessageInfo{
AttachmentNames: []string{},
CreatedAt: curr,
Creator: "reply2user",
ReplyCount: 0,
Preview: replyContent,
Size: int64(len(replyContent)),
},
Message: details.ChannelMessageInfo{
AttachmentNames: []string{},
CreatedAt: initial,
Creator: "user",
ReplyCount: 2,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
}
return msg, i
},
},
{
name: "Many Replies - last has attachments",
msgAndInfo: func() (models.ChatMessageable, *details.GroupsInfo) {
msg := models.NewChatMessage()
msg.SetCreatedDateTime(&initial)
msg.SetLastModifiedDateTime(&initial)
msg.SetBody(body)
msg.SetSubject(ptr.To("subject"))
msg.SetAttachments(attachments)
iden := models.NewIdentity()
iden.SetDisplayName(ptr.To("user"))
from := models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
msg.SetFrom(from)
// replies
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply1user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply1 := models.NewChatMessage()
reply1.SetCreatedDateTime(&mid)
reply1.SetLastModifiedDateTime(&mid)
reply1.SetFrom(from)
reply1.SetBody(replyBody)
iden = models.NewIdentity()
iden.SetDisplayName(ptr.To("reply2user"))
from = models.NewChatMessageFromIdentitySet()
from.SetUser(iden)
reply2 := models.NewChatMessage()
reply2.SetCreatedDateTime(&curr)
reply2.SetLastModifiedDateTime(&curr)
reply2.SetFrom(from)
reply2.SetBody(replyBody)
reply2.SetAttachments(replyAttachments)
msg.SetReplies([]models.ChatMessageable{reply1, reply2})
i := &details.GroupsInfo{
ItemType: details.GroupsChannelMessage,
Modified: curr,
LastReply: details.ChannelMessageInfo{
AttachmentNames: expectReplyAttachNames,
CreatedAt: curr,
Creator: "reply2user",
ReplyCount: 0,
Preview: replyContent,
Size: int64(len(replyContent)),
},
Message: details.ChannelMessageInfo{
AttachmentNames: expectAttachNames,
CreatedAt: initial,
Creator: "user",
ReplyCount: 2,
Preview: content,
Size: int64(len(content)),
Subject: "subject",
},
} }
return msg, i return msg, i
@ -233,8 +544,24 @@ func (suite *ChannelsAPIUnitSuite) TestChannelMessageInfo() {
} }
for _, test := range tests { for _, test := range tests {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T()
chMsg, expected := test.msgAndInfo() chMsg, expected := test.msgAndInfo()
assert.Equal(suite.T(), expected, channelMessageInfo(chMsg)) result := channelMessageInfo(chMsg)
ma := result.Message.AttachmentNames
result.Message.AttachmentNames = nil
ema := expected.Message.AttachmentNames
expected.Message.AttachmentNames = nil
lra := result.LastReply.AttachmentNames
result.LastReply.AttachmentNames = nil
elra := expected.LastReply.AttachmentNames
expected.LastReply.AttachmentNames = nil
assert.Equal(t, expected, result)
assert.ElementsMatch(t, ema, ma)
assert.ElementsMatch(t, elra, lra)
}) })
} }
} }