chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
|
|
@ -9,6 +10,74 @@ import (
|
|||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
func TestChannelCreateCreatesPermanentInviteAndHasLink(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "private group with main link",
|
||||
Megagroup: true,
|
||||
Date: 1_700_000_090,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
if !created.Channel.HasLink {
|
||||
t.Fatalf("created channel HasLink = false, want true")
|
||||
}
|
||||
view, err := store.GetChannel(ctx, 1, created.Channel.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get channel: %v", err)
|
||||
}
|
||||
if !view.Channel.HasLink {
|
||||
t.Fatalf("stored channel HasLink = false, want true")
|
||||
}
|
||||
if view.ExportedInvite == nil || !view.ExportedInvite.Permanent || view.ExportedInvite.Revoked || view.ExportedInvite.AdminUserID != 1 {
|
||||
t.Fatalf("owner view exported invite = %+v, want creator permanent main link", view.ExportedInvite)
|
||||
}
|
||||
invites, err := store.ListExportedInvites(ctx, domain.ChannelInviteListRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
AdminUserID: 1,
|
||||
Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list exported invites: %v", err)
|
||||
}
|
||||
if invites.Count != 1 || len(invites.Invites) != 1 || !invites.Invites[0].Permanent || invites.Invites[0].Revoked {
|
||||
t.Fatalf("invites = %+v, want one active permanent main link", invites)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelViewExportedInviteIsAdminOnly(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "private group with regular member",
|
||||
Megagroup: true,
|
||||
MemberUserIDs: []int64{2},
|
||||
Date: 1_700_000_091,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
ownerView, err := store.GetChannel(ctx, 1, created.Channel.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get owner channel: %v", err)
|
||||
}
|
||||
if ownerView.ExportedInvite == nil || !ownerView.ExportedInvite.Permanent {
|
||||
t.Fatalf("owner exported invite = %+v, want permanent main link", ownerView.ExportedInvite)
|
||||
}
|
||||
memberView, err := store.GetChannel(ctx, 2, created.Channel.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get member channel: %v", err)
|
||||
}
|
||||
if memberView.ExportedInvite != nil {
|
||||
t.Fatalf("member exported invite = %+v, want nil", memberView.ExportedInvite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelRealtimeRecipientsAreCapped(t *testing.T) {
|
||||
store := NewChannelStore()
|
||||
memberIDs := make([]int64, domain.MaxChannelRealtimeFanout+25)
|
||||
|
|
@ -39,6 +108,71 @@ func TestChannelRealtimeRecipientsAreCapped(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestChannelCreatorLeaveTransfersOwner(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "creator transfer",
|
||||
Megagroup: true,
|
||||
MemberUserIDs: []int64{2, 3},
|
||||
Date: 1_700_000_110,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
if _, err := store.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
MemberID: 3,
|
||||
AdminRights: domain.ChannelAdminRights{
|
||||
ChangeInfo: true,
|
||||
AddAdmins: true,
|
||||
},
|
||||
Date: 1_700_000_111,
|
||||
}); err != nil {
|
||||
t.Fatalf("promote admin: %v", err)
|
||||
}
|
||||
future, err := store.FutureCreatorAfterLeave(ctx, created.Channel.ID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("future creator: %v", err)
|
||||
}
|
||||
if future.UserID != 3 {
|
||||
t.Fatalf("future creator = %+v, want admin user 3", future)
|
||||
}
|
||||
left, err := store.LeaveChannel(ctx, created.Channel.ID, 1, 1_700_000_112)
|
||||
if err != nil {
|
||||
t.Fatalf("creator leave: %v", err)
|
||||
}
|
||||
if left.Channel.CreatorUserID != 3 || left.Channel.ParticipantsCount != 2 || left.Channel.AdminsCount != 1 {
|
||||
t.Fatalf("channel after creator leave = %+v, want owner=3 participants=2 admins=1", left.Channel)
|
||||
}
|
||||
newOwner, err := store.GetParticipant(ctx, 3, created.Channel.ID, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("get new owner: %v", err)
|
||||
}
|
||||
if newOwner.Role != domain.ChannelRoleCreator || newOwner.Status != domain.ChannelMemberActive {
|
||||
t.Fatalf("new owner = %+v, want active creator", newOwner)
|
||||
}
|
||||
oldOwner, err := store.GetParticipant(ctx, 3, created.Channel.ID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("get old owner: %v", err)
|
||||
}
|
||||
if oldOwner.Status != domain.ChannelMemberLeft || oldOwner.Role == domain.ChannelRoleCreator {
|
||||
t.Fatalf("old owner = %+v, want left non-creator", oldOwner)
|
||||
}
|
||||
if _, err := store.JoinChannel(ctx, created.Channel.ID, 1, 1_700_000_113); err != nil {
|
||||
t.Fatalf("old owner rejoin: %v", err)
|
||||
}
|
||||
rejoined, err := store.GetParticipant(ctx, 3, created.Channel.ID, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("get rejoined old owner: %v", err)
|
||||
}
|
||||
if rejoined.Role != domain.ChannelRoleMember || rejoined.Status != domain.ChannelMemberActive || rejoined.Rank != "" {
|
||||
t.Fatalf("rejoined old owner = %+v, want active plain member", rejoined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelAdminAndBanDoNotAdvanceChannelPts(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
|
|
@ -71,7 +205,24 @@ func TestChannelAdminAndBanDoNotAdvanceChannelPts(t *testing.T) {
|
|||
t.Fatalf("edit admin pts = event(%d,%d) channel %d, want unchanged %d", promoted.Event.Pts, promoted.Event.PtsCount, promoted.Channel.Pts, ptsFloor)
|
||||
}
|
||||
|
||||
banned, err := store.EditChannelBanned(ctx, domain.EditChannelBannedRequest{
|
||||
muted, err := store.EditChannelBanned(ctx, domain.EditChannelBannedRequest{
|
||||
UserID: 1,
|
||||
ChannelID: channelID,
|
||||
Participant: domain.Peer{Type: domain.PeerTypeUser, ID: 2},
|
||||
BannedRights: domain.ChannelBannedRights{
|
||||
SendMessages: true,
|
||||
UntilDate: 1_700_001_121,
|
||||
},
|
||||
Date: 1_700_000_122,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("edit banned mute: %v", err)
|
||||
}
|
||||
if muted.Event.Pts != 0 || muted.Event.PtsCount != 0 || muted.Channel.Pts != ptsFloor || muted.ServiceEvent.Pts != 0 {
|
||||
t.Fatalf("mute pts = event(%d,%d) channel %d, want unchanged %d without service message", muted.Event.Pts, muted.Event.PtsCount, muted.Channel.Pts, ptsFloor)
|
||||
}
|
||||
|
||||
kicked, err := store.EditChannelBanned(ctx, domain.EditChannelBannedRequest{
|
||||
UserID: 1,
|
||||
ChannelID: channelID,
|
||||
Participant: domain.Peer{Type: domain.PeerTypeUser, ID: 2},
|
||||
|
|
@ -79,13 +230,21 @@ func TestChannelAdminAndBanDoNotAdvanceChannelPts(t *testing.T) {
|
|||
ViewMessages: true,
|
||||
UntilDate: 1_700_001_121,
|
||||
},
|
||||
Date: 1_700_000_122,
|
||||
Date: 1_700_000_123,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("edit banned: %v", err)
|
||||
t.Fatalf("edit banned kick: %v", err)
|
||||
}
|
||||
if banned.Event.Pts != 0 || banned.Event.PtsCount != 0 || banned.Channel.Pts != ptsFloor {
|
||||
t.Fatalf("edit banned pts = event(%d,%d) channel %d, want unchanged %d", banned.Event.Pts, banned.Event.PtsCount, banned.Channel.Pts, ptsFloor)
|
||||
// megagroup 踢人产生可见 "X removed Y" 服务消息并占 channel pts;
|
||||
// participant update 本身仍不占 pts。
|
||||
if kicked.Event.Pts != 0 || kicked.Event.PtsCount != 0 {
|
||||
t.Fatalf("kick participant event = (%d,%d), must stay transient", kicked.Event.Pts, kicked.Event.PtsCount)
|
||||
}
|
||||
if kicked.ServiceEvent.Pts != ptsFloor+1 || kicked.Message.Action == nil || kicked.Message.Action.Type != domain.ChannelActionChatDelete {
|
||||
t.Fatalf("kick service = event %+v message %+v, want ChatDelete service message at pts %d", kicked.ServiceEvent, kicked.Message, ptsFloor+1)
|
||||
}
|
||||
if kicked.Channel.Pts != ptsFloor+1 {
|
||||
t.Fatalf("kick channel pts = %d, want %d", kicked.Channel.Pts, ptsFloor+1)
|
||||
}
|
||||
|
||||
diff, err := store.ListChannelDifference(ctx, domain.ChannelDifferenceRequest{
|
||||
|
|
@ -97,8 +256,145 @@ func TestChannelAdminAndBanDoNotAdvanceChannelPts(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("list difference: %v", err)
|
||||
}
|
||||
if len(diff.Events) != 0 || diff.Pts != ptsFloor {
|
||||
t.Fatalf("difference after participant state change = %+v, want no durable events at pts %d", diff, ptsFloor)
|
||||
if len(diff.Events) != 1 || diff.Pts != ptsFloor+1 {
|
||||
t.Fatalf("difference after kick = %+v, want only the kick service message at pts %d", diff, ptsFloor+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelStoreGetMessagesNonMemberPublicPreview(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
const owner, outsider int64 = 1, 99
|
||||
|
||||
// 公开广播频道:非成员可读取消息(查看他人资料里的公开「个人频道」时,DrKLO 经
|
||||
// channels.getMessages 拉最新一帖依赖此;否则资料页个人频道整块不显示)。
|
||||
pub, err := store.CreateChannel(ctx, domain.CreateChannelRequest{CreatorUserID: owner, Title: "public", Broadcast: true, Date: 1_700_000_000})
|
||||
if err != nil {
|
||||
t.Fatalf("create public: %v", err)
|
||||
}
|
||||
if _, err := store.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{UserID: owner, ChannelID: pub.Channel.ID, Username: "pub_preview_mem"}); err != nil {
|
||||
t.Fatalf("make public: %v", err)
|
||||
}
|
||||
sent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{UserID: owner, ChannelID: pub.Channel.ID, RandomID: 111, Message: "hello", Date: 1_700_000_001})
|
||||
if err != nil {
|
||||
t.Fatalf("send public: %v", err)
|
||||
}
|
||||
got, err := store.GetChannelMessages(ctx, outsider, pub.Channel.ID, []int{sent.Message.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("non-member getMessages on public channel: %v", err)
|
||||
}
|
||||
if len(got.Messages) != 1 || got.Messages[0].ID != sent.Message.ID {
|
||||
t.Fatalf("non-member should read public channel message, got %+v", got.Messages)
|
||||
}
|
||||
|
||||
// 私有广播频道(无 username):非成员仍被拒(ErrChannelPrivate)。
|
||||
priv, err := store.CreateChannel(ctx, domain.CreateChannelRequest{CreatorUserID: owner, Title: "private", Broadcast: true, Date: 1_700_000_002})
|
||||
if err != nil {
|
||||
t.Fatalf("create private: %v", err)
|
||||
}
|
||||
privSent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{UserID: owner, ChannelID: priv.Channel.ID, RandomID: 222, Message: "secret", Date: 1_700_000_003})
|
||||
if err != nil {
|
||||
t.Fatalf("send private: %v", err)
|
||||
}
|
||||
if _, err := store.GetChannelMessages(ctx, outsider, priv.Channel.ID, []int{privSent.Message.ID}); !errors.Is(err, domain.ErrChannelPrivate) {
|
||||
t.Fatalf("non-member getMessages on private channel = %v, want ErrChannelPrivate", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelStoreStoryMessageForwardsPublicOnlyAndDeleteRollback(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
source := domain.Peer{Type: domain.PeerTypeUser, ID: 42}
|
||||
media := &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindStory,
|
||||
Story: &domain.MessageStory{
|
||||
Peer: source,
|
||||
ID: 7,
|
||||
Story: &domain.Story{
|
||||
Owner: source,
|
||||
ID: 7,
|
||||
},
|
||||
},
|
||||
}
|
||||
publicCreated, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "public story forwards",
|
||||
Broadcast: true,
|
||||
Date: 1_700_000_130,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create public channel: %v", err)
|
||||
}
|
||||
publicChannel, err := store.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
||||
UserID: 1,
|
||||
ChannelID: publicCreated.Channel.ID,
|
||||
Username: "story_forward_memory",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("make public channel: %v", err)
|
||||
}
|
||||
privateCreated, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "private story forwards",
|
||||
Broadcast: true,
|
||||
Date: 1_700_000_131,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create private channel: %v", err)
|
||||
}
|
||||
publicSent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: publicChannel.ID,
|
||||
RandomID: 9141301,
|
||||
Media: media,
|
||||
Date: 1_700_000_132,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send public story message: %v", err)
|
||||
}
|
||||
if _, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: privateCreated.Channel.ID,
|
||||
RandomID: 9141302,
|
||||
Media: media,
|
||||
Date: 1_700_000_133,
|
||||
}); err != nil {
|
||||
t.Fatalf("send private story message: %v", err)
|
||||
}
|
||||
list, err := store.ListStoryMessageForwards(ctx, domain.StoryMessageForwardListRequest{
|
||||
ViewerUserID: 42,
|
||||
Owner: source,
|
||||
StoryID: 7,
|
||||
Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list story message forwards: %v", err)
|
||||
}
|
||||
if list.Count != 1 || len(list.Forwards) != 1 {
|
||||
t.Fatalf("story message forwards = %+v, want one public forward", list)
|
||||
}
|
||||
if got := list.Forwards[0].PublicForward.Message.ChannelID; got != publicChannel.ID {
|
||||
t.Fatalf("forward channel = %d, want public channel %d", got, publicChannel.ID)
|
||||
}
|
||||
if _, err := store.DeleteChannelMessages(ctx, domain.DeleteChannelMessagesRequest{
|
||||
UserID: 1,
|
||||
ChannelID: publicChannel.ID,
|
||||
IDs: []int{publicSent.Message.ID},
|
||||
Date: 1_700_000_134,
|
||||
}); err != nil {
|
||||
t.Fatalf("delete public story message: %v", err)
|
||||
}
|
||||
empty, err := store.ListStoryMessageForwards(ctx, domain.StoryMessageForwardListRequest{
|
||||
ViewerUserID: 42,
|
||||
Owner: source,
|
||||
StoryID: 7,
|
||||
Limit: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list story message forwards after delete: %v", err)
|
||||
}
|
||||
if empty.Count != 0 || len(empty.Forwards) != 0 {
|
||||
t.Fatalf("story message forwards after delete = %+v, want empty", empty)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -493,8 +789,18 @@ func TestChannelDeleteHistoryCapsHugeMaxID(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("delete second batch: %v", err)
|
||||
}
|
||||
if second.Offset != 0 || len(second.DeletedIDs) != 3 || second.Event.PtsCount != 3 {
|
||||
t.Fatalf("second batch = %+v, want final bounded page", second)
|
||||
if second.Offset != 0 || len(second.DeletedIDs) != 2 || second.Event.PtsCount != 2 {
|
||||
t.Fatalf("second batch = %+v, want final bounded page keeping create service message", second)
|
||||
}
|
||||
if second.Channel.TopMessageID != created.Message.ID {
|
||||
t.Fatalf("top after full clear = %d, want create service message %d", second.Channel.TopMessageID, created.Message.ID)
|
||||
}
|
||||
history, err := store.ListChannelHistory(ctx, 1, domain.ChannelHistoryFilter{ChannelID: created.Channel.ID, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("list history after full clear: %v", err)
|
||||
}
|
||||
if len(history.Messages) != 1 || history.Messages[0].ID != created.Message.ID || history.Messages[0].Action == nil {
|
||||
t.Fatalf("history after full clear = %+v, want only create service message", history.Messages)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -678,6 +984,230 @@ func TestChannelUnreadExcludesOwnOutgoing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestChannelMessageReplyMarkupSurvivesReadPaths(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "channel reply markup",
|
||||
Megagroup: true,
|
||||
MemberUserIDs: []int64{2},
|
||||
Date: 1_700_000_380,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
markup := &domain.MessageReplyMarkup{Inline: [][]domain.MarkupButton{{
|
||||
{Type: domain.MarkupButtonCallback, Text: "Open", Data: []byte{0x00, 0xff, 0x42}},
|
||||
}}}
|
||||
assertMarkup := func(name string, got *domain.MessageReplyMarkup) {
|
||||
t.Helper()
|
||||
if got == nil || len(got.Inline) != 1 || len(got.Inline[0]) != 1 {
|
||||
t.Fatalf("%s markup = %+v, want one callback button", name, got)
|
||||
}
|
||||
btn := got.Inline[0][0]
|
||||
if btn.Type != domain.MarkupButtonCallback || btn.Text != "Open" || !bytes.Equal(btn.Data, []byte{0x00, 0xff, 0x42}) {
|
||||
t.Fatalf("%s button = %+v, want callback Open bytes", name, btn)
|
||||
}
|
||||
}
|
||||
sent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
RandomID: 38_001,
|
||||
Message: "via inline keyboard",
|
||||
ViaBotID: 99,
|
||||
ReplyMarkup: markup,
|
||||
Date: 1_700_000_381,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send channel message: %v", err)
|
||||
}
|
||||
markup.Inline[0][0].Text = "Changed"
|
||||
markup.Inline[0][0].Data[0] = 0x7f
|
||||
assertMarkup("send result", sent.Message.ReplyMarkup)
|
||||
assertMarkup("send event", sent.Event.Message.ReplyMarkup)
|
||||
|
||||
duplicate, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
RandomID: 38_001,
|
||||
Message: "duplicate must not replace",
|
||||
Date: 1_700_000_382,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("duplicate send: %v", err)
|
||||
}
|
||||
if !duplicate.Duplicate || duplicate.Message.ViaBotID != 99 {
|
||||
t.Fatalf("duplicate = %+v, want original via bot", duplicate.Message)
|
||||
}
|
||||
assertMarkup("duplicate message", duplicate.Message.ReplyMarkup)
|
||||
assertMarkup("duplicate event", duplicate.Event.Message.ReplyMarkup)
|
||||
|
||||
history, err := store.ListChannelHistory(ctx, 1, domain.ChannelHistoryFilter{ChannelID: created.Channel.ID, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("list history: %v", err)
|
||||
}
|
||||
if len(history.Messages) == 0 || history.Messages[0].ViaBotID != 99 {
|
||||
t.Fatalf("history messages = %+v, want via bot 99", history.Messages)
|
||||
}
|
||||
assertMarkup("history message", history.Messages[0].ReplyMarkup)
|
||||
|
||||
byID, err := store.GetChannelMessages(ctx, 1, created.Channel.ID, []int{sent.Message.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("get channel messages: %v", err)
|
||||
}
|
||||
if len(byID.Messages) != 1 || byID.Messages[0].ViaBotID != 99 {
|
||||
t.Fatalf("get messages = %+v, want via bot 99", byID.Messages)
|
||||
}
|
||||
assertMarkup("get messages", byID.Messages[0].ReplyMarkup)
|
||||
|
||||
diff, err := store.ListChannelDifference(ctx, domain.ChannelDifferenceRequest{UserID: 1, ChannelID: created.Channel.ID, Pts: created.Channel.Pts, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("list difference: %v", err)
|
||||
}
|
||||
if len(diff.NewMessages) != 1 || diff.NewMessages[0].ViaBotID != 99 {
|
||||
t.Fatalf("difference messages = %+v, want via bot 99", diff.NewMessages)
|
||||
}
|
||||
assertMarkup("difference message", diff.NewMessages[0].ReplyMarkup)
|
||||
|
||||
dialogs, err := store.ListChannelDialogs(ctx, 1, domain.DialogFilter{Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("list dialogs: %v", err)
|
||||
}
|
||||
if len(dialogs.Messages) == 0 || dialogs.Messages[0].ViaBotID != 99 {
|
||||
t.Fatalf("dialog messages = %+v, want via bot 99", dialogs.Messages)
|
||||
}
|
||||
assertMarkup("dialog top message", dialogs.Messages[0].ReplyMarkup)
|
||||
}
|
||||
|
||||
func TestChannelMessageViaBotEditUpdatesReplyMarkup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "channel via bot edit",
|
||||
Megagroup: true,
|
||||
MemberUserIDs: []int64{2},
|
||||
Date: 1_700_000_390,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
sent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
RandomID: 39_001,
|
||||
Message: "before",
|
||||
ViaBotID: 99,
|
||||
Date: 1_700_000_391,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send channel message: %v", err)
|
||||
}
|
||||
markup := &domain.MessageReplyMarkup{Inline: [][]domain.MarkupButton{{
|
||||
{Type: domain.MarkupButtonCallback, Text: "Done", Data: []byte("v2")},
|
||||
}}}
|
||||
if _, err := store.EditChannelMessage(ctx, domain.EditChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
ID: sent.Message.ID,
|
||||
Message: "wrong",
|
||||
ViaBotEditBotID: 100,
|
||||
EditDate: 1_700_000_392,
|
||||
SetReplyMarkup: true,
|
||||
ReplyMarkup: markup,
|
||||
}); err != domain.ErrMessageAuthorRequired {
|
||||
t.Fatalf("wrong via bot edit err = %v, want ErrMessageAuthorRequired", err)
|
||||
}
|
||||
edited, err := store.EditChannelMessage(ctx, domain.EditChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
ID: sent.Message.ID,
|
||||
Message: "after",
|
||||
ViaBotEditBotID: 99,
|
||||
EditDate: 1_700_000_393,
|
||||
SetReplyMarkup: true,
|
||||
ReplyMarkup: markup,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("via bot edit: %v", err)
|
||||
}
|
||||
if edited.Event.Type != domain.ChannelUpdateEditMessage || edited.Message.Body != "after" || edited.Message.ViaBotID != 99 {
|
||||
t.Fatalf("edited = %+v event=%+v, want edit via bot", edited.Message, edited.Event)
|
||||
}
|
||||
if edited.Message.ReplyMarkup == nil || edited.Message.ReplyMarkup.Inline[0][0].Text != "Done" || !bytes.Equal(edited.Message.ReplyMarkup.Inline[0][0].Data, []byte("v2")) {
|
||||
t.Fatalf("edited markup = %+v, want Done/v2", edited.Message.ReplyMarkup)
|
||||
}
|
||||
diff, err := store.ListChannelDifference(ctx, domain.ChannelDifferenceRequest{UserID: 1, ChannelID: created.Channel.ID, Pts: sent.Event.Pts, Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("list edit difference: %v", err)
|
||||
}
|
||||
if len(diff.OtherUpdates) != 1 || diff.OtherUpdates[0].Message.Body != "after" {
|
||||
t.Fatalf("edit difference = %+v, want one edited message", diff.OtherUpdates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBroadcastChannelReactionsAreAnonymousAndSkipUnread(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
created, err := store.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: 1,
|
||||
Title: "broadcast reaction",
|
||||
Broadcast: true,
|
||||
MemberUserIDs: []int64{2},
|
||||
Date: 1_700_000_500,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
sent, err := store.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||||
UserID: 1,
|
||||
ChannelID: created.Channel.ID,
|
||||
RandomID: 50_001,
|
||||
Message: "broadcast post",
|
||||
Date: 1_700_000_501,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("send channel message: %v", err)
|
||||
}
|
||||
res, err := store.SetChannelMessageReactions(ctx, domain.SetChannelMessageReactionsRequest{
|
||||
UserID: 2,
|
||||
ChannelID: created.Channel.ID,
|
||||
MessageID: sent.Message.ID,
|
||||
Reactions: []domain.MessageReaction{{
|
||||
Type: domain.MessageReactionEmoji,
|
||||
Emoticon: "\U0001f44d",
|
||||
}},
|
||||
Date: 1_700_000_502,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("set channel reaction: %v", err)
|
||||
}
|
||||
if len(res.Reactions.Results) != 1 || res.Reactions.Results[0].Count != 1 {
|
||||
t.Fatalf("broadcast reaction results = %+v, want count-only aggregate", res.Reactions.Results)
|
||||
}
|
||||
if len(res.Reactions.Recent) != 0 {
|
||||
t.Fatalf("broadcast recent reactors = %+v, want anonymous (empty)", res.Reactions.Recent)
|
||||
}
|
||||
if res.Reactions.CanSeeList {
|
||||
t.Fatalf("broadcast can_see_list = true, want false")
|
||||
}
|
||||
for _, rows := range store.reactions[created.Channel.ID][sent.Message.ID] {
|
||||
for _, row := range rows {
|
||||
if row.Unread {
|
||||
t.Fatalf("broadcast reaction row = %+v, want unread bookkeeping skipped", row)
|
||||
}
|
||||
}
|
||||
}
|
||||
dialogs, err := store.GetChannelDialogs(ctx, 1, []int64{created.Channel.ID})
|
||||
if err != nil {
|
||||
t.Fatalf("get owner channel dialogs: %v", err)
|
||||
}
|
||||
if len(dialogs.Dialogs) != 1 || dialogs.Dialogs[0].UnreadReactions != 0 {
|
||||
t.Fatalf("owner dialogs = %+v, want no unread reaction badge on broadcast", dialogs.Dialogs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelReadMessageContentsClearsVisibleUnreadReactions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := NewChannelStore()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue