fix im p0 backend issues
This commit is contained in:
+137
-4
@@ -109,10 +109,18 @@ func normalizeCallInvite(head map[string]any, content any) (map[string]any, any,
|
||||
if state != "started" {
|
||||
return nil, nil, types.ErrMalformed
|
||||
}
|
||||
call, ok := content.(map[string]any)
|
||||
if !ok {
|
||||
if typed, typedOK := content.(types.KVMap); typedOK {
|
||||
call = map[string]any(typed)
|
||||
call := callInvitePayloadMap(content)
|
||||
if len(call) == 0 || !hasCallIdentity(call) {
|
||||
headCall := callInvitePayloadMap(head)
|
||||
if len(call) == 0 {
|
||||
call = headCall
|
||||
} else {
|
||||
for key, value := range headCall {
|
||||
if _, exists := call[key]; !exists {
|
||||
call[key] = value
|
||||
}
|
||||
}
|
||||
normalizeCallPayloadAliases(call)
|
||||
}
|
||||
}
|
||||
if len(call) == 0 {
|
||||
@@ -123,6 +131,16 @@ func normalizeCallInvite(head map[string]any, content any) (map[string]any, any,
|
||||
if callType == "" {
|
||||
callType, _ = call["media_type"].(string)
|
||||
}
|
||||
if callType == "" {
|
||||
callType, _ = call["type"].(string)
|
||||
}
|
||||
if callType == "" {
|
||||
if aonly, _ := call["aonly"].(bool); aonly {
|
||||
callType = constCallTypeAudio
|
||||
} else {
|
||||
callType = constCallTypeVideo
|
||||
}
|
||||
}
|
||||
callType = strings.ToLower(strings.TrimSpace(callType))
|
||||
if strings.TrimSpace(callID) == "" || (callType != constCallTypeAudio && callType != constCallTypeVideo) {
|
||||
return nil, nil, types.ErrMalformed
|
||||
@@ -147,6 +165,121 @@ func normalizeCallInvite(head map[string]any, content any) (map[string]any, any,
|
||||
return normalizedHead, normalizedContent, nil
|
||||
}
|
||||
|
||||
func callInvitePayloadMap(content any) map[string]any {
|
||||
call := anyMap(content)
|
||||
if len(call) == 0 {
|
||||
return nil
|
||||
}
|
||||
normalizeCallPayloadAliases(call)
|
||||
if hasCallIdentity(call) {
|
||||
return call
|
||||
}
|
||||
if draftyCall := draftyVideoCallPayload(call); len(draftyCall) > 0 {
|
||||
for key, value := range call {
|
||||
if _, exists := draftyCall[key]; !exists && key != "fmt" && key != "ent" && key != "txt" {
|
||||
draftyCall[key] = value
|
||||
}
|
||||
}
|
||||
normalizeCallPayloadAliases(draftyCall)
|
||||
return draftyCall
|
||||
}
|
||||
return call
|
||||
}
|
||||
|
||||
func anyMap(value any) map[string]any {
|
||||
if typed, ok := value.(map[string]any); ok {
|
||||
return typed
|
||||
}
|
||||
if typed, ok := value.(types.KVMap); ok {
|
||||
return map[string]any(typed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeCallPayloadAliases(call map[string]any) {
|
||||
if call == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := call["callId"]; !ok {
|
||||
for _, key := range []string{"call_id", "id"} {
|
||||
if value, exists := call[key]; exists {
|
||||
call["callId"] = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := call["callType"]; !ok {
|
||||
for _, key := range []string{"call_type", "mediaType", "media_type"} {
|
||||
if value, exists := call[key]; exists {
|
||||
call["callType"] = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasCallIdentity(call map[string]any) bool {
|
||||
if call == nil {
|
||||
return false
|
||||
}
|
||||
callID, _ := call["callId"].(string)
|
||||
if strings.TrimSpace(callID) == "" {
|
||||
return false
|
||||
}
|
||||
callType, _ := call["callType"].(string)
|
||||
if strings.TrimSpace(callType) != "" {
|
||||
return true
|
||||
}
|
||||
if mediaType, _ := call["media_type"].(string); strings.TrimSpace(mediaType) != "" {
|
||||
return true
|
||||
}
|
||||
if _, ok := call["aonly"].(bool); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func draftyVideoCallPayload(doc map[string]any) map[string]any {
|
||||
for _, raw := range anySlice(doc["ent"]) {
|
||||
entity := anyMap(raw)
|
||||
if entityType, _ := entity["tp"].(string); entityType != "VC" {
|
||||
continue
|
||||
}
|
||||
if data := anyMap(entity["data"]); len(data) > 0 {
|
||||
return data
|
||||
}
|
||||
return map[string]any{}
|
||||
}
|
||||
for _, raw := range anySlice(doc["fmt"]) {
|
||||
format := anyMap(raw)
|
||||
if formatType, _ := format["tp"].(string); formatType == "VC" {
|
||||
return map[string]any{}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func anySlice(value any) []any {
|
||||
switch typed := value.(type) {
|
||||
case []any:
|
||||
return typed
|
||||
case []map[string]any:
|
||||
items := make([]any, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
case []types.KVMap:
|
||||
items := make([]any, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func callPayloadString(payload any, key string) string {
|
||||
data, ok := payload.(map[string]any)
|
||||
if !ok {
|
||||
|
||||
@@ -445,7 +445,8 @@ func (a *adapter) TenantGroupInvitationAccept(id t.TenantID, invitationID string
|
||||
return &inv, nil
|
||||
}
|
||||
var deletedAt sql.NullTime
|
||||
err = tx.QueryRowxContext(ctx, "SELECT DeletedAt FROM subscriptions WHERE Topic=? AND User=?", inv.Topic, uid.String()).Scan(&deletedAt)
|
||||
err = tx.QueryRowxContext(ctx, "SELECT deletedat FROM im_subscriptions WHERE topic=? AND userid=?",
|
||||
inv.Topic, decodeUID(uid)).Scan(&deletedAt)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+13
-4
@@ -3570,6 +3570,7 @@ func serveIMFavoriteBatchCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
failed = append(failed, map[string]any{"topic": sourceTopic, "seq": src.Seq, "reason": relationErrorReason(err)})
|
||||
continue
|
||||
}
|
||||
idempotencyKey := favoriteItemIdempotencyKey(req.Header.Get("Idempotency-Key"), sourceTopic, src.Seq)
|
||||
fav := &types.FavoriteItem{
|
||||
TenantID: principal.TenantID,
|
||||
FavoriteID: "fav_" + store.Store.GetUidString(),
|
||||
@@ -3580,7 +3581,7 @@ func serveIMFavoriteBatchCreate(wrt http.ResponseWriter, req *http.Request) {
|
||||
Snapshot: favoriteSnapshot(msg),
|
||||
AttachmentRefs: favoriteAttachmentRefs(msg),
|
||||
Availability: messageAvailability(*msg),
|
||||
IdempotencyKey: strings.TrimSpace(req.Header.Get("Idempotency-Key")),
|
||||
IdempotencyKey: idempotencyKey,
|
||||
CollectedAt: now,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -4021,13 +4022,13 @@ func serveIMMessageContext(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMError(wrt, req, http.StatusNotFound, 30004, imReasonMalformedRequest, "message context route not found", nil)
|
||||
return
|
||||
}
|
||||
principal, _, ok := authenticateTopicReader(wrt, req, topic)
|
||||
principal, normalizedTopic, ok := authenticateTopicReader(wrt, req, topic)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
before := parseIMLimit(req.URL.Query().Get("before"), 20, 100)
|
||||
after := parseIMLimit(req.URL.Query().Get("after"), 20, 100)
|
||||
msgs, err := store.Messages.GetAll(principal.TenantID, topic, principal.Uid, &types.QueryOpt{Since: seq - int64(before), Before: seq + int64(after) + 1, Limit: before + after + 1})
|
||||
msgs, err := store.Messages.GetAll(principal.TenantID, normalizedTopic, principal.Uid, &types.QueryOpt{Since: seq - int64(before), Before: seq + int64(after) + 1, Limit: before + after + 1})
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
@@ -4036,7 +4037,7 @@ func serveIMMessageContext(wrt http.ResponseWriter, req *http.Request) {
|
||||
for _, msg := range msgs {
|
||||
items = append(items, messageContextPayload(msg))
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"topic": topic, "anchor_seq": seq, "items": items})
|
||||
writeIMSuccess(wrt, req, map[string]any{"topic": normalizedTopic, "anchor_seq": seq, "items": items})
|
||||
}
|
||||
|
||||
func serveIMTopicMedia(wrt http.ResponseWriter, req *http.Request) {
|
||||
@@ -9353,6 +9354,14 @@ func normalizeIMFavoriteSourceTopic(viewer types.Uid, topic string) (string, boo
|
||||
return normalizeIMUserVisibleTopic(viewer, topic)
|
||||
}
|
||||
|
||||
func favoriteItemIdempotencyKey(requestKey, topic string, seq int64) string {
|
||||
requestKey = strings.TrimSpace(requestKey)
|
||||
if requestKey == "" {
|
||||
return ""
|
||||
}
|
||||
return stableIMEventID("favorite", requestKey, strings.TrimSpace(topic), seq)
|
||||
}
|
||||
|
||||
func normalizeIMUserVisibleTopic(viewer types.Uid, topic string) (string, bool) {
|
||||
topic = strings.TrimSpace(topic)
|
||||
if topic == "" {
|
||||
|
||||
+28
-4
@@ -4870,6 +4870,17 @@ func TestServeIMFavoriteBatchCreateCountsExisting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFavoriteItemIdempotencyKeyIncludesMessageIdentity(t *testing.T) {
|
||||
first := favoriteItemIdempotencyKey("same-key", "grpAlpha", 1)
|
||||
second := favoriteItemIdempotencyKey("same-key", "grpAlpha", 2)
|
||||
if first == "" || second == "" || first == second {
|
||||
t.Fatalf("favorite item idempotency key must be stable per source message: %q %q", first, second)
|
||||
}
|
||||
if favoriteItemIdempotencyKey("", "grpAlpha", 1) != "" {
|
||||
t.Fatal("empty request idempotency key should stay empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMFavoriteBatchCreateRejectsUnreadableSource(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -5694,10 +5705,10 @@ func TestServeIMTopicReadActionsNormalizeP2PUserTopic(t *testing.T) {
|
||||
oldStore := store.Store
|
||||
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
|
||||
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
|
||||
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth).Times(2)
|
||||
tokenAuth.EXPECT().IsInitialized().Return(true).Times(2)
|
||||
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth).Times(3)
|
||||
tokenAuth.EXPECT().IsInitialized().Return(true).Times(3)
|
||||
tokenAuth.EXPECT().Authenticate(auth.AuthContext{}, []byte("token-1")).
|
||||
Return(&auth.Rec{TenantID: 7, Uid: actor, AuthLevel: auth.LevelAuth}, nil, nil).Times(2)
|
||||
Return(&auth.Rec{TenantID: 7, Uid: actor, AuthLevel: auth.LevelAuth}, nil, nil).Times(3)
|
||||
store.Store = mockStore
|
||||
oldSubs := store.Subs
|
||||
oldMessages := store.Messages
|
||||
@@ -5717,10 +5728,11 @@ func TestServeIMTopicReadActionsNormalizeP2PUserTopic(t *testing.T) {
|
||||
Topic: normalizedTopic,
|
||||
ModeWant: types.ModeCPublic,
|
||||
ModeGiven: types.ModeCPublic,
|
||||
}, nil).Times(2)
|
||||
}, nil).Times(3)
|
||||
textMsg := types.Message{TenantID: 7, SeqId: 9, Topic: normalizedTopic, From: actor.String(), Head: types.KVMap{"type": "text"}, Content: map[string]any{"text": "好的"}}
|
||||
imageMsg := types.Message{TenantID: 7, SeqId: 10, Topic: normalizedTopic, From: actor.String(), Head: types.KVMap{"type": "image"}, Content: map[string]any{"file_id": "file-1", "mime": "image/png"}}
|
||||
messages.EXPECT().Search(types.TenantID(7), normalizedTopic, actor, "好的", "text", "", 50).Return([]types.Message{textMsg}, "", nil)
|
||||
messages.EXPECT().GetAll(types.TenantID(7), normalizedTopic, actor, gomock.Any()).Return([]types.Message{textMsg}, nil)
|
||||
messages.EXPECT().Search(types.TenantID(7), normalizedTopic, actor, "", "image", "", 50).Return([]types.Message{imageMsg}, "", nil)
|
||||
|
||||
searchReq := httptest.NewRequest(http.MethodGet, "/v1/im/topics/"+peer.UserId()+"/messages/search?q=%E5%A5%BD%E7%9A%84&type=text", nil)
|
||||
@@ -5731,6 +5743,18 @@ func TestServeIMTopicReadActionsNormalizeP2PUserTopic(t *testing.T) {
|
||||
t.Fatalf("search status: got %d body=%s", searchRec.Code, searchRec.Body.String())
|
||||
}
|
||||
|
||||
contextReq := httptest.NewRequest(http.MethodGet, "/v1/im/topics/"+peer.UserId()+"/messages/9/context", nil)
|
||||
contextReq.Header.Set("Authorization", "Bearer token-1")
|
||||
contextRec := httptest.NewRecorder()
|
||||
serveIMTopicMessageAction(contextRec, contextReq)
|
||||
if contextRec.Code != http.StatusOK {
|
||||
t.Fatalf("context status: got %d body=%s", contextRec.Code, contextRec.Body.String())
|
||||
}
|
||||
contextData := decodeIMHTTPResponse(t, contextRec).Data.(map[string]any)
|
||||
if contextData["topic"] != normalizedTopic {
|
||||
t.Fatalf("context topic was not normalized: %#v", contextData)
|
||||
}
|
||||
|
||||
mediaReq := httptest.NewRequest(http.MethodGet, "/v1/im/topics/"+peer.UserId()+"/media?type=image", nil)
|
||||
mediaReq.Header.Set("Authorization", "Bearer token-1")
|
||||
mediaRec := httptest.NewRecorder()
|
||||
|
||||
@@ -1286,6 +1286,10 @@ func (s *Session) onLogin(msgID string, timestamp time.Time, rec *auth.Rec, miss
|
||||
|
||||
// Record deviceId used in this session
|
||||
if s.deviceID != "" {
|
||||
isNewDevice, deviceErr := isNewLoginDevice(s.tenantID, rec.Uid, s.deviceID)
|
||||
if deviceErr != nil {
|
||||
logs.Warn.Println("s.onLogin: failed to check known device", rec.Uid, deviceErr, s.sid)
|
||||
}
|
||||
if err := store.Devices.Update(s.tenantID, rec.Uid, "", &types.DeviceDef{
|
||||
TenantID: s.tenantID,
|
||||
DeviceId: s.deviceID,
|
||||
@@ -1294,6 +1298,8 @@ func (s *Session) onLogin(msgID string, timestamp time.Time, rec *auth.Rec, miss
|
||||
Lang: s.lang,
|
||||
}); err != nil {
|
||||
logs.Warn.Println("failed to update device record", err)
|
||||
} else if isNewDevice {
|
||||
createLoginDeviceSecurityNotice(s.tenantID, rec.Uid, s.deviceID, s.platf, s.userAgent, timestamp)
|
||||
}
|
||||
}
|
||||
if !s.uid.IsZero() {
|
||||
@@ -1310,6 +1316,55 @@ func (s *Session) onLogin(msgID string, timestamp time.Time, rec *auth.Rec, miss
|
||||
return reply
|
||||
}
|
||||
|
||||
func isNewLoginDevice(tenantID types.TenantID, uid types.Uid, deviceID string) (bool, error) {
|
||||
if store.Devices == nil || strings.TrimSpace(deviceID) == "" || uid.IsZero() {
|
||||
return false, nil
|
||||
}
|
||||
devices, _, err := store.Devices.GetAll(tenantID, uid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, device := range devices[uid] {
|
||||
if device.DeviceId == deviceID {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func createLoginDeviceSecurityNotice(tenantID types.TenantID, uid types.Uid, deviceID, platform, userAgent string, at time.Time) {
|
||||
if store.Security == nil || uid.IsZero() || strings.TrimSpace(deviceID) == "" {
|
||||
return
|
||||
}
|
||||
eventID := stableIMEventID("security.login_device", uid.UserId(), deviceID)
|
||||
if err := createIMSystemNotification(tenantID, uid, eventID, "security.login_device",
|
||||
"新设备登录提醒", loginDeviceSecurityNoticeSummary(platform, userAgent),
|
||||
map[string]any{
|
||||
"scene": "login_device",
|
||||
"device_id": deviceID,
|
||||
"platform": strings.TrimSpace(platform),
|
||||
"user_agent": strings.TrimSpace(userAgent),
|
||||
"login_at": at.UTC().Format(time.RFC3339Nano),
|
||||
}); err != nil {
|
||||
logs.Warn.Println("s.onLogin: failed to create login device notification", uid, err)
|
||||
}
|
||||
}
|
||||
|
||||
func loginDeviceSecurityNoticeSummary(platform, userAgent string) string {
|
||||
platform = strings.TrimSpace(platform)
|
||||
if platform == "" {
|
||||
platform = "未知设备"
|
||||
}
|
||||
userAgent = strings.TrimSpace(userAgent)
|
||||
if userAgent == "" {
|
||||
return platform + " 登录了你的账号"
|
||||
}
|
||||
if len([]rune(userAgent)) > 80 {
|
||||
userAgent = string([]rune(userAgent)[:80])
|
||||
}
|
||||
return platform + " 登录了你的账号:" + userAgent
|
||||
}
|
||||
|
||||
func currentNodeID() string {
|
||||
if globals.cluster != nil && globals.cluster.thisNodeName != "" {
|
||||
return globals.cluster.thisNodeName
|
||||
|
||||
@@ -19,6 +19,48 @@ type testTenantStore struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type testLoginDeviceStore struct {
|
||||
devices map[types.Uid][]types.DeviceDef
|
||||
updates int
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *testLoginDeviceStore) Update(tenantID types.TenantID, uid types.Uid, oldDeviceID string, dev *types.DeviceDef) error {
|
||||
s.updates++
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
if dev != nil {
|
||||
dev.TenantID = tenantID
|
||||
s.devices[uid] = append(s.devices[uid], *dev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testLoginDeviceStore) GetAll(_ types.TenantID, uids ...types.Uid) (map[types.Uid][]types.DeviceDef, int, error) {
|
||||
if s.err != nil {
|
||||
return nil, 0, s.err
|
||||
}
|
||||
result := make(map[types.Uid][]types.DeviceDef)
|
||||
count := 0
|
||||
for _, uid := range uids {
|
||||
result[uid] = append([]types.DeviceDef(nil), s.devices[uid]...)
|
||||
count += len(result[uid])
|
||||
}
|
||||
return result, count, nil
|
||||
}
|
||||
|
||||
func (s *testLoginDeviceStore) Delete(_ types.TenantID, uid types.Uid, deviceID string) error {
|
||||
devices := s.devices[uid]
|
||||
for i, device := range devices {
|
||||
if device.DeviceId == deviceID {
|
||||
s.devices[uid] = append(devices[:i], devices[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return types.ErrNotFound
|
||||
}
|
||||
|
||||
func (s testTenantStore) GetByCode(string) (*types.Tenant, error) {
|
||||
return s.tenant, s.err
|
||||
}
|
||||
@@ -46,6 +88,40 @@ func test_makeSession(uid types.Uid) *Session {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginDeviceSecurityNoticeOnlyForNewDevice(t *testing.T) {
|
||||
uid := types.Uid(42)
|
||||
devices := &testLoginDeviceStore{devices: map[types.Uid][]types.DeviceDef{
|
||||
uid: {{TenantID: 7, DeviceId: "known-device", Platform: "ios"}},
|
||||
}}
|
||||
security := &testSecurityStore{}
|
||||
oldDevices := store.Devices
|
||||
oldSecurity := store.Security
|
||||
store.Devices = devices
|
||||
store.Security = security
|
||||
t.Cleanup(func() {
|
||||
store.Devices = oldDevices
|
||||
store.Security = oldSecurity
|
||||
})
|
||||
|
||||
known, err := isNewLoginDevice(7, uid, "known-device")
|
||||
if err != nil || known {
|
||||
t.Fatalf("known device detected as new: new=%v err=%v", known, err)
|
||||
}
|
||||
newDevice, err := isNewLoginDevice(7, uid, "new-device")
|
||||
if err != nil || !newDevice {
|
||||
t.Fatalf("new device was not detected: new=%v err=%v", newDevice, err)
|
||||
}
|
||||
|
||||
createLoginDeviceSecurityNotice(7, uid, "new-device", "ios", "UnitTestUA", time.Date(2026, 9, 6, 12, 0, 0, 0, time.UTC))
|
||||
if len(security.notifications) != 1 {
|
||||
t.Fatalf("expected one security notification, got %d", len(security.notifications))
|
||||
}
|
||||
notification := security.notifications[0]
|
||||
if notification.EventType != "security.login_device" || notification.Title == "" || notification.Action["device_id"] != "new-device" {
|
||||
t.Fatalf("unexpected login device notification: %#v", notification)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatchHello(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||
|
||||
@@ -493,6 +493,66 @@ func TestHandleBroadcastCall(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBroadcastDraftyCallInvite(t *testing.T) {
|
||||
numUsers := 2
|
||||
helper := TopicTestHelper{}
|
||||
helper.setUp(t, numUsers, types.TopicCatP2P, "p2p-test" /*attach=*/, true)
|
||||
oldThirdParty := store.ThirdPartyConfig
|
||||
globals.iceServers = []iceServer{{Username: "dummy"}}
|
||||
store.ThirdPartyConfig = testThirdPartyConfigStore{
|
||||
"audio_call_enabled": "1",
|
||||
"video_call_enabled": "1",
|
||||
}
|
||||
helper.topic.lastID = 5
|
||||
defer func() {
|
||||
globals.iceServers = nil
|
||||
store.ThirdPartyConfig = oldThirdParty
|
||||
helper.tearDown()
|
||||
}()
|
||||
helper.mm.EXPECT().Save(types.TenantID(1), gomock.Any(), gomock.Any(), gomock.Any()).
|
||||
Return(&types.MessageSaveResult{SeqId: 1, MarkedReadBySender: true}, nil)
|
||||
|
||||
from := helper.uids[0].UserId()
|
||||
msg := &ClientComMessage{
|
||||
AsUser: from,
|
||||
Original: from,
|
||||
Pub: &MsgClientPub{
|
||||
Topic: "p2p",
|
||||
Head: map[string]any{"webrtc": "started", "mime": "text/x-drafty"},
|
||||
Content: map[string]any{
|
||||
"txt": " ",
|
||||
"ent": []any{map[string]any{
|
||||
"tp": "VC",
|
||||
"data": map[string]any{
|
||||
"call_id": "call-drafty-1",
|
||||
"aonly": true,
|
||||
"state": "started",
|
||||
},
|
||||
}},
|
||||
},
|
||||
NoEcho: true,
|
||||
},
|
||||
sess: helper.sessions[0],
|
||||
}
|
||||
helper.topic.handleClientMsg(msg)
|
||||
helper.finish()
|
||||
|
||||
if errorMsgs, hasError := helper.hubMessages["__ERROR__"]; hasError {
|
||||
t.Fatal(errorMsgs[0].Ctrl.Text)
|
||||
}
|
||||
if len(helper.results[1].messages) != 1 {
|
||||
t.Fatalf("callee: expected 1 message, got %d", len(helper.results[1].messages))
|
||||
}
|
||||
data := helper.results[1].messages[0].(*ServerComMessage).Data
|
||||
content := data.Content.(map[string]any)
|
||||
if content["callId"] != "call-drafty-1" || content["callType"] != "audio" || content["video"] != false {
|
||||
t.Fatalf("unexpected normalized Drafty call content: %#v", content)
|
||||
}
|
||||
if data.Head["x-im-type"] != "call" || data.Head["mime"] != "text/x-drafty" {
|
||||
t.Fatalf("unexpected normalized Drafty call head: %#v", data.Head)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBroadcastCallRequiresStructuredInvite(t *testing.T) {
|
||||
numUsers := 2
|
||||
helper := TopicTestHelper{}
|
||||
|
||||
Reference in New Issue
Block a user