247 lines
8.9 KiB
Go
247 lines
8.9 KiB
Go
package rpc
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/iamxvbaba/td/tg"
|
||
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// "see https://example.com/x" 的 URL 在 UTF-16 偏移 4、长度 21。
|
||
const (
|
||
wpTestMessage = "see https://example.com/x"
|
||
wpTestURL = "https://example.com/x"
|
||
wpURLEntityOff = 4
|
||
wpURLEntityLen = 21
|
||
)
|
||
|
||
func wpURLEntities() []tg.MessageEntityClass {
|
||
return []tg.MessageEntityClass{&tg.MessageEntityURL{Offset: wpURLEntityOff, Length: wpURLEntityLen}}
|
||
}
|
||
|
||
// TestSendMessageAttachesWebPagePending 验证启用预览时,带 URL 的文本消息挂上 pending 占位
|
||
// (id==url_hash 保证与 done 解析关联),并投影 invert_media。
|
||
func TestSendMessageAttachesWebPagePending(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
r.deps.Files.(*fakeFiles).webPagePreviewOn = true
|
||
now := time.Date(2030, time.January, 2, 3, 4, 5, 0, time.UTC)
|
||
r.clock = fixedClock{now: now}
|
||
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Message: wpTestMessage,
|
||
Entities: wpURLEntities(),
|
||
RandomID: 5101,
|
||
InvertMedia: true,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
msg := newMessageFromUpdates(t, updates)
|
||
|
||
wrap, ok := msg.Media.(*tg.MessageMediaWebPage)
|
||
if !ok {
|
||
t.Fatalf("media = %T, want *tg.MessageMediaWebPage", msg.Media)
|
||
}
|
||
pending, ok := wrap.Webpage.(*tg.WebPagePending)
|
||
if !ok {
|
||
t.Fatalf("webpage = %T, want *tg.WebPagePending", wrap.Webpage)
|
||
}
|
||
wantID := domain.WebPageURLHash(wpTestURL)
|
||
if pending.ID != wantID {
|
||
t.Errorf("pending id = %d, want url_hash %d", pending.ID, wantID)
|
||
}
|
||
if url, _ := pending.GetURL(); url != wpTestURL {
|
||
t.Errorf("pending url = %q, want %q", url, wpTestURL)
|
||
}
|
||
wantDeadline := int(now.Add(webPagePendingLifetime).Unix())
|
||
if pending.Date != wantDeadline {
|
||
t.Errorf("pending date = %d, want retry deadline %d", pending.Date, wantDeadline)
|
||
}
|
||
if time.Unix(int64(pending.Date), 0).Sub(now) <= webPageResolveTimeout {
|
||
t.Errorf("pending deadline must outlive resolver timeout: deadline=%s timeout=%s", time.Unix(int64(pending.Date), 0), webPageResolveTimeout)
|
||
}
|
||
if !msg.InvertMedia {
|
||
t.Errorf("invert_media not projected onto message")
|
||
}
|
||
}
|
||
|
||
// TestSendChannelMessageUsesSameFutureWebPageDeadline 锁定频道发送也经过同一 pending
|
||
// 截止时间构造路径,避免只修私聊 echo 而频道仍被客户端立即判定过期。
|
||
func TestSendChannelMessageUsesSameFutureWebPageDeadline(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, channel := newRichChannelTestRouter(t)
|
||
r.deps.Files.(*fakeFiles).webPagePreviewOn = true
|
||
// 本测试只验证发送投影,不启动异步解析 goroutine。
|
||
r.webPageResolveSem = nil
|
||
now := time.Date(2030, time.February, 3, 4, 5, 6, 0, time.UTC)
|
||
r.clock = fixedClock{now: now}
|
||
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerChannel{ChannelID: channel.ID, AccessHash: channel.AccessHash},
|
||
Message: wpTestMessage,
|
||
Entities: wpURLEntities(),
|
||
RandomID: 5106,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("send channel message: %v", err)
|
||
}
|
||
msg := newMessageFromUpdates(t, updates)
|
||
wrap, ok := msg.Media.(*tg.MessageMediaWebPage)
|
||
if !ok {
|
||
t.Fatalf("channel media = %T, want *tg.MessageMediaWebPage", msg.Media)
|
||
}
|
||
pending, ok := wrap.Webpage.(*tg.WebPagePending)
|
||
if !ok {
|
||
t.Fatalf("channel webpage = %T, want *tg.WebPagePending", wrap.Webpage)
|
||
}
|
||
if want := int(now.Add(webPagePendingLifetime).Unix()); pending.Date != want {
|
||
t.Errorf("channel pending date = %d, want retry deadline %d", pending.Date, want)
|
||
}
|
||
}
|
||
|
||
// TestSendMessageAttachesCachedDoneCard 验证:URL 已缓存解析时,发送 echo 直接带 done 卡片
|
||
// (非 pending)——官方行为,TDesktop 据此立即渲染、不依赖异步换卡。
|
||
func TestSendMessageAttachesCachedDoneCard(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
f := r.deps.Files.(*fakeFiles)
|
||
f.webPagePreviewOn = true
|
||
f.lookupWebPageFn = func(u string) (domain.MessageWebPage, bool) {
|
||
return domain.MessageWebPage{State: domain.MessageWebPageStateDone, ID: domain.WebPageURLHash(u), URL: u, DisplayURL: "example.com", Title: "Cached"}, true
|
||
}
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash}, Message: wpTestMessage, Entities: wpURLEntities(), RandomID: 5201,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
msg := newMessageFromUpdates(t, updates)
|
||
wrap, ok := msg.Media.(*tg.MessageMediaWebPage)
|
||
if !ok {
|
||
t.Fatalf("media = %T, want *tg.MessageMediaWebPage", msg.Media)
|
||
}
|
||
page, ok := wrap.Webpage.(*tg.WebPage)
|
||
if !ok {
|
||
t.Fatalf("echo webpage = %T, want *tg.WebPage (done card directly)", wrap.Webpage)
|
||
}
|
||
if v, _ := page.GetTitle(); v != "Cached" {
|
||
t.Errorf("title = %q, want Cached", v)
|
||
}
|
||
}
|
||
|
||
// TestSendMessageNoWebpageSuppressesPreview 验证 no_webpage 抑制占位。
|
||
func TestSendMessageNoWebpageSuppressesPreview(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
r.deps.Files.(*fakeFiles).webPagePreviewOn = true
|
||
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Message: wpTestMessage,
|
||
Entities: wpURLEntities(),
|
||
RandomID: 5102,
|
||
NoWebpage: true,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
if msg := newMessageFromUpdates(t, updates); msg.Media != nil {
|
||
t.Fatalf("media = %T, want nil (no_webpage)", msg.Media)
|
||
}
|
||
}
|
||
|
||
// TestSendMessagePreviewDisabledNoPlaceholder 验证未启用预览时不挂占位(否则永久 pending)。
|
||
func TestSendMessagePreviewDisabledNoPlaceholder(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
// webPagePreviewOn 默认 false。
|
||
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Message: wpTestMessage,
|
||
Entities: wpURLEntities(),
|
||
RandomID: 5103,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
if msg := newMessageFromUpdates(t, updates); msg.Media != nil {
|
||
t.Fatalf("media = %T, want nil (preview disabled)", msg.Media)
|
||
}
|
||
}
|
||
|
||
// TestSendMediaInputWebPageAttachesPending 验证 sendMedia 的 InputMediaWebPage 经降级到
|
||
// sendMessage 后,仍从文本 URL 挂上 pending 占位(启用预览时)。
|
||
func TestSendMediaInputWebPageAttachesPending(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
r.deps.Files.(*fakeFiles).webPagePreviewOn = true
|
||
|
||
updates, err := r.onMessagesSendMedia(WithUserID(ctx, owner.ID), &tg.MessagesSendMediaRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Media: &tg.InputMediaWebPage{URL: wpTestURL},
|
||
Message: wpTestMessage,
|
||
Entities: wpURLEntities(),
|
||
RandomID: 5105,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMedia webpage: %v", err)
|
||
}
|
||
msg := newMessageFromUpdates(t, updates)
|
||
wrap, ok := msg.Media.(*tg.MessageMediaWebPage)
|
||
if !ok {
|
||
t.Fatalf("media = %T, want *tg.MessageMediaWebPage", msg.Media)
|
||
}
|
||
if _, ok := wrap.Webpage.(*tg.WebPagePending); !ok {
|
||
t.Fatalf("webpage = %T, want *tg.WebPagePending", wrap.Webpage)
|
||
}
|
||
}
|
||
|
||
// TestSendMessageHighlightsBareURL 验证服务端为不带 url 实体的消息(如 TDesktop 发的)补
|
||
// MessageEntityURL,使链接高亮——独立于预览是否启用。
|
||
func TestSendMessageHighlightsBareURL(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Message: "see https://example.com/x",
|
||
RandomID: 5301, // 无 entities,模拟 TDesktop
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
msg := newMessageFromUpdates(t, updates)
|
||
var found bool
|
||
for _, e := range msg.Entities {
|
||
if u, ok := e.(*tg.MessageEntityURL); ok && u.Offset == 4 && u.Length == 21 {
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatalf("sent message missing url highlight entity: %+v", msg.Entities)
|
||
}
|
||
}
|
||
|
||
// TestSendMessageNoURLNoPlaceholder 验证无 URL 实体时不挂占位。
|
||
func TestSendMessageNoURLNoPlaceholder(t *testing.T) {
|
||
ctx := context.Background()
|
||
r, owner, friend := newMediaTestRouter(t)
|
||
r.deps.Files.(*fakeFiles).webPagePreviewOn = true
|
||
|
||
updates, err := r.onMessagesSendMessage(WithUserID(ctx, owner.ID), &tg.MessagesSendMessageRequest{
|
||
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||
Message: "just plain text, no link",
|
||
RandomID: 5104,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("sendMessage: %v", err)
|
||
}
|
||
if msg := newMessageFromUpdates(t, updates); msg.Media != nil {
|
||
t.Fatalf("media = %T, want nil (no url)", msg.Media)
|
||
}
|
||
}
|