218 lines
7.1 KiB
Go
218 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/tinode/chat/server/store"
|
|
"github.com/tinode/chat/server/store/mock_store"
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
func TestGroupMemberProfileEventIsVisibleToAllMembers(t *testing.T) {
|
|
const event = "group.member.profile.updated"
|
|
if isAdminOnlyIMGroupEvent(event) {
|
|
t.Fatal("group nickname update must reach ordinary members")
|
|
}
|
|
payload := enrichIMGroupMemberEventPayload(event, map[string]any{"user_id": "usrMember"}).(map[string]any)
|
|
if payload["member_sync"] != "single" || payload["visibility"] != "all_members" {
|
|
t.Fatalf("unexpected group profile event payload: %#v", payload)
|
|
}
|
|
}
|
|
|
|
func TestPublishIMGroupRealtimeEventRestrictsMemberRemoval(t *testing.T) {
|
|
oldGroupBusiness := store.GroupBusiness
|
|
oldSessions := globals.sessionStore
|
|
oldHub := globals.hub
|
|
admin := types.Uid(1)
|
|
affected := types.Uid(2)
|
|
regular := types.Uid(3)
|
|
actor := types.Uid(4)
|
|
store.GroupBusiness = &testGroupBusinessStore{listMembers: []types.GroupMember{
|
|
{UserID: admin, Role: types.GroupMemberRoleAdmin, MemberStatus: types.GroupMemberStatusActive},
|
|
}}
|
|
globals.sessionStore = NewSessionStore(time.Minute)
|
|
globals.hub = &Hub{routeSrv: make(chan *ServerComMessage, 2)}
|
|
t.Cleanup(func() {
|
|
store.GroupBusiness = oldGroupBusiness
|
|
globals.sessionStore = oldSessions
|
|
globals.hub = oldHub
|
|
})
|
|
|
|
sessions := map[types.Uid]*Session{}
|
|
for _, uid := range []types.Uid{admin, affected, regular} {
|
|
sess := &Session{
|
|
sid: uid.UserId(), uid: uid, tenantID: 7, proto: WEBSOCK,
|
|
send: make(chan any, 2), subs: make(map[string]*Subscription),
|
|
}
|
|
globals.sessionStore.sessCache[sess.sid] = sess
|
|
sessions[uid] = sess
|
|
}
|
|
|
|
publishIMGroupRealtimeEvent(7, "grpRestricted", actor, "group.member.left",
|
|
map[string]any{"user_id": affected.UserId()})
|
|
|
|
for _, uid := range []types.Uid{admin, affected} {
|
|
select {
|
|
case raw := <-sessions[uid].send:
|
|
msg, ok := raw.(*ServerComMessage)
|
|
if !ok || msg.Info == nil || msg.Info.Event != "group.member.left" || msg.Info.Topic != "grpRestricted" {
|
|
t.Fatalf("unexpected event for %s: %#v", uid.UserId(), raw)
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(msg.Info.Payload, &payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if payload["member_sync"] != "remove" || payload["visibility"] != "admins_and_affected" {
|
|
t.Fatalf("unexpected payload for %s: %#v", uid.UserId(), payload)
|
|
}
|
|
default:
|
|
t.Fatalf("expected event for %s", uid.UserId())
|
|
}
|
|
}
|
|
select {
|
|
case raw := <-sessions[regular].send:
|
|
t.Fatalf("regular member received restricted event: %#v", raw)
|
|
default:
|
|
}
|
|
if len(globals.hub.routeSrv) != 0 {
|
|
t.Fatalf("restricted event was broadcast to topic")
|
|
}
|
|
}
|
|
|
|
func TestPublishIMGroupRealtimeEventBroadcastsMemberJoin(t *testing.T) {
|
|
oldHub := globals.hub
|
|
globals.hub = &Hub{routeSrv: make(chan *ServerComMessage, 1)}
|
|
t.Cleanup(func() { globals.hub = oldHub })
|
|
|
|
publishIMGroupRealtimeEvent(7, "grpPublic", types.Uid(1), "group.member.joined",
|
|
map[string]any{"user_id": types.Uid(2).UserId()})
|
|
|
|
select {
|
|
case msg := <-globals.hub.routeSrv:
|
|
if msg.Info == nil || msg.Info.Event != "group.member.joined" {
|
|
t.Fatalf("unexpected joined event: %#v", msg)
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(msg.Info.Payload, &payload); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if payload["member_sync"] != "single" || payload["visibility"] != "all_members" {
|
|
t.Fatalf("unexpected joined payload: %#v", payload)
|
|
}
|
|
default:
|
|
t.Fatal("joined event was not broadcast")
|
|
}
|
|
}
|
|
|
|
func TestSendPushForStoredGroupMessageIncludesEverySubscription(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
oldTopics := store.Topics
|
|
oldUsersUpdate := globals.usersUpdate
|
|
oldCluster := globals.cluster
|
|
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
|
|
store.Topics = topics
|
|
globals.usersUpdate = make(chan *UserCacheReq, 1)
|
|
globals.cluster = nil
|
|
t.Cleanup(func() {
|
|
store.Topics = oldTopics
|
|
globals.usersUpdate = oldUsersUpdate
|
|
globals.cluster = oldCluster
|
|
})
|
|
|
|
subs := make([]types.Subscription, 0, 1500)
|
|
for i := 1; i <= 1500; i++ {
|
|
sub := types.Subscription{
|
|
User: types.Uid(i).String(), Topic: "grpLarge", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull,
|
|
}
|
|
if i == 1500 {
|
|
sub.Private = map[string]any{"mute_notifications": true}
|
|
}
|
|
subs = append(subs, sub)
|
|
}
|
|
topics.EXPECT().GetSubs(types.TenantID(7), "grpLarge", nil).Return(subs, nil)
|
|
msg := &types.Message{
|
|
ObjHeader: types.ObjHeader{CreatedAt: time.Now().UTC()}, TenantID: 7,
|
|
Topic: "grpLarge", SeqId: 99, Content: map[string]any{"text": "成员加入了群聊"},
|
|
}
|
|
|
|
sendPushForStoredGroupMessage(7, msg)
|
|
|
|
select {
|
|
case req := <-globals.usersUpdate:
|
|
if req.PushRcpt == nil || len(req.PushRcpt.To) != len(subs) {
|
|
t.Fatalf("push recipients: got %d want %d", len(req.PushRcpt.To), len(subs))
|
|
}
|
|
for _, recipient := range req.PushRcpt.To {
|
|
if !recipient.ShouldIncrementUnreadCountInCache {
|
|
t.Fatal("system group message did not increment per-user unread")
|
|
}
|
|
}
|
|
if !req.PushRcpt.To[types.Uid(1500)].Silent {
|
|
t.Fatal("muted group recipient must receive a silent push")
|
|
}
|
|
default:
|
|
t.Fatal("group system message did not enqueue push")
|
|
}
|
|
}
|
|
|
|
func TestSendPushForHTTPForwardMessageIncrementsRecipientUnreadOnly(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
oldTopics := store.Topics
|
|
oldUsersUpdate := globals.usersUpdate
|
|
oldCluster := globals.cluster
|
|
oldSessions := globals.sessionStore
|
|
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
|
|
store.Topics = topics
|
|
globals.usersUpdate = make(chan *UserCacheReq, 1)
|
|
globals.cluster = nil
|
|
globals.sessionStore = NewSessionStore(time.Minute)
|
|
t.Cleanup(func() {
|
|
store.Topics = oldTopics
|
|
globals.usersUpdate = oldUsersUpdate
|
|
globals.cluster = oldCluster
|
|
globals.sessionStore = oldSessions
|
|
})
|
|
|
|
sender := types.Uid(42)
|
|
receiver := types.Uid(99)
|
|
topic := sender.P2PName(receiver)
|
|
topics.EXPECT().GetSubs(types.TenantID(7), topic, nil).Return([]types.Subscription{
|
|
{User: sender.String(), Topic: topic, ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull},
|
|
{User: receiver.String(), Topic: topic, ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull},
|
|
}, nil)
|
|
msg := &types.Message{
|
|
ObjHeader: types.ObjHeader{CreatedAt: time.Now().UTC()},
|
|
TenantID: 7,
|
|
Topic: topic,
|
|
SeqId: 12,
|
|
From: sender.String(),
|
|
Head: types.KVMap{"x-im-type": "text"},
|
|
Content: map[string]any{"text": "forwarded"},
|
|
}
|
|
|
|
sendPushForHTTPForwardMessage(7, msg, true, msg.CreatedAt)
|
|
|
|
select {
|
|
case req := <-globals.usersUpdate:
|
|
if req.PushRcpt == nil || len(req.PushRcpt.To) != 2 {
|
|
t.Fatalf("push recipients: %#v", req)
|
|
}
|
|
if req.PushRcpt.To[sender].ShouldIncrementUnreadCountInCache {
|
|
t.Fatal("sender should not get unread increment for a sender-read forward")
|
|
}
|
|
if !req.PushRcpt.To[receiver].ShouldIncrementUnreadCountInCache {
|
|
t.Fatal("receiver should get unread increment for forwarded message")
|
|
}
|
|
if req.PushRcpt.Payload.Topic != topic || req.PushRcpt.Payload.SeqId != 12 {
|
|
t.Fatalf("unexpected push payload: %#v", req.PushRcpt.Payload)
|
|
}
|
|
default:
|
|
t.Fatal("http forward message did not enqueue push")
|
|
}
|
|
}
|