315 lines
12 KiB
Go
315 lines
12 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/gotd/td/clock"
|
|
"github.com/gotd/td/tg"
|
|
"github.com/gotd/td/tgerr"
|
|
"go.uber.org/zap/zaptest"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestRPCRequestFingerprintStableAndPayloadSensitive(t *testing.T) {
|
|
req := &tg.MessagesSendMessageRequest{
|
|
Peer: &tg.InputPeerUser{UserID: 1002, AccessHash: 22},
|
|
Message: "hello",
|
|
RandomID: 991,
|
|
Entities: []tg.MessageEntityClass{&tg.MessageEntityBold{Offset: 0, Length: 5}},
|
|
}
|
|
|
|
first, err := rpcRequestFingerprint(req)
|
|
if err != nil {
|
|
t.Fatalf("first fingerprint: %v", err)
|
|
}
|
|
second, err := rpcRequestFingerprint(req)
|
|
if err != nil {
|
|
t.Fatalf("second fingerprint: %v", err)
|
|
}
|
|
if len(first) != 32 || !bytes.Equal(first, second) {
|
|
t.Fatalf("fingerprints = %x / %x, want stable SHA-256", first, second)
|
|
}
|
|
|
|
req.Message = "changed"
|
|
changed, err := rpcRequestFingerprint(req)
|
|
if err != nil {
|
|
t.Fatalf("changed fingerprint: %v", err)
|
|
}
|
|
if bytes.Equal(first, changed) {
|
|
t.Fatalf("changed payload fingerprint = %x, want different from %x", changed, first)
|
|
}
|
|
}
|
|
|
|
func TestSendMessageFingerprintIgnoresRetryOnlyHints(t *testing.T) {
|
|
first := &tg.MessagesSendMessageRequest{
|
|
Peer: &tg.InputPeerUser{UserID: 1002, AccessHash: 22},
|
|
Message: "hello",
|
|
RandomID: 991,
|
|
ClearDraft: true,
|
|
Background: true,
|
|
UpdateStickersetsOrder: true,
|
|
}
|
|
retry := *first
|
|
retry.Flags.Set(31) // stale decoded flags must not leak into the canonical intent.
|
|
retry.ClearDraft = false
|
|
retry.Background = false
|
|
retry.UpdateStickersetsOrder = false
|
|
|
|
a, err := sendMessageIdempotencyFingerprint(first)
|
|
if err != nil {
|
|
t.Fatalf("first fingerprint: %v", err)
|
|
}
|
|
b, err := sendMessageIdempotencyFingerprint(&retry)
|
|
if err != nil {
|
|
t.Fatalf("retry fingerprint: %v", err)
|
|
}
|
|
if !bytes.Equal(a, b) {
|
|
t.Fatalf("retry-only flags changed fingerprint: %x != %x", a, b)
|
|
}
|
|
|
|
retry.Message = "different"
|
|
c, err := sendMessageIdempotencyFingerprint(&retry)
|
|
if err != nil {
|
|
t.Fatalf("changed fingerprint: %v", err)
|
|
}
|
|
if bytes.Equal(a, c) {
|
|
t.Fatal("durable message change did not change fingerprint")
|
|
}
|
|
}
|
|
|
|
func TestSendMultiMediaFingerprintIsPerItemAndSubsetStable(t *testing.T) {
|
|
item1 := tg.InputSingleMedia{Media: &tg.InputMediaEmpty{}, RandomID: 101, Message: "one"}
|
|
item2 := tg.InputSingleMedia{Media: &tg.InputMediaEmpty{}, RandomID: 102, Message: "two"}
|
|
full := &tg.MessagesSendMultiMediaRequest{
|
|
Peer: &tg.InputPeerUser{UserID: 1002, AccessHash: 22},
|
|
ClearDraft: true,
|
|
Background: true,
|
|
MultiMedia: []tg.InputSingleMedia{item1, item2},
|
|
}
|
|
subset := &tg.MessagesSendMultiMediaRequest{
|
|
Peer: full.Peer,
|
|
MultiMedia: []tg.InputSingleMedia{item2},
|
|
}
|
|
|
|
fromFull, err := sendMultiMediaItemIdempotencyFingerprint(full, item2)
|
|
if err != nil {
|
|
t.Fatalf("full item fingerprint: %v", err)
|
|
}
|
|
fromSubset, err := sendMultiMediaItemIdempotencyFingerprint(subset, item2)
|
|
if err != nil {
|
|
t.Fatalf("subset item fingerprint: %v", err)
|
|
}
|
|
if !bytes.Equal(fromFull, fromSubset) {
|
|
t.Fatalf("subset retry fingerprint = %x, want %x", fromSubset, fromFull)
|
|
}
|
|
|
|
changed := item2
|
|
changed.Message = "changed"
|
|
different, err := sendMultiMediaItemIdempotencyFingerprint(subset, changed)
|
|
if err != nil {
|
|
t.Fatalf("changed item fingerprint: %v", err)
|
|
}
|
|
if bytes.Equal(fromFull, different) {
|
|
t.Fatal("changed album item reused the original fingerprint")
|
|
}
|
|
}
|
|
|
|
func TestForwardFingerprintIsPerItemAndSubsetStable(t *testing.T) {
|
|
full := &tg.MessagesForwardMessagesRequest{
|
|
FromPeer: &tg.InputPeerUser{UserID: 1002, AccessHash: 22},
|
|
ID: []int{41, 42},
|
|
RandomID: []int64{201, 202},
|
|
ToPeer: &tg.InputPeerUser{UserID: 1003, AccessHash: 33},
|
|
Background: true,
|
|
}
|
|
subset := *full
|
|
subset.Flags = 0
|
|
subset.Background = false
|
|
subset.ID = []int{42}
|
|
subset.RandomID = []int64{202}
|
|
|
|
fromFull, err := forwardMessagesItemIdempotencyFingerprint(full, 42, 202)
|
|
if err != nil {
|
|
t.Fatalf("full item fingerprint: %v", err)
|
|
}
|
|
fromSubset, err := forwardMessagesItemIdempotencyFingerprint(&subset, 42, 202)
|
|
if err != nil {
|
|
t.Fatalf("subset item fingerprint: %v", err)
|
|
}
|
|
if !bytes.Equal(fromFull, fromSubset) {
|
|
t.Fatalf("subset retry fingerprint = %x, want %x", fromSubset, fromFull)
|
|
}
|
|
|
|
different, err := forwardMessagesItemIdempotencyFingerprint(&subset, 41, 202)
|
|
if err != nil {
|
|
t.Fatalf("changed source fingerprint: %v", err)
|
|
}
|
|
if bytes.Equal(fromFull, different) {
|
|
t.Fatal("different source message reused the original fingerprint")
|
|
}
|
|
}
|
|
|
|
func TestMessageSendErrMapsRandomIDConflict(t *testing.T) {
|
|
err := messageSendErr(fmt.Errorf("wrapped store error: %w", domain.ErrMessageRandomIDDuplicate))
|
|
if !tgerr.Is(err, "RANDOM_ID_DUPLICATE") || !tgerr.IsCode(err, 500) {
|
|
t.Fatalf("messageSendErr = %v, want 500 RANDOM_ID_DUPLICATE", err)
|
|
}
|
|
}
|
|
|
|
func TestMessageForwardErrMapsRandomIDConflict(t *testing.T) {
|
|
err := messageForwardErr(fmt.Errorf("wrapped store error: %w", domain.ErrMessageRandomIDDuplicate))
|
|
if !tgerr.Is(err, "RANDOM_ID_DUPLICATE") || !tgerr.IsCode(err, 500) {
|
|
t.Fatalf("messageForwardErr = %v, want 500 RANDOM_ID_DUPLICATE", err)
|
|
}
|
|
}
|
|
|
|
func TestPrivateSendDuplicateResponseIncludesAndroidConfirmationSnapshot(t *testing.T) {
|
|
res := domain.SendPrivateTextResult{
|
|
Duplicate: true,
|
|
SenderMessage: domain.Message{
|
|
ID: 41, UID: 51, RandomID: 9911, OwnerUserID: 1001,
|
|
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 1002},
|
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: 1001},
|
|
Date: 1700000000, Out: true, Body: "confirmed", Pts: 7,
|
|
},
|
|
SenderEvent: domain.UpdateEvent{Pts: 7, PtsCount: 1, Date: 1700000000},
|
|
}
|
|
updates := tgPrivateSendResultUpdates(res, 9911, true, nil, nil)
|
|
if len(updates.Updates) != 2 {
|
|
t.Fatalf("duplicate updates = %#v, want mapping + new message", updates.Updates)
|
|
}
|
|
mapping, ok := updates.Updates[0].(*tg.UpdateMessageID)
|
|
if !ok || mapping.ID != 41 || mapping.RandomID != 9911 {
|
|
t.Fatalf("duplicate mapping = %#v, want id/random_id 41/9911", updates.Updates[0])
|
|
}
|
|
confirmed, ok := updates.Updates[1].(*tg.UpdateNewMessage)
|
|
if !ok || confirmed.Pts != 7 || confirmed.PtsCount != 1 {
|
|
t.Fatalf("duplicate confirmation = %#v, want UpdateNewMessage pts 7/1", updates.Updates[1])
|
|
}
|
|
msg, ok := confirmed.Message.(*tg.Message)
|
|
if !ok || msg.ID != 41 || msg.Message != "confirmed" {
|
|
t.Fatalf("duplicate confirmation message = %#v, want sender snapshot", confirmed.Message)
|
|
}
|
|
}
|
|
|
|
func TestPrivateSendDeletedDuplicateConfirmsThenConvergesWithDurableDelete(t *testing.T) {
|
|
deleteEvent := domain.UpdateEvent{UserID: 1001, Type: domain.UpdateEventDeleteMessages, Pts: 9, PtsCount: 1, Date: 1700000002, MessageIDs: []int{41}}
|
|
res := domain.SendPrivateTextResult{
|
|
Duplicate: true,
|
|
SenderMessage: domain.Message{
|
|
ID: 41, UID: 51, RandomID: 9911, OwnerUserID: 1001,
|
|
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 1002},
|
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: 1001},
|
|
Date: 1700000000, Out: true, Body: "original", Pts: 7,
|
|
},
|
|
SenderEvent: domain.UpdateEvent{Pts: 7, PtsCount: 1, Date: 1700000000},
|
|
ReplayDeleteEvent: &deleteEvent,
|
|
}
|
|
updates := tgPrivateSendResultUpdates(res, 9911, true, nil, nil)
|
|
if len(updates.Updates) != 3 {
|
|
t.Fatalf("deleted duplicate updates = %#v, want mapping + new + delete", updates.Updates)
|
|
}
|
|
if _, ok := updates.Updates[1].(*tg.UpdateNewMessage); !ok {
|
|
t.Fatalf("deleted duplicate confirmation = %#v, want UpdateNewMessage", updates.Updates[1])
|
|
}
|
|
deleted, ok := updates.Updates[2].(*tg.UpdateDeleteMessages)
|
|
if !ok || deleted.Pts != 9 || deleted.PtsCount != 1 || len(deleted.Messages) != 1 || deleted.Messages[0] != 41 {
|
|
t.Fatalf("deleted duplicate convergence = %#v, want real delete event", updates.Updates[2])
|
|
}
|
|
}
|
|
|
|
func TestForwardDeletedDuplicateIncludesMessageAndDelete(t *testing.T) {
|
|
deleteEvent := &domain.UpdateEvent{UserID: 1001, Type: domain.UpdateEventDeleteMessages, Pts: 12, PtsCount: 1, Date: 1700000012, MessageIDs: []int{61}}
|
|
updates := tgForwardMessagesUpdates(domain.ForwardPrivateMessagesResult{
|
|
SenderMessages: []domain.Message{{
|
|
ID: 61, UID: 71, RandomID: 8811, OwnerUserID: 1001,
|
|
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 1002},
|
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: 1001},
|
|
Date: 1700000010, Out: true, Body: "forwarded", Pts: 10,
|
|
}},
|
|
SenderEvents: []domain.UpdateEvent{{Pts: 10, PtsCount: 1, Date: 1700000010}},
|
|
Duplicates: []bool{true},
|
|
ReplayDeleteEvents: []*domain.UpdateEvent{deleteEvent},
|
|
}, []int64{8811}, nil, nil)
|
|
if len(updates.Updates) != 3 {
|
|
t.Fatalf("forward duplicate updates = %#v, want mapping + new + delete", updates.Updates)
|
|
}
|
|
if _, ok := updates.Updates[1].(*tg.UpdateNewMessage); !ok {
|
|
t.Fatalf("forward duplicate confirmation = %#v, want UpdateNewMessage", updates.Updates[1])
|
|
}
|
|
if deleted, ok := updates.Updates[2].(*tg.UpdateDeleteMessages); !ok || len(deleted.Messages) != 1 || deleted.Messages[0] != 61 || deleted.Pts != 12 {
|
|
t.Fatalf("forward duplicate delete = %#v, want durable delete", updates.Updates[2])
|
|
}
|
|
}
|
|
|
|
func TestChannelDeletedDuplicateEchoIncludesMessageAndDelete(t *testing.T) {
|
|
router := New(Config{}, Deps{}, zaptest.NewLogger(t), clock.System)
|
|
deleteEvent := &domain.ChannelUpdateEvent{ChannelID: 2001, Type: domain.ChannelUpdateDeleteMessages, Pts: 12, PtsCount: 1, Date: 1700000012, MessageIDs: []int{61}}
|
|
msg := domain.ChannelMessage{
|
|
ChannelID: 2001, ID: 61, RandomID: 8811, SenderUserID: 1001,
|
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: 1001},
|
|
Date: 1700000010, Body: "channel", Pts: 10,
|
|
}
|
|
updates := router.channelMessagesUpdatesWithPeerCache(context.Background(), 1001, []domain.SendChannelMessageResult{{
|
|
Channel: domain.Channel{ID: 2001, AccessHash: 22, Title: "group", Megagroup: true, Date: 1700000000},
|
|
Message: msg,
|
|
Event: domain.ChannelUpdateEvent{
|
|
ChannelID: 2001, Type: domain.ChannelUpdateNewMessage, Pts: 10, PtsCount: 1, Date: 1700000010, Message: msg,
|
|
},
|
|
Duplicate: true,
|
|
ReplayDeleteEvent: deleteEvent,
|
|
}}, []int64{8811}, true, nil, newViewerPeerCache(router))
|
|
if len(updates.Updates) != 3 {
|
|
t.Fatalf("channel duplicate updates = %#v, want mapping + new + delete", updates.Updates)
|
|
}
|
|
if _, ok := updates.Updates[1].(*tg.UpdateNewChannelMessage); !ok {
|
|
t.Fatalf("channel duplicate confirmation = %#v, want UpdateNewChannelMessage", updates.Updates[1])
|
|
}
|
|
if deleted, ok := updates.Updates[2].(*tg.UpdateDeleteChannelMessages); !ok || deleted.ChannelID != 2001 || len(deleted.Messages) != 1 || deleted.Messages[0] != 61 || deleted.Pts != 12 {
|
|
t.Fatalf("channel duplicate delete = %#v, want durable channel delete", updates.Updates[2])
|
|
}
|
|
}
|
|
|
|
func TestPrivateSendRecordsRawOriginAuthKey(t *testing.T) {
|
|
const (
|
|
senderID = int64(1001)
|
|
recipientID = int64(1002)
|
|
)
|
|
raw := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
|
|
business := [8]byte{8, 7, 6, 5, 4, 3, 2, 1}
|
|
messages := &captureMessages{}
|
|
router := New(Config{}, Deps{
|
|
Messages: messages,
|
|
Users: mapUsersService{users: map[int64]domain.User{
|
|
senderID: {ID: senderID, FirstName: "Sender"},
|
|
recipientID: {ID: recipientID, FirstName: "Recipient"},
|
|
}},
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
ctx := WithSessionID(
|
|
WithRawAuthKeyID(
|
|
WithAuthKeyID(
|
|
WithUserID(context.Background(), senderID),
|
|
business,
|
|
),
|
|
raw,
|
|
),
|
|
77,
|
|
)
|
|
if _, err := router.onMessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
|
|
Peer: &tg.InputPeerUser{UserID: recipientID},
|
|
Message: "raw origin",
|
|
RandomID: 992,
|
|
}); err != nil {
|
|
t.Fatalf("send message: %v", err)
|
|
}
|
|
if messages.sendReq.OriginAuthKeyID != raw || messages.sendReq.OriginAuthKeyID == business {
|
|
t.Fatalf("origin auth key = %x, want raw %x (business %x)", messages.sendReq.OriginAuthKeyID, raw, business)
|
|
}
|
|
if messages.sendReq.OriginSessionID != 77 {
|
|
t.Fatalf("origin session = %d, want 77", messages.sendReq.OriginSessionID)
|
|
}
|
|
}
|