fix: sync latest channel and contact fixes
This commit is contained in:
parent
e83650b9fd
commit
35e5d38f4d
11 changed files with 255 additions and 81 deletions
|
|
@ -2748,8 +2748,8 @@ func TestPublicChannelPreviewAllowsNonMemberHistory(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("non-member GetDifference public preview: %v", err)
|
t.Fatalf("non-member GetDifference public preview: %v", err)
|
||||||
}
|
}
|
||||||
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.NewMessages) != 1 || diff.NewMessages[0].Body != "public preview post" {
|
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.Events) != 0 || len(diff.NewMessages) != 0 || len(diff.OtherUpdates) != 0 {
|
||||||
t.Fatalf("preview diff = %+v, want public preview post at current pts", diff)
|
t.Fatalf("preview diff = %+v, want empty public preview difference at current pts", diff)
|
||||||
}
|
}
|
||||||
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
|
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
|
||||||
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)
|
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)
|
||||||
|
|
|
||||||
|
|
@ -421,20 +421,38 @@ func (s *Service) GetPeerSettings(ctx context.Context, userID int64, peer domain
|
||||||
return domain.PeerSettings{}, err
|
return domain.PeerSettings{}, err
|
||||||
}
|
}
|
||||||
shareContact := found && !contact.Mutual
|
shareContact := found && !contact.Mutual
|
||||||
|
needContactsException := false
|
||||||
if s.privacy != nil {
|
if s.privacy != nil {
|
||||||
peerCanSeePhone, err := s.privacy.CanSee(ctx, userID, peer.ID, domain.PrivacyKeyPhoneNumber)
|
peerCanSeePhone, err := s.peerCanSeeCurrentUserPhone(ctx, userID, peer.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.PeerSettings{}, err
|
return domain.PeerSettings{}, err
|
||||||
}
|
}
|
||||||
|
needContactsException = !peerCanSeePhone
|
||||||
shareContact = found && !peerCanSeePhone
|
shareContact = found && !peerCanSeePhone
|
||||||
}
|
}
|
||||||
return domain.PeerSettings{
|
return domain.PeerSettings{
|
||||||
AddContact: !found,
|
AddContact: !found,
|
||||||
BlockContact: !blocked,
|
BlockContact: !blocked,
|
||||||
ShareContact: shareContact,
|
ShareContact: shareContact,
|
||||||
|
NeedContactsException: needContactsException,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) peerCanSeeCurrentUserPhone(ctx context.Context, ownerUserID, viewerUserID int64) (bool, error) {
|
||||||
|
allowed, err := s.privacy.CanSee(ctx, ownerUserID, viewerUserID, domain.PrivacyKeyPhoneNumber)
|
||||||
|
if err != nil || allowed {
|
||||||
|
return allowed, err
|
||||||
|
}
|
||||||
|
if s.contacts == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
_, found, err := s.contacts.Get(ctx, viewerUserID, ownerUserID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
||||||
// BlockContact adds peer to the current user's blocklist.
|
// BlockContact adds peer to the current user's blocklist.
|
||||||
func (s *Service) BlockContact(ctx context.Context, userID, peerUserID int64, date int) (bool, error) {
|
func (s *Service) BlockContact(ctx context.Context, userID, peerUserID int64, date int) (bool, error) {
|
||||||
if s == nil || s.contacts == nil || userID == 0 || peerUserID == 0 || peerUserID == userID {
|
if s == nil || s.contacts == nil || userID == 0 || peerUserID == 0 || peerUserID == userID {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
privacyapp "telesrv/internal/app/privacy"
|
||||||
"telesrv/internal/domain"
|
"telesrv/internal/domain"
|
||||||
"telesrv/internal/store"
|
"telesrv/internal/store"
|
||||||
"telesrv/internal/store/memory"
|
"telesrv/internal/store/memory"
|
||||||
|
|
@ -391,6 +392,94 @@ func TestAddContactNormalizesPhoneToDigits(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddContactPhonePrivacyExceptionPeerSettings(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
users := memory.NewUserStore()
|
||||||
|
contactsStore := memory.NewContactStore()
|
||||||
|
privacySvc := privacyapp.NewService(memory.NewPrivacyStore(), contactsStore)
|
||||||
|
alice, err := users.Create(ctx, domain.User{Phone: "15550000101", FirstName: "Alice", LastName: "A"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create alice: %v", err)
|
||||||
|
}
|
||||||
|
bob, err := users.Create(ctx, domain.User{Phone: "15550000102", FirstName: "Bob", LastName: "B"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create bob: %v", err)
|
||||||
|
}
|
||||||
|
carol, err := users.Create(ctx, domain.User{Phone: "15550000103", FirstName: "Carol", LastName: "C"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create carol: %v", err)
|
||||||
|
}
|
||||||
|
dave, err := users.Create(ctx, domain.User{Phone: "15550000104", FirstName: "Dave", LastName: "D"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create dave: %v", err)
|
||||||
|
}
|
||||||
|
svc := NewService(contactsStore, users).Configure(WithPrivacyEvaluator(privacySvc))
|
||||||
|
|
||||||
|
bobSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: bob.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bob peer settings before add: %v", err)
|
||||||
|
}
|
||||||
|
if !bobSettings.AddContact || bobSettings.ShareContact || !bobSettings.NeedContactsException {
|
||||||
|
t.Fatalf("bob settings before add = %+v, want add + need exception only", bobSettings)
|
||||||
|
}
|
||||||
|
if _, err := svc.AddContact(ctx, alice.ID, domain.ContactInput{
|
||||||
|
ContactUserID: bob.ID,
|
||||||
|
Phone: bob.Phone,
|
||||||
|
FirstName: "Bobby",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("alice add bob: %v", err)
|
||||||
|
}
|
||||||
|
bobSettings, err = svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: bob.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bob peer settings after add without exception: %v", err)
|
||||||
|
}
|
||||||
|
if bobSettings.AddContact || !bobSettings.ShareContact || !bobSettings.NeedContactsException {
|
||||||
|
t.Fatalf("bob settings after add without exception = %+v, want share + need exception", bobSettings)
|
||||||
|
}
|
||||||
|
if allowed, err := privacySvc.CanSee(ctx, alice.ID, bob.ID, domain.PrivacyKeyPhoneNumber); err != nil {
|
||||||
|
t.Fatalf("bob can see alice phone: %v", err)
|
||||||
|
} else if allowed {
|
||||||
|
t.Fatalf("bob can see alice phone = true, want false before exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := svc.AddContact(ctx, alice.ID, domain.ContactInput{
|
||||||
|
ContactUserID: carol.ID,
|
||||||
|
Phone: carol.Phone,
|
||||||
|
FirstName: "Carol",
|
||||||
|
AddPhonePrivacyException: true,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("alice add carol with exception: %v", err)
|
||||||
|
}
|
||||||
|
carolSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: carol.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("carol peer settings after exception: %v", err)
|
||||||
|
}
|
||||||
|
if carolSettings.AddContact || carolSettings.ShareContact || carolSettings.NeedContactsException {
|
||||||
|
t.Fatalf("carol settings after exception = %+v, want no add/share/need exception", carolSettings)
|
||||||
|
}
|
||||||
|
if allowed, err := privacySvc.CanSee(ctx, alice.ID, carol.ID, domain.PrivacyKeyPhoneNumber); err != nil {
|
||||||
|
t.Fatalf("carol can see alice phone: %v", err)
|
||||||
|
} else if !allowed {
|
||||||
|
t.Fatalf("carol can see alice phone = false, want true after exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := contactsStore.Upsert(ctx, dave.ID, domain.ContactInput{
|
||||||
|
ContactUserID: alice.ID,
|
||||||
|
Phone: alice.Phone,
|
||||||
|
FirstName: alice.FirstName,
|
||||||
|
LastName: alice.LastName,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("dave add alice: %v", err)
|
||||||
|
}
|
||||||
|
daveSettings, err := svc.GetPeerSettings(ctx, alice.ID, domain.Peer{Type: domain.PeerTypeUser, ID: dave.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dave peer settings before alice add: %v", err)
|
||||||
|
}
|
||||||
|
if !daveSettings.AddContact || daveSettings.ShareContact || daveSettings.NeedContactsException {
|
||||||
|
t.Fatalf("dave settings with reverse contact = %+v, want add only", daveSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAcceptContactRequiresExistingContactRequest(t *testing.T) {
|
func TestAcceptContactRequiresExistingContactRequest(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
users := memory.NewUserStore()
|
users := memory.NewUserStore()
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,9 @@ func (s *Service) appendMissingChannelPeerPreviews(ctx context.Context, userID i
|
||||||
if !ok || view.Forbidden {
|
if !ok || view.Forbidden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if view.Self.Status != domain.ChannelMemberActive {
|
||||||
|
continue
|
||||||
|
}
|
||||||
history, err := s.channels.ListChannelHistory(ctx, userID, domain.ChannelHistoryFilter{
|
history, err := s.channels.ListChannelHistory(ctx, userID, domain.ChannelHistoryFilter{
|
||||||
ChannelID: channelID,
|
ChannelID: channelID,
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
|
|
|
||||||
|
|
@ -695,7 +695,7 @@ func TestGetPeerDialogsRejectsHugeVector(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
|
func TestGetPeerDialogsSkipsPublicChannelPreviewForNonMember(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
channelStore := memory.NewChannelStore()
|
channelStore := memory.NewChannelStore()
|
||||||
channels := appchannels.NewService(channelStore)
|
channels := appchannels.NewService(channelStore)
|
||||||
|
|
@ -716,13 +716,12 @@ func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("UpdateUsername public: %v", err)
|
t.Fatalf("UpdateUsername public: %v", err)
|
||||||
}
|
}
|
||||||
sent, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
||||||
ChannelID: public.Channel.ID,
|
ChannelID: public.Channel.ID,
|
||||||
RandomID: 99,
|
RandomID: 99,
|
||||||
Message: "public peer dialog top",
|
Message: "public peer dialog top",
|
||||||
Date: 1700002010,
|
Date: 1700002010,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendMessage public: %v", err)
|
t.Fatalf("SendMessage public: %v", err)
|
||||||
}
|
}
|
||||||
private, err := channels.CreateChannel(ctx, 1001, domain.CreateChannelRequest{
|
private, err := channels.CreateChannel(ctx, 1001, domain.CreateChannelRequest{
|
||||||
|
|
@ -741,28 +740,12 @@ func TestGetPeerDialogsIncludesPublicChannelPreviewForNonMember(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetPeerDialogs public preview: %v", err)
|
t.Fatalf("GetPeerDialogs public preview: %v", err)
|
||||||
}
|
}
|
||||||
if len(list.Dialogs) != 1 {
|
if len(list.Dialogs) != 0 || len(list.ChannelMessages) != 0 || len(list.Channels) != 0 || list.Count != 0 {
|
||||||
t.Fatalf("dialogs = %+v, want only public preview dialog", list.Dialogs)
|
t.Fatalf("peer dialogs = %+v, want no materialized public preview dialog", list)
|
||||||
}
|
|
||||||
dialog := findChannelDialog(t, list, public.Channel.ID)
|
|
||||||
if dialog.TopMessage != sent.Message.ID || dialog.TopMessageDate != sent.Message.Date {
|
|
||||||
t.Fatalf("preview dialog top = id %d date %d, want %d/%d", dialog.TopMessage, dialog.TopMessageDate, sent.Message.ID, sent.Message.Date)
|
|
||||||
}
|
|
||||||
if !dialog.ChannelLeft {
|
|
||||||
t.Fatalf("preview dialog ChannelLeft = false, want read-only left preview")
|
|
||||||
}
|
|
||||||
if dialog.UnreadCount != 0 || dialog.ReadInboxMaxID < sent.Message.ID || dialog.ReadOutboxMaxID < sent.Message.ID {
|
|
||||||
t.Fatalf("preview dialog read/unread = %+v, want read through top and no unread", dialog)
|
|
||||||
}
|
|
||||||
if len(list.ChannelMessages) != 1 || list.ChannelMessages[0].Body != "public peer dialog top" {
|
|
||||||
t.Fatalf("channel messages = %+v, want public top message", list.ChannelMessages)
|
|
||||||
}
|
|
||||||
if len(list.Channels) != 1 || list.Channels[0].ID != public.Channel.ID {
|
|
||||||
t.Fatalf("channels = %+v, want public channel shell", list.Channels)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
|
func TestGetPeerDialogsBatchesMissingChannelVisibilityChecks(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
channelStore := &countingDialogChannelStore{ChannelStore: memory.NewChannelStore()}
|
channelStore := &countingDialogChannelStore{ChannelStore: memory.NewChannelStore()}
|
||||||
channels := appchannels.NewService(channelStore)
|
channels := appchannels.NewService(channelStore)
|
||||||
|
|
@ -783,13 +766,12 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("UpdateUsername first: %v", err)
|
t.Fatalf("UpdateUsername first: %v", err)
|
||||||
}
|
}
|
||||||
firstMsg, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
||||||
ChannelID: first.Channel.ID,
|
ChannelID: first.Channel.ID,
|
||||||
RandomID: 101,
|
RandomID: 101,
|
||||||
Message: "first public preview",
|
Message: "first public preview",
|
||||||
Date: 1700002110,
|
Date: 1700002110,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendMessage first: %v", err)
|
t.Fatalf("SendMessage first: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,13 +790,12 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("UpdateUsername second: %v", err)
|
t.Fatalf("UpdateUsername second: %v", err)
|
||||||
}
|
}
|
||||||
secondMsg, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
if _, err := channels.SendMessage(ctx, 1001, domain.SendChannelMessageRequest{
|
||||||
ChannelID: second.Channel.ID,
|
ChannelID: second.Channel.ID,
|
||||||
RandomID: 102,
|
RandomID: 102,
|
||||||
Message: "second public preview",
|
Message: "second public preview",
|
||||||
Date: 1700002130,
|
Date: 1700002130,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SendMessage second: %v", err)
|
t.Fatalf("SendMessage second: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -839,19 +820,10 @@ func TestGetPeerDialogsBatchesMissingChannelPreviews(t *testing.T) {
|
||||||
t.Fatalf("GetPeerDialogs batch previews: %v", err)
|
t.Fatalf("GetPeerDialogs batch previews: %v", err)
|
||||||
}
|
}
|
||||||
if channelStore.getChannelsCalls != 1 || channelStore.getChannelCalls != 0 {
|
if channelStore.getChannelsCalls != 1 || channelStore.getChannelCalls != 0 {
|
||||||
t.Fatalf("preview channel calls: GetChannels=%d GetChannel=%d, want one batch call only", channelStore.getChannelsCalls, channelStore.getChannelCalls)
|
t.Fatalf("visibility channel calls: GetChannels=%d GetChannel=%d, want one batch call only", channelStore.getChannelsCalls, channelStore.getChannelCalls)
|
||||||
}
|
}
|
||||||
if len(list.Dialogs) != 2 {
|
if len(list.Dialogs) != 0 || len(list.ChannelMessages) != 0 || len(list.Channels) != 0 || list.Count != 0 {
|
||||||
t.Fatalf("dialogs = %+v, want two public previews", list.Dialogs)
|
t.Fatalf("peer dialogs = %+v, want no public preview dialogs", list)
|
||||||
}
|
|
||||||
if got := findChannelDialog(t, list, first.Channel.ID); got.TopMessage != firstMsg.Message.ID {
|
|
||||||
t.Fatalf("first preview top = %d, want %d", got.TopMessage, firstMsg.Message.ID)
|
|
||||||
}
|
|
||||||
if got := findChannelDialog(t, list, second.Channel.ID); got.TopMessage != secondMsg.Message.ID {
|
|
||||||
t.Fatalf("second preview top = %d, want %d", got.TopMessage, secondMsg.Message.ID)
|
|
||||||
}
|
|
||||||
if len(list.ChannelMessages) != 2 {
|
|
||||||
t.Fatalf("channel messages = %+v, want two top messages", list.ChannelMessages)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,20 +135,9 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("non-member getChannelDifference public preview: %v", err)
|
t.Fatalf("non-member getChannelDifference public preview: %v", err)
|
||||||
}
|
}
|
||||||
fullDiff, ok := diff.(*tg.UpdatesChannelDifference)
|
emptyDiff, ok := diff.(*tg.UpdatesChannelDifferenceEmpty)
|
||||||
if !ok || !fullDiff.Final || fullDiff.Pts != sent.Event.Pts || len(fullDiff.NewMessages) != 1 {
|
if !ok || !emptyDiff.Final || emptyDiff.Pts != sent.Event.Pts {
|
||||||
t.Fatalf("channel difference = %T %+v, want one public preview message at current pts", diff, diff)
|
t.Fatalf("channel difference = %T %+v, want empty public preview difference at current pts", diff, diff)
|
||||||
}
|
|
||||||
diffMsg, ok := fullDiff.NewMessages[0].(*tg.Message)
|
|
||||||
if !ok || diffMsg.Message != "public preview rpc post" {
|
|
||||||
t.Fatalf("channel difference message = %T %+v, want public preview rpc post", fullDiff.NewMessages[0], fullDiff.NewMessages[0])
|
|
||||||
}
|
|
||||||
if len(fullDiff.Chats) != 1 {
|
|
||||||
t.Fatalf("channel difference chats = %d, want public channel chat", len(fullDiff.Chats))
|
|
||||||
}
|
|
||||||
diffChat, ok := fullDiff.Chats[0].(*tg.Channel)
|
|
||||||
if !ok || !diffChat.Left || diffChat.ID != public.Channel.ID {
|
|
||||||
t.Fatalf("channel difference chat = %T %+v, want left public channel", fullDiff.Chats[0], fullDiff.Chats[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
domainPeers, err := r.dialogPeersFromInput(WithUserID(ctx, viewer.ID), viewer.ID, []tg.InputDialogPeerClass{&tg.InputDialogPeer{Peer: peer}})
|
domainPeers, err := r.dialogPeersFromInput(WithUserID(ctx, viewer.ID), viewer.ID, []tg.InputDialogPeerClass{&tg.InputDialogPeer{Peer: peer}})
|
||||||
|
|
@ -162,8 +151,8 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("dialog service public preview: %v", err)
|
t.Fatalf("dialog service public preview: %v", err)
|
||||||
}
|
}
|
||||||
if len(directPeerDialogs.Dialogs) != 1 || len(directPeerDialogs.ChannelMessages) != 1 || len(directPeerDialogs.Channels) != 1 {
|
if len(directPeerDialogs.Dialogs) != 0 || len(directPeerDialogs.ChannelMessages) != 0 || len(directPeerDialogs.Channels) != 0 {
|
||||||
t.Fatalf("direct peer dialogs = %+v, want one dialog/message/channel", directPeerDialogs)
|
t.Fatalf("direct peer dialogs = %+v, want no public preview dialog/message/channel", directPeerDialogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
peerDialogsReq := &tg.MessagesGetPeerDialogsRequest{
|
peerDialogsReq := &tg.MessagesGetPeerDialogsRequest{
|
||||||
|
|
@ -181,20 +170,8 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("getPeerDialogs response = %T, want peer dialogs", peerDialogsEnc)
|
t.Fatalf("getPeerDialogs response = %T, want peer dialogs", peerDialogsEnc)
|
||||||
}
|
}
|
||||||
if len(peerDialogs.Dialogs) != 1 || len(peerDialogs.Messages) != 1 || len(peerDialogs.Chats) != 1 {
|
if len(peerDialogs.Dialogs) != 0 || len(peerDialogs.Messages) != 0 || len(peerDialogs.Chats) != 0 {
|
||||||
t.Fatalf("peer dialogs = %+v, want one dialog/message/channel", peerDialogs)
|
t.Fatalf("peer dialogs = %+v, want no public preview dialog/message/channel", peerDialogs)
|
||||||
}
|
|
||||||
tgDialog, ok := peerDialogs.Dialogs[0].(*tg.Dialog)
|
|
||||||
if !ok || tgDialog.TopMessage <= 0 || tgDialog.UnreadCount != 0 {
|
|
||||||
t.Fatalf("peer dialog = %T %+v, want read-only public preview dialog", peerDialogs.Dialogs[0], peerDialogs.Dialogs[0])
|
|
||||||
}
|
|
||||||
tgMessage, ok := peerDialogs.Messages[0].(*tg.Message)
|
|
||||||
if !ok || tgMessage.Message != "public preview rpc post" {
|
|
||||||
t.Fatalf("peer dialog message = %T %+v, want public preview rpc post", peerDialogs.Messages[0], peerDialogs.Messages[0])
|
|
||||||
}
|
|
||||||
peerDialogChat, ok := peerDialogs.Chats[0].(*tg.Channel)
|
|
||||||
if !ok || !peerDialogChat.Left || peerDialogChat.ID != public.Channel.ID {
|
|
||||||
t.Fatalf("peer dialog chat = %T %+v, want left public channel", peerDialogs.Chats[0], peerDialogs.Chats[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := channelService.JoinChannel(ctx, viewer.ID, public.Channel.ID, 1700010120); err != nil {
|
if _, err := channelService.JoinChannel(ctx, viewer.ID, public.Channel.ID, 1700010120); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
appchannels "telesrv/internal/app/channels"
|
appchannels "telesrv/internal/app/channels"
|
||||||
appcontacts "telesrv/internal/app/contacts"
|
appcontacts "telesrv/internal/app/contacts"
|
||||||
|
appprivacy "telesrv/internal/app/privacy"
|
||||||
appstories "telesrv/internal/app/stories"
|
appstories "telesrv/internal/app/stories"
|
||||||
appupdates "telesrv/internal/app/updates"
|
appupdates "telesrv/internal/app/updates"
|
||||||
appusers "telesrv/internal/app/users"
|
appusers "telesrv/internal/app/users"
|
||||||
|
|
@ -1057,6 +1058,84 @@ func TestContactsAcceptContactReturnsSettingsAndReset(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContactsAddContactPhonePrivacyExceptionUpdatesPeerSettings(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
userStore := memory.NewUserStore()
|
||||||
|
contactsStore := memory.NewContactStore()
|
||||||
|
privacySvc := appprivacy.NewService(memory.NewPrivacyStore(), contactsStore)
|
||||||
|
alice, err := userStore.Create(ctx, domain.User{AccessHash: 31, Phone: "2001", FirstName: "Alice", LastName: "A"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create alice: %v", err)
|
||||||
|
}
|
||||||
|
bob, err := userStore.Create(ctx, domain.User{AccessHash: 32, Phone: "2002", FirstName: "Bob", LastName: "B"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create bob: %v", err)
|
||||||
|
}
|
||||||
|
carol, err := userStore.Create(ctx, domain.User{AccessHash: 33, Phone: "2003", FirstName: "Carol", LastName: "C"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create carol: %v", err)
|
||||||
|
}
|
||||||
|
contactsSvc := appcontacts.NewService(contactsStore, userStore).Configure(appcontacts.WithPrivacyEvaluator(privacySvc))
|
||||||
|
usersSvc := appusers.NewService(userStore, appusers.WithContactStore(contactsStore), appusers.WithPrivacyEvaluator(privacySvc))
|
||||||
|
r := New(Config{}, Deps{
|
||||||
|
Contacts: contactsSvc,
|
||||||
|
Users: usersSvc,
|
||||||
|
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000410, 0)})
|
||||||
|
|
||||||
|
withoutException, err := r.onContactsAddContact(WithUserID(ctx, alice.ID), &tg.ContactsAddContactRequest{
|
||||||
|
ID: &tg.InputUser{UserID: bob.ID, AccessHash: bob.AccessHash},
|
||||||
|
Phone: bob.Phone,
|
||||||
|
FirstName: "Bobby",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("contacts.addContact without exception: %v", err)
|
||||||
|
}
|
||||||
|
bobSettings := firstPeerSettingsUpdate(t, withoutException)
|
||||||
|
if bobSettings.Settings.AddContact || !bobSettings.Settings.ShareContact || !bobSettings.Settings.NeedContactsException {
|
||||||
|
t.Fatalf("bob peer settings = %+v, want share + need exception", bobSettings.Settings)
|
||||||
|
}
|
||||||
|
if allowed, err := privacySvc.CanSee(ctx, alice.ID, bob.ID, domain.PrivacyKeyPhoneNumber); err != nil {
|
||||||
|
t.Fatalf("bob can see alice phone: %v", err)
|
||||||
|
} else if allowed {
|
||||||
|
t.Fatalf("bob can see alice phone = true, want false before exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
withException, err := r.onContactsAddContact(WithUserID(ctx, alice.ID), &tg.ContactsAddContactRequest{
|
||||||
|
AddPhonePrivacyException: true,
|
||||||
|
ID: &tg.InputUser{UserID: carol.ID, AccessHash: carol.AccessHash},
|
||||||
|
Phone: carol.Phone,
|
||||||
|
FirstName: "Carol",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("contacts.addContact with exception: %v", err)
|
||||||
|
}
|
||||||
|
carolSettings := firstPeerSettingsUpdate(t, withException)
|
||||||
|
if carolSettings.Settings.AddContact || carolSettings.Settings.ShareContact || carolSettings.Settings.NeedContactsException {
|
||||||
|
t.Fatalf("carol peer settings = %+v, want no add/share/need exception", carolSettings.Settings)
|
||||||
|
}
|
||||||
|
if allowed, err := privacySvc.CanSee(ctx, alice.ID, carol.ID, domain.PrivacyKeyPhoneNumber); err != nil {
|
||||||
|
t.Fatalf("carol can see alice phone: %v", err)
|
||||||
|
} else if !allowed {
|
||||||
|
t.Fatalf("carol can see alice phone = false, want true after exception")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstPeerSettingsUpdate(t *testing.T, updates tg.UpdatesClass) *tg.UpdatePeerSettings {
|
||||||
|
t.Helper()
|
||||||
|
got, ok := updates.(*tg.Updates)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("updates = %T, want *tg.Updates", updates)
|
||||||
|
}
|
||||||
|
if len(got.Updates) == 0 {
|
||||||
|
t.Fatalf("updates = %+v, want UpdatePeerSettings", got.Updates)
|
||||||
|
}
|
||||||
|
settings, ok := got.Updates[0].(*tg.UpdatePeerSettings)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("update[0] = %T, want UpdatePeerSettings", got.Updates[0])
|
||||||
|
}
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
func TestContactsStatusesUsesOnlineSessionFallback(t *testing.T) {
|
func TestContactsStatusesUsesOnlineSessionFallback(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
alice := domain.User{ID: 1000000001, AccessHash: 11, FirstName: "Alice"}
|
alice := domain.User{ID: 1000000001, AccessHash: 11, FirstName: "Alice"}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"telesrv/internal/domain"
|
"telesrv/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
@ -119,8 +120,23 @@ func (s *ChannelStore) GetChannelDialogs(_ context.Context, viewerUserID int64,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[channelID] = struct{}{}
|
seen[channelID] = struct{}{}
|
||||||
channel, member, _, err := s.channelForViewerLocked(viewerUserID, channelID)
|
channel, member, err := s.channelAndMemberLocked(viewerUserID, channelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !errors.Is(err, domain.ErrChannelPrivate) && !errors.Is(err, domain.ErrChannelInvalid) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
channel, ok = s.channels[channelID]
|
||||||
|
if !ok || channel.Deleted || !channel.Monoforum || channel.LinkedMonoforumID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parentMember, ok := s.members[channel.LinkedMonoforumID][viewerUserID]
|
||||||
|
if !ok || parentMember.Status != domain.ChannelMemberActive || !isChannelAdmin(parentMember) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
member = syntheticMonoforumAdminMember(channel, parentMember)
|
||||||
|
}
|
||||||
|
if member.Status != domain.ChannelMemberActive {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dialog := channelDialogToDialog(s.dialogForMemberLocked(viewerUserID, channel, member), channel.Pts, member.Status)
|
dialog := channelDialogToDialog(s.dialogForMemberLocked(viewerUserID, channel, member), channel.Pts, member.Status)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,16 @@ func (s *ChannelStore) ListChannelDifference(_ context.Context, req domain.Chann
|
||||||
if preview {
|
if preview {
|
||||||
dialog = previewChannelDialog(req.UserID, channel, member)
|
dialog = previewChannelDialog(req.UserID, channel, member)
|
||||||
}
|
}
|
||||||
|
if preview && member.Status != domain.ChannelMemberActive {
|
||||||
|
return domain.ChannelDifference{
|
||||||
|
Channel: channel,
|
||||||
|
Self: member,
|
||||||
|
Pts: channel.Pts,
|
||||||
|
Final: true,
|
||||||
|
Timeout: 30,
|
||||||
|
Dialog: dialog,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
if channel.Pts-req.Pts > limit {
|
if channel.Pts-req.Pts > limit {
|
||||||
messages := make([]domain.ChannelMessage, 0, domain.MaxChannelDifferenceTooLongMessages)
|
messages := make([]domain.ChannelMessage, 0, domain.MaxChannelDifferenceTooLongMessages)
|
||||||
for i := len(s.messages[req.ChannelID]) - 1; i >= 0 && len(messages) < domain.MaxChannelDifferenceTooLongMessages; i-- {
|
for i := len(s.messages[req.ChannelID]) - 1; i >= 0 && len(messages) < domain.MaxChannelDifferenceTooLongMessages; i-- {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ func TestChannelStoreDifferenceStartsAtMemberAvailableMinPts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChannelStorePublicPreviewDifferenceAllowsNonMember(t *testing.T) {
|
func TestChannelStorePublicPreviewDifferenceSkipsNonMemberMessages(t *testing.T) {
|
||||||
pool := testPool(t)
|
pool := testPool(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
suffix := randomSuffix(t)
|
suffix := randomSuffix(t)
|
||||||
|
|
@ -183,8 +183,8 @@ func TestChannelStorePublicPreviewDifferenceAllowsNonMember(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("list public preview difference: %v", err)
|
t.Fatalf("list public preview difference: %v", err)
|
||||||
}
|
}
|
||||||
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.NewMessages) != 1 || diff.NewMessages[0].Body != "public preview difference" {
|
if !diff.Final || diff.Pts != sent.Event.Pts || len(diff.Events) != 0 || len(diff.NewMessages) != 0 || len(diff.OtherUpdates) != 0 {
|
||||||
t.Fatalf("preview diff = %+v, want one public preview message at current pts", diff)
|
t.Fatalf("preview diff = %+v, want empty public preview difference at current pts", diff)
|
||||||
}
|
}
|
||||||
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
|
if diff.Dialog.UnreadCount != 0 || diff.Dialog.ReadInboxMaxID < sent.Message.ID {
|
||||||
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)
|
t.Fatalf("preview diff dialog = %+v, want read-only public preview dialog", diff.Dialog)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,16 @@ func (s *ChannelStore) ListChannelDifference(ctx context.Context, req domain.Cha
|
||||||
if limit <= 0 || limit > domain.MaxChannelDifferenceLimit {
|
if limit <= 0 || limit > domain.MaxChannelDifferenceLimit {
|
||||||
limit = domain.MaxChannelDifferenceLimit
|
limit = domain.MaxChannelDifferenceLimit
|
||||||
}
|
}
|
||||||
|
if preview && member.Status != domain.ChannelMemberActive {
|
||||||
|
return domain.ChannelDifference{
|
||||||
|
Channel: channel,
|
||||||
|
Self: member,
|
||||||
|
Pts: channel.Pts,
|
||||||
|
Final: true,
|
||||||
|
Timeout: 30,
|
||||||
|
Dialog: previewChannelDialog(req.UserID, channel, member),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
if channel.Pts-req.Pts > limit {
|
if channel.Pts-req.Pts > limit {
|
||||||
args := []any{req.ChannelID}
|
args := []any{req.ChannelID}
|
||||||
where := "channel_id = $1 AND NOT deleted"
|
where := "channel_id = $1 AND NOT deleted"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue