feat: complete IM P0 and P1 reliability work
This commit is contained in:
+302
-19
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -33,6 +34,57 @@ type testVerificationStore struct {
|
||||
tokenHash string
|
||||
}
|
||||
|
||||
type testHTTPInfrastructureStore struct {
|
||||
records map[string]*types.HTTPIdempotencyRecord
|
||||
events []types.BusinessEvent
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) recordKey(record *types.HTTPIdempotencyRecord) string {
|
||||
return record.UserID.String() + "|" + record.Method + "|" + record.Route + "|" + record.Key
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) BeginIdempotency(_ types.TenantID, record *types.HTTPIdempotencyRecord) (*types.HTTPIdempotencyRecord, bool, error) {
|
||||
if s.records == nil {
|
||||
s.records = make(map[string]*types.HTTPIdempotencyRecord)
|
||||
}
|
||||
key := s.recordKey(record)
|
||||
if existing := s.records[key]; existing != nil {
|
||||
copy := *existing
|
||||
copy.ResponseBody = append([]byte(nil), existing.ResponseBody...)
|
||||
return ©, false, nil
|
||||
}
|
||||
copy := *record
|
||||
copy.State = types.HTTPIdempotencyProcessing
|
||||
s.records[key] = ©
|
||||
return ©, true, nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) CompleteIdempotency(_ types.TenantID, record *types.HTTPIdempotencyRecord) error {
|
||||
copy := *record
|
||||
copy.State = types.HTTPIdempotencyCompleted
|
||||
copy.ResponseBody = append([]byte(nil), record.ResponseBody...)
|
||||
s.records[s.recordKey(record)] = ©
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) AbortIdempotency(_ types.TenantID, record *types.HTTPIdempotencyRecord) error {
|
||||
delete(s.records, s.recordKey(record))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) CreateEvent(_ types.TenantID, event *types.BusinessEvent) error {
|
||||
s.events = append(s.events, *event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) ListPendingEvents(_ types.TenantID, _ int, _ time.Time) ([]types.BusinessEvent, error) {
|
||||
return append([]types.BusinessEvent(nil), s.events...), nil
|
||||
}
|
||||
|
||||
func (s *testHTTPInfrastructureStore) MarkEventDelivered(_ types.TenantID, _ string, _ time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testRelationsStore struct {
|
||||
context *types.RelationshipContext
|
||||
created *types.FriendRequest
|
||||
@@ -149,6 +201,12 @@ type testGroupBusinessStore struct {
|
||||
updatedMembers []types.GroupMemberPatch
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) CreateGroup(_ types.TenantID, _ *types.Topic, _ types.Uid, _ any,
|
||||
settings *types.GroupSettings, _ []types.Uid, _ any, _ time.Time) error {
|
||||
s.settings = settings
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) CreateSettings(_ types.TenantID, settings *types.GroupSettings) (*types.GroupSettings, error) {
|
||||
s.settings = settings
|
||||
return settings, nil
|
||||
@@ -225,7 +283,7 @@ func (s *testGroupBusinessStore) ListJoinRequests(_ types.TenantID, _ string, st
|
||||
return out, "", nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) ResolveJoinRequest(_ types.TenantID, topic, requestID string, actor types.Uid, action, _ string, _ int, now time.Time) (*types.GroupJoinRequest, error) {
|
||||
func (s *testGroupBusinessStore) ResolveJoinRequest(_ types.TenantID, topic, requestID string, actor types.Uid, action, _ string, _ int, _ any, now time.Time) (*types.GroupJoinRequest, error) {
|
||||
status := types.GroupJoinRequestStatusRejected
|
||||
if action == "accept" {
|
||||
status = types.GroupJoinRequestStatusAccepted
|
||||
@@ -267,6 +325,61 @@ func (s *testGroupBusinessStore) DeleteMute(_ types.TenantID, _ string, _ types.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) InviteMember(_ types.TenantID, topic string, inviter, target types.Uid,
|
||||
message, idempotencyKey string, _ any, now time.Time) (bool, error) {
|
||||
for _, invitation := range s.invitations {
|
||||
if invitation.Topic == topic && invitation.TargetUserID == target {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
s.invitations = append(s.invitations, types.GroupInvitation{Topic: topic, InviterUserID: inviter,
|
||||
TargetUserID: target, Message: message, IdempotencyKey: idempotencyKey,
|
||||
Status: types.GroupInvitationStatusJoined, CreatedAt: now, UpdatedAt: now})
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) Leave(_ types.TenantID, _ string, _ types.Uid, _ time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) UpdateRole(_ types.TenantID, topic string, _ types.Uid, target types.Uid,
|
||||
role string, _ types.AccessMode, _ time.Time) error {
|
||||
s.updatedMembers = append(s.updatedMembers, types.GroupMemberPatch{Role: &role})
|
||||
s.member = &types.GroupMember{Topic: topic, UserID: target, Role: role, MemberStatus: types.GroupMemberStatusActive}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) TransferOwner(_ types.TenantID, topic string, _, target types.Uid, _ int, _ time.Time) error {
|
||||
settings, _ := s.GetSettings(0, topic)
|
||||
settings.OwnerUserID = target
|
||||
s.settings = settings
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) CanPost(_ types.TenantID, topic string, uid types.Uid, admin bool, now time.Time) (bool, error) {
|
||||
if admin {
|
||||
return true, nil
|
||||
}
|
||||
if s.settings != nil && s.settings.Topic == topic && s.settings.MuteAll {
|
||||
return false, nil
|
||||
}
|
||||
for _, mute := range s.mutes {
|
||||
if mute.Topic == topic && mute.UserID == uid && mute.Status == types.GroupMuteStatusActive &&
|
||||
(mute.ExpiresAt == nil || mute.ExpiresAt.After(now)) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) ListPendingEvents(_ types.TenantID, _ int, _ time.Time) ([]types.GroupEvent, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *testGroupBusinessStore) MarkEventDelivered(_ types.TenantID, _ string, _ time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultStringPtr(value *string, fallback string) string {
|
||||
if value == nil {
|
||||
return fallback
|
||||
@@ -501,6 +614,12 @@ func (s *testProfilesStore) UpdatePrivacy(_ types.TenantID, uid types.Uid, patch
|
||||
if patch.AddMeMethods != nil {
|
||||
privacy.AddMeMethods = patch.AddMeMethods
|
||||
}
|
||||
if patch.ProfileVisibility != nil {
|
||||
privacy.ProfileVisibility = *patch.ProfileVisibility
|
||||
}
|
||||
if patch.LastSeenVisibility != nil {
|
||||
privacy.LastSeenVisibility = *patch.LastSeenVisibility
|
||||
}
|
||||
if patch.ReadReceipts != nil {
|
||||
privacy.ReadReceipts = *patch.ReadReceipts
|
||||
}
|
||||
@@ -821,6 +940,84 @@ func expectIMBearer(t *testing.T, ctrl *gomock.Controller, token string, tenantI
|
||||
return mockStore
|
||||
}
|
||||
|
||||
func TestIMHTTPProtocolHeaders(t *testing.T) {
|
||||
apiKey := makeTestAPIKey(t)
|
||||
called := 0
|
||||
handler := withIMHTTPProtocol("/v1/im/", http.HandlerFunc(func(wrt http.ResponseWriter, req *http.Request) {
|
||||
called++
|
||||
writeIMSuccess(wrt, req, map[string]any{"ok": true})
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusForbidden || called != 0 {
|
||||
t.Fatalf("missing API key: status=%d called=%d", rec.Code, called)
|
||||
}
|
||||
if rec.Header().Get("X-Request-ID") == "" {
|
||||
t.Fatal("server did not generate X-Request-ID")
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest || decodeIMHTTPResponse(t, rec).Reason != imReasonSchemaUnsupported {
|
||||
t.Fatalf("missing schema: status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
req.Header.Set("X-Request-ID", "request-1")
|
||||
rec = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK || called != 1 || rec.Header().Get("X-Request-ID") != "request-1" {
|
||||
t.Fatalf("valid protocol request: status=%d called=%d request_id=%q", rec.Code, called, rec.Header().Get("X-Request-ID"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIMHTTPIdempotencyReplayAndConflict(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldInfrastructure := store.HTTPInfrastructure
|
||||
infrastructure := &testHTTPInfrastructureStore{}
|
||||
store.HTTPInfrastructure = infrastructure
|
||||
defer func() { store.HTTPInfrastructure = oldInfrastructure }()
|
||||
|
||||
called := 0
|
||||
handler := withIMHTTPProtocol("/v1/im/", http.HandlerFunc(func(wrt http.ResponseWriter, req *http.Request) {
|
||||
called++
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
writeIMSuccess(wrt, req, map[string]any{"body": string(body), "call": called})
|
||||
}))
|
||||
apiKey := makeTestAPIKey(t)
|
||||
request := func(body string) *httptest.ResponseRecorder {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/test", strings.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
req.Header.Set("Idempotency-Key", "same-key")
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
first := request(`{"value":1}`)
|
||||
second := request(`{"value":1}`)
|
||||
if first.Code != http.StatusOK || second.Code != http.StatusOK || called != 1 {
|
||||
t.Fatalf("replay failed: first=%d second=%d called=%d", first.Code, second.Code, called)
|
||||
}
|
||||
if second.Header().Get("X-Idempotent-Replay") != "true" || first.Body.String() != second.Body.String() {
|
||||
t.Fatalf("response was not replayed: first=%s second=%s", first.Body.String(), second.Body.String())
|
||||
}
|
||||
conflict := request(`{"value":2}`)
|
||||
if conflict.Code != http.StatusConflict || decodeIMHTTPResponse(t, conflict).Reason != imReasonIdempotencyConflict || called != 1 {
|
||||
t.Fatalf("conflict failed: status=%d called=%d body=%s", conflict.Code, called, conflict.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMTenantResolveSuccessFromHeader(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{
|
||||
@@ -1364,11 +1561,16 @@ func TestServeIMWorkbench(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldWorkbench := store.Workbench
|
||||
oldTenants := store.Tenants
|
||||
store.Workbench = &testWorkbenchStore{version: 3, modules: []types.WorkbenchModule{
|
||||
{Code: "organization", Name: "组织架构", Route: "/organization", Enabled: true, Permission: "enabled"},
|
||||
{Code: "external", Name: "外部链接", Route: "https://example.com", Enabled: true, Permission: "enabled"},
|
||||
}}
|
||||
defer func() { store.Workbench = oldWorkbench }()
|
||||
store.Tenants = testTenantStore{tenant: &types.Tenant{ID: 7, Code: "acme", State: types.TenantStateActive}}
|
||||
defer func() {
|
||||
store.Workbench = oldWorkbench
|
||||
store.Tenants = oldTenants
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/workbench", nil)
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
@@ -1391,6 +1593,10 @@ func TestServeIMWorkbench(t *testing.T) {
|
||||
if item["code"] != "organization" || item["route"] != "/organization" {
|
||||
t.Fatalf("unexpected module: %#v", item)
|
||||
}
|
||||
enterprise := data["enterprise"].(map[string]any)
|
||||
if enterprise["tenant_code"] != "acme" {
|
||||
t.Fatalf("tenant_code: got %#v, want acme", enterprise["tenant_code"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMOrganizationDepartments(t *testing.T) {
|
||||
@@ -1838,6 +2044,43 @@ func TestServeIMMePrivacy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIMCanViewLastSeen(t *testing.T) {
|
||||
oldProfiles := store.Profiles
|
||||
oldRelations := store.Relations
|
||||
defer func() {
|
||||
store.Profiles = oldProfiles
|
||||
store.Relations = oldRelations
|
||||
}()
|
||||
|
||||
viewer, target := types.Uid(1), types.Uid(2)
|
||||
profiles := &testProfilesStore{privacy: &types.UserPrivacySettings{
|
||||
UserID: target, LastSeenVisibility: "everyone",
|
||||
}}
|
||||
store.Profiles = profiles
|
||||
store.Relations = &testRelationsStore{context: &types.RelationshipContext{
|
||||
TenantID: 7, UserID: viewer, PeerID: target, Relationship: types.RelationshipNone,
|
||||
}}
|
||||
if !imCanViewLastSeen(7, viewer, target) {
|
||||
t.Fatal("everyone visibility must expose last seen")
|
||||
}
|
||||
|
||||
profiles.privacy.LastSeenVisibility = "nobody"
|
||||
if imCanViewLastSeen(7, viewer, target) {
|
||||
t.Fatal("nobody visibility must hide last seen")
|
||||
}
|
||||
|
||||
profiles.privacy.LastSeenVisibility = "friends"
|
||||
if imCanViewLastSeen(7, viewer, target) {
|
||||
t.Fatal("non-friend must not see friends-only last seen")
|
||||
}
|
||||
store.Relations = &testRelationsStore{context: &types.RelationshipContext{
|
||||
TenantID: 7, UserID: viewer, PeerID: target, Relationship: types.RelationshipFriend,
|
||||
}}
|
||||
if !imCanViewLastSeen(7, viewer, target) {
|
||||
t.Fatal("friend must see friends-only last seen")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMAppReleaseLatest(t *testing.T) {
|
||||
oldTenants := store.Tenants
|
||||
oldReleases := store.AppReleases
|
||||
@@ -2118,13 +2361,16 @@ func TestServeIMMessageReceipts(t *testing.T) {
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldSubs := store.Subs
|
||||
oldTopics := store.Topics
|
||||
oldProfiles := store.Profiles
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
|
||||
store.Subs = subs
|
||||
store.Topics = topics
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Subs = oldSubs
|
||||
store.Topics = oldTopics
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
mySub := &types.Subscription{
|
||||
@@ -2326,15 +2572,6 @@ func TestServeIMGroupCreate(t *testing.T) {
|
||||
store.GroupBusiness = oldGroupBusiness
|
||||
}()
|
||||
|
||||
topics.EXPECT().Create(types.TenantID(7), gomock.Any(), types.Uid(42), gomock.Any()).DoAndReturn(
|
||||
func(_ types.TenantID, topic *types.Topic, _ types.Uid, _ any) error {
|
||||
if topic.Id != "grpnewgroup" {
|
||||
t.Fatalf("unexpected topic id: %s", topic.Id)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
subs.EXPECT().Create(types.TenantID(7), gomock.Any()).Return(nil)
|
||||
topics.EXPECT().UpdateSubCnt(types.TenantID(7), "grpnewgroup").Return(nil)
|
||||
topics.EXPECT().Get(types.TenantID(7), "grpnewgroup").Return(&types.Topic{
|
||||
ObjHeader: types.ObjHeader{Id: "grpnewgroup"},
|
||||
TenantID: 7,
|
||||
@@ -2475,9 +2712,6 @@ func TestServeIMGroupLeave(t *testing.T) {
|
||||
ModeWant: types.ModeCPublic,
|
||||
ModeGiven: types.ModeCPublic,
|
||||
}, nil)
|
||||
subs.EXPECT().Delete(types.TenantID(7), "grpAlpha", types.Uid(42)).Return(nil)
|
||||
topics.EXPECT().UpdateSubCnt(types.TenantID(7), "grpAlpha").Return(nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/groups/grpAlpha/leave", nil)
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -2493,6 +2727,46 @@ func TestServeIMGroupLeave(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMGroupInvitationAddsNonMember(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 42)
|
||||
oldSubs := store.Subs
|
||||
oldGroupBusiness := store.GroupBusiness
|
||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||
groupStore := &testGroupBusinessStore{}
|
||||
store.Subs = subs
|
||||
store.GroupBusiness = groupStore
|
||||
defer func() {
|
||||
store.Subs = oldSubs
|
||||
store.GroupBusiness = oldGroupBusiness
|
||||
}()
|
||||
owner := types.Uid(42)
|
||||
target := types.Uid(43)
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", owner, false).Return(&types.Subscription{
|
||||
TenantID: 7, User: owner.String(), Topic: "grpAlpha", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull,
|
||||
}, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/groups/grpAlpha/invitations",
|
||||
bytes.NewBufferString(`{"user_ids":["`+target.UserId()+`"],"message":"welcome"}`))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
req.Header.Set("Idempotency-Key", "invite-1")
|
||||
rec := httptest.NewRecorder()
|
||||
serveIMGroupResource(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
items := data["items"].([]any)
|
||||
if len(items) != 1 || items[0].(map[string]any)["status"] != "joined" {
|
||||
t.Fatalf("unexpected invitation response: %#v", data)
|
||||
}
|
||||
if len(groupStore.invitations) != 1 || groupStore.invitations[0].TargetUserID != target {
|
||||
t.Fatalf("invitation was not persisted: %#v", groupStore.invitations)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeIMGroupQRCodesAndResolve(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -2595,7 +2869,8 @@ func TestServeIMGroupAnnouncementGovernanceJoinRequest(t *testing.T) {
|
||||
store.GroupBusiness = oldGroupBusiness
|
||||
}()
|
||||
selfSub := &types.Subscription{TenantID: 7, User: types.Uid(42).String(), Topic: "grpAlpha", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull}
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(selfSub, nil).Times(3)
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(selfSub, nil).Times(2)
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(nil, nil)
|
||||
|
||||
annReq := httptest.NewRequest(http.MethodPut, "/v1/im/groups/grpAlpha/announcements/current", bytes.NewBufferString(`{"content":"公告","version":1}`))
|
||||
annReq.Header.Set("Authorization", "Bearer token-1")
|
||||
@@ -2663,10 +2938,7 @@ func TestServeIMGroupAdminsMutesAndOwnerTransfer(t *testing.T) {
|
||||
members := []types.Subscription{*ownerSub, member}
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", owner, false).Return(ownerSub, nil).Times(3)
|
||||
topics.EXPECT().GetUsers(types.TenantID(7), "grpAlpha", gomock.Any()).Return(members, nil).Times(2)
|
||||
subs.EXPECT().Update(types.TenantID(7), "grpAlpha", target, gomock.Any()).Return(nil).Times(2)
|
||||
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", target, false).Return(&member, nil)
|
||||
subs.EXPECT().Update(types.TenantID(7), "grpAlpha", owner, gomock.Any()).Return(nil)
|
||||
topics.EXPECT().OwnerChange(types.TenantID(7), "grpAlpha", target).Return(nil)
|
||||
|
||||
adminReq := httptest.NewRequest(http.MethodPost, "/v1/im/groups/grpAlpha/admins/batch", bytes.NewBufferString(`{"user_ids":["`+target.UserId()+`"],"version":1}`))
|
||||
adminReq.Header.Set("Authorization", "Bearer token-1")
|
||||
@@ -3236,6 +3508,7 @@ func TestServeIMMeQRCodesAndResolve(t *testing.T) {
|
||||
store.Store = mockStore
|
||||
oldUsers := store.Users
|
||||
oldRelations := store.Relations
|
||||
oldProfiles := store.Profiles
|
||||
users := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
||||
store.Users = users
|
||||
owner := types.Uid(1)
|
||||
@@ -3258,10 +3531,12 @@ func TestServeIMMeQRCodesAndResolve(t *testing.T) {
|
||||
Relationship: types.RelationshipNone,
|
||||
CanSend: false,
|
||||
}}
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Store = oldStore
|
||||
store.Users = oldUsers
|
||||
store.Relations = oldRelations
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/me/qrcodes", bytes.NewBufferString(`{"purpose":"add_friend","expires_in_seconds":60}`))
|
||||
@@ -3447,9 +3722,14 @@ func TestServeIMFriendRequestCreate(t *testing.T) {
|
||||
mockStore := expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||
mockStore.EXPECT().GetUidString().Return("request-1")
|
||||
oldRelations := store.Relations
|
||||
oldProfiles := store.Profiles
|
||||
relations := &testRelationsStore{}
|
||||
store.Relations = relations
|
||||
defer func() { store.Relations = oldRelations }()
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Relations = oldRelations
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/friend-requests",
|
||||
bytes.NewBufferString(`{"target_user_id":"`+types.Uid(2).UserId()+`","message":"你好","source":"search"}`))
|
||||
@@ -3643,6 +3923,7 @@ func TestServeIMUserProfile(t *testing.T) {
|
||||
expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||
oldUsers := store.Users
|
||||
oldRelations := store.Relations
|
||||
oldProfiles := store.Profiles
|
||||
users := mock_store.NewMockUsersPersistenceInterface(ctrl)
|
||||
peer := types.Uid(2)
|
||||
users.EXPECT().Get(types.TenantID(7), peer).Return(&types.User{
|
||||
@@ -3659,9 +3940,11 @@ func TestServeIMUserProfile(t *testing.T) {
|
||||
Relationship: types.RelationshipFriend,
|
||||
CanSend: true,
|
||||
}}
|
||||
store.Profiles = &testProfilesStore{}
|
||||
defer func() {
|
||||
store.Users = oldUsers
|
||||
store.Relations = oldRelations
|
||||
store.Profiles = oldProfiles
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/im/users/"+peer.UserId()+"/profile", nil)
|
||||
|
||||
Reference in New Issue
Block a user