fix: sync harden channel reads and suggested approvals
This commit is contained in:
parent
037ce017d4
commit
209a0d279b
14 changed files with 467 additions and 26 deletions
|
|
@ -124,6 +124,13 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
|
|||
if !ok || !historyChat.Left || historyChat.ID != public.Channel.ID {
|
||||
t.Fatalf("history chat = %T %+v, want left public channel", history.Chats[0], history.Chats[0])
|
||||
}
|
||||
readOK, err := r.onChannelsReadHistory(WithUserID(ctx, viewer.ID), &tg.ChannelsReadHistoryRequest{
|
||||
Channel: input,
|
||||
MaxID: sent.Message.ID,
|
||||
})
|
||||
if err != nil || !readOK {
|
||||
t.Fatalf("non-member readHistory public preview = %v err=%v, want successful no-op", readOK, err)
|
||||
}
|
||||
|
||||
viewerCtx := WithSessionID(WithRawAuthKeyID(WithUserID(ctx, viewer.ID), [8]byte{9, 2}), 9202)
|
||||
diff, err := r.onUpdatesGetChannelDifference(viewerCtx, &tg.UpdatesGetChannelDifferenceRequest{
|
||||
|
|
@ -266,3 +273,44 @@ func TestPublicChannelPreviewRPCsAllowNonMember(t *testing.T) {
|
|||
t.Fatalf("joined peer dialog chat = %T %+v, want active channel with left=false", joinedPeerDialogs.Chats[0], joinedPeerDialogs.Chats[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelsReadHistoryAllowsSyntheticMonoforumViewers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
userStore := memory.NewUserStore()
|
||||
owner, err := userStore.Create(ctx, domain.User{AccessHash: 92101, Phone: "15550092101", FirstName: "Owner"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
subscriber, err := userStore.Create(ctx, domain.User{AccessHash: 92102, Phone: "15550092102", FirstName: "Subscriber"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channelStore := memory.NewChannelStore()
|
||||
channels := appchannels.NewService(channelStore)
|
||||
parent, err := channels.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
|
||||
Title: "Monoforum Read RPC", Broadcast: true, Date: 1_700_011_000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enabled, err := channelStore.SetPaidMessagesPrice(ctx, owner.ID, parent.Channel.ID, 0, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mono, err := channelStore.GetChannelByID(ctx, enabled.Channel.LinkedMonoforumID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := New(Config{}, Deps{
|
||||
Users: appusers.NewService(userStore), Channels: channels,
|
||||
}, zaptest.NewLogger(t), clock.System)
|
||||
for _, userID := range []int64{owner.ID, subscriber.ID} {
|
||||
ok, err := r.onChannelsReadHistory(WithUserID(ctx, userID), &tg.ChannelsReadHistoryRequest{
|
||||
Channel: &tg.InputChannel{ChannelID: mono.ID, AccessHash: mono.AccessHash},
|
||||
MaxID: mono.TopMessageID,
|
||||
})
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("synthetic monoforum read for %d = %v err=%v, want successful no-op", userID, ok, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ func (r *Router) onChannelsReadHistory(ctx context.Context, req *tg.ChannelsRead
|
|||
if r.deps.Channels == nil {
|
||||
return true, nil
|
||||
}
|
||||
if req == nil {
|
||||
return false, inputRequestInvalidErr()
|
||||
}
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return false, internalErr()
|
||||
|
|
@ -75,6 +78,9 @@ func (r *Router) onChannelsReadHistory(ctx context.Context, req *tg.ChannelsRead
|
|||
if err != nil {
|
||||
return false, channelInvalidErr(err)
|
||||
}
|
||||
if read.ReadOnly {
|
||||
return true, nil
|
||||
}
|
||||
if _, err := r.recordChannelReadInbox(ctx, userID, read); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
minSuggestedPostScheduleDelay = 5 * 60
|
||||
maxSuggestedPostScheduleDelay = 31 * 24 * 60 * 60
|
||||
maxSuggestedPostRejectComment = 1024
|
||||
)
|
||||
|
||||
|
|
@ -54,7 +52,7 @@ func (r *Router) onMessagesToggleSuggestedPostApproval(ctx context.Context, req
|
|||
}
|
||||
now := int(r.clock.Now().Unix())
|
||||
scheduleDate, hasScheduleDate := req.GetScheduleDate()
|
||||
if hasScheduleDate && (req.Reject || scheduleDate < now+minSuggestedPostScheduleDelay || scheduleDate > now+maxSuggestedPostScheduleDelay) {
|
||||
if hasScheduleDate && (req.Reject || scheduleDate <= 0) {
|
||||
return nil, scheduleDateInvalidErr()
|
||||
}
|
||||
result, err := service.ToggleSuggestedPostApproval(ctx, domain.ToggleSuggestedPostApprovalRequest{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package rpc
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/iamxvbaba/td/bin"
|
||||
"github.com/iamxvbaba/td/clock"
|
||||
|
|
@ -110,6 +111,107 @@ func TestMessagesToggleSuggestedPostApprovalRegisteredAndProjectsLifecycle(t *te
|
|||
}
|
||||
}
|
||||
|
||||
func TestMessagesToggleSuggestedPostApprovalAcceptsDelayedAbsoluteDate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
users := memory.NewUserStore()
|
||||
owner, err := users.Create(ctx, domain.User{AccessHash: 111, Phone: "15551110111", FirstName: "Owner"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
subscriber, err := users.Create(ctx, domain.User{AccessHash: 112, Phone: "15551110112", FirstName: "Subscriber"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
channelsStore := memory.NewChannelStore()
|
||||
channels := appchannels.NewService(channelsStore)
|
||||
created, err := channels.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
|
||||
Title: "Delayed Suggested", Broadcast: true, Date: 1_700_030_000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
enabled, err := channelsStore.SetPaidMessagesPrice(ctx, owner.ID, created.Channel.ID, 0, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mono, err := channelsStore.GetChannelByID(ctx, enabled.Channel.LinkedMonoforumID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
saved := domain.Peer{Type: domain.PeerTypeUser, ID: subscriber.ID}
|
||||
suggestion, err := channels.SendMonoforumMessage(ctx, domain.SendMonoforumMessageRequest{
|
||||
MonoforumID: mono.ID, SenderUserID: subscriber.ID, SavedPeer: saved,
|
||||
RandomID: 101, Message: "delayed RPC suggestion", SuggestedPost: &domain.SuggestedPost{}, Date: 1_700_030_010,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const now = 1_700_030_100
|
||||
req := &tg.MessagesToggleSuggestedPostApprovalRequest{
|
||||
Peer: &tg.InputPeerChannel{ChannelID: mono.ID, AccessHash: mono.AccessHash},
|
||||
MsgID: suggestion.Message.ID,
|
||||
}
|
||||
req.SetScheduleDate(now + 2*60)
|
||||
dispatch := func(t *testing.T, router *Router, layer int) *tg.Updates {
|
||||
t.Helper()
|
||||
var raw bin.Buffer
|
||||
if err := req.Encode(&raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := router.Dispatch(WithLayer(WithUserID(ctx, owner.ID), layer), [8]byte{}, 0, &raw)
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch delayed approval at Layer %d: %v", layer, err)
|
||||
}
|
||||
updates, ok := response.(*tg.Updates)
|
||||
if !ok {
|
||||
t.Fatalf("delayed approval response=%T", response)
|
||||
}
|
||||
return updates
|
||||
}
|
||||
first := dispatch(t, New(Config{}, Deps{
|
||||
Users: appusers.NewService(users), Channels: channels,
|
||||
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(now, 0)}), 228)
|
||||
if len(first.Updates) != 2 {
|
||||
t.Fatalf("near-future approval updates=%d, want edit + approval service", len(first.Updates))
|
||||
}
|
||||
replay := dispatch(t, New(Config{}, Deps{
|
||||
Users: appusers.NewService(users), Channels: channels,
|
||||
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(now+10*60, 0)}), 227)
|
||||
if len(replay.Updates) != 2 {
|
||||
t.Fatalf("late duplicate updates=%d, want persisted edit + approval service", len(replay.Updates))
|
||||
}
|
||||
|
||||
due, err := channels.SendMonoforumMessage(ctx, domain.SendMonoforumMessageRequest{
|
||||
MonoforumID: mono.ID, SenderUserID: subscriber.ID, SavedPeer: saved,
|
||||
RandomID: 102, Message: "due RPC suggestion", SuggestedPost: &domain.SuggestedPost{}, Date: now + 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dueReq := &tg.MessagesToggleSuggestedPostApprovalRequest{
|
||||
Peer: &tg.InputPeerChannel{ChannelID: mono.ID, AccessHash: mono.AccessHash},
|
||||
MsgID: due.Message.ID,
|
||||
}
|
||||
dueReq.SetScheduleDate(now - 1)
|
||||
var dueRaw bin.Buffer
|
||||
if err := dueReq.Encode(&dueRaw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
response, err := New(Config{}, Deps{
|
||||
Users: appusers.NewService(users), Channels: channels,
|
||||
}, zaptest.NewLogger(t), fixedClock{now: time.Unix(now+2, 0)}).Dispatch(
|
||||
WithLayer(WithUserID(ctx, owner.ID), 228), [8]byte{}, 0, &dueRaw,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch already-due approval: %v", err)
|
||||
}
|
||||
dueUpdates, ok := response.(*tg.Updates)
|
||||
if !ok || len(dueUpdates.Updates) != 3 {
|
||||
t.Fatalf("already-due response=%T %#v, want edit + approval + published", response, response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuggestedPostTLProjectionSeparatesSuggestionAndPublishedPaymentFlags(t *testing.T) {
|
||||
original := domain.ChannelMessage{ChannelID: 10, ID: 1, SenderUserID: 20, From: domain.Peer{Type: domain.PeerTypeUser, ID: 20}, SavedPeer: domain.Peer{Type: domain.PeerTypeUser, ID: 20}, Date: 100, Body: "proposal", SuggestedPost: &domain.SuggestedPost{Accepted: true, Price: &domain.SuggestedPostPrice{Kind: domain.SuggestedPostPriceStars, Amount: 10}}}
|
||||
proposal := tgChannelMessage(20, original).(*tg.Message)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue