feat: support group invite privacy verification

This commit is contained in:
richjohnrambo
2026-09-12 11:20:48 +08:00
parent 80c802af20
commit ca3957e3b3
12 changed files with 197 additions and 63 deletions
+72 -4
View File
@@ -451,6 +451,9 @@ func (s *testGroupBusinessStore) AcceptInvitation(_ types.TenantID, invitationID
for i := range s.invitations {
if s.invitations[i].InvitationID == invitationID {
s.invitations[i].TargetUserID = uid
if s.invitations[i].JoinRequestID != "" {
return &s.invitations[i], nil
}
s.invitations[i].Status = types.GroupInvitationStatusJoined
s.invitations[i].Result = "joined"
s.invitations[i].HandledAt = &now
@@ -1063,7 +1066,7 @@ func (s *testProfilesStore) GetPrivacy(_ types.TenantID, uid types.Uid) (*types.
now := time.Now().UTC().Round(time.Millisecond)
s.privacy = &types.UserPrivacySettings{
UserID: uid, AddMeMethods: map[string]bool{"account": true, "phone": true, "qr": true, "group": true},
ProfileVisibility: "friends", LastSeenVisibility: "friends", ReadReceipts: true, Version: 1, CreatedAt: now, UpdatedAt: now,
ProfileVisibility: "friends", LastSeenVisibility: "friends", ReadReceipts: true, GroupInviteVerification: false, Version: 1, CreatedAt: now, UpdatedAt: now,
}
}
return s.privacy, nil
@@ -1083,6 +1086,9 @@ func (s *testProfilesStore) UpdatePrivacy(_ types.TenantID, uid types.Uid, patch
if patch.ReadReceipts != nil {
privacy.ReadReceipts = *patch.ReadReceipts
}
if patch.GroupInviteVerification != nil {
privacy.GroupInviteVerification = *patch.GroupInviteVerification
}
privacy.Version = patch.Version + 1
privacy.UpdatedAt = time.Now().UTC().Round(time.Millisecond)
return privacy, nil
@@ -3716,7 +3722,7 @@ func TestServeIMMePrivacy(t *testing.T) {
store.Profiles = profiles
defer func() { store.Profiles = oldProfiles }()
req := httptest.NewRequest(http.MethodPatch, "/v1/im/me/privacy", strings.NewReader(`{"add_me_methods":{"account":true,"phone":false,"qr":true,"group":true},"read_receipts":false,"version":1}`))
req := httptest.NewRequest(http.MethodPatch, "/v1/im/me/privacy", strings.NewReader(`{"add_me_methods":{"account":true,"phone":false,"qr":true,"group":true},"read_receipts":false,"group_invite_verification":true,"version":1}`))
req.Header.Set("Authorization", "Bearer token-1")
rec := httptest.NewRecorder()
serveIMMePrivacy(rec, req)
@@ -3726,7 +3732,9 @@ func TestServeIMMePrivacy(t *testing.T) {
}
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
methods := data["add_me_methods"].(map[string]any)
if methods["phone"] != false || data["read_receipts"] != false || data["version"].(float64) != 2 {
editable := data["editable"].(map[string]any)
if methods["phone"] != false || data["read_receipts"] != false || data["group_invite_verification"] != true ||
editable["group_invite_verification"] != true || data["version"].(float64) != 2 {
t.Fatalf("unexpected privacy: %#v", data)
}
}
@@ -4802,7 +4810,7 @@ func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
items := data["items"].([]any)
item := items[0].(map[string]any)
if item["invitation_id"] != "ginv_invite" || item["join_request_id"] != "gjr_request" || item["status"] != types.GroupInvitationStatusPending {
if item["invitation_id"] != "ginv_invite" || item["join_request_id"] != "gjr_request" || item["status"] != types.GroupInvitationStatusPendingApproval {
t.Fatalf("unexpected invitation response: %#v", data)
}
if len(groupStore.joinRequests) != 1 || groupStore.joinRequests[0].RequestID != "gjr_request" ||
@@ -4850,6 +4858,66 @@ func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
}
}
func TestServeIMGroupInvitationRespectsTargetPrivacyApproval(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStore := expectIMBearer(t, ctrl, "token-1", 7, 42)
mockStore.EXPECT().GetUidString().Return("invite")
mockStore.EXPECT().GetUidString().Return("request")
oldSubs := store.Subs
oldGroupBusiness := store.GroupBusiness
oldProfiles := store.Profiles
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
groupStore := &testGroupBusinessStore{settings: &types.GroupSettings{
Topic: "grpAlpha", OwnerUserID: types.Uid(42), Name: "Alpha", GroupStatus: types.GroupStatusActive,
JoinApproval: false, MemberInvite: true, MaxAdminCount: groupMaxAdminCount, Version: 1,
}}
store.Subs = subs
store.GroupBusiness = groupStore
store.Profiles = &testProfilesStore{privacy: &types.UserPrivacySettings{
UserID: types.Uid(43),
AddMeMethods: map[string]bool{"account": true, "phone": true, "qr": true, "group": true},
ProfileVisibility: "friends",
LastSeenVisibility: "friends",
ReadReceipts: true,
GroupInviteVerification: true,
Version: 1,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
}}
defer func() {
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
store.Profiles = oldProfiles
}()
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)
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", target, false).Return(nil, types.ErrNotFound)
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-privacy")
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)
item := items[0].(map[string]any)
if item["invitation_id"] != "ginv_invite" || item["join_request_id"] != "gjr_request" || item["status"] != types.GroupInvitationStatusPendingApproval {
t.Fatalf("unexpected invitation response: %#v", data)
}
if len(groupStore.joinRequests) != 1 || groupStore.joinRequests[0].ApplicantUserID != target || groupStore.joinRequests[0].Source != "invitation" {
t.Fatalf("join request was not created from privacy setting: %#v", groupStore.joinRequests)
}
}
func TestServeIMGroupInvitationAccept(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()