owpengram-server/internal/rpc/stickers_emoji_index_test.go
2026-07-28 16:22:34 +08:00

162 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"testing"
"github.com/iamxvbaba/td/clock"
"github.com/iamxvbaba/td/tg"
"go.uber.org/zap/zaptest"
"telesrv/internal/domain"
)
func emojiStickerRouter(t *testing.T) *Router {
t.Helper()
files := &fakeFiles{
docs: map[int64]domain.Document{
201: {ID: 201, AccessHash: 1, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
202: {ID: 202, AccessHash: 2, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
301: {ID: 301, AccessHash: 3, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
401: {ID: 401, AccessHash: 4, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
402: {ID: 402, AccessHash: 5, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
403: {ID: 403, AccessHash: 6, MimeType: "application/x-tgsticker", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrSticker}}},
},
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindStickers: {
{ID: 10, Hash: 1, Packs: []domain.StickerPack{
{Emoticon: "👍", DocumentIDs: []int64{201, 202}},
{Emoticon: "🔥", DocumentIDs: []int64{301}},
{Emoticon: "👋", DocumentIDs: []int64{401}},
{Emoticon: "⭐", DocumentIDs: []int64{402}},
{Emoticon: "📂", DocumentIDs: []int64{403}},
}},
{ID: 12, Hash: 2, Archived: true, Packs: []domain.StickerPack{
{Emoticon: "👍", DocumentIDs: []int64{999}}, // 归档集应被排除
}},
},
},
}
return New(Config{}, Deps{Files: files}, zaptest.NewLogger(t), clock.System)
}
func stickerDocIDs(t *testing.T, res tg.MessagesStickersClass) []int64 {
t.Helper()
full, ok := res.(*tg.MessagesStickers)
if !ok {
t.Fatalf("res = %T, want *tg.MessagesStickers", res)
}
ids := make([]int64, 0, len(full.Stickers))
for _, d := range full.Stickers {
if doc, ok := d.(*tg.Document); ok {
ids = append(ids, doc.ID)
}
}
return ids
}
// TestMessagesGetStickersByEmoji 验证 emoji→贴纸索引检索 + 归档排除 + hash notModified
// + hash=0 永不返回 NotModified崩溃安全契约+ 变体选择符归一化。
func TestMessagesGetStickersByEmoji(t *testing.T) {
r := emojiStickerRouter(t)
ctx := WithUserID(context.Background(), 1000000001)
first, err := r.onMessagesGetStickers(ctx, &tg.MessagesGetStickersRequest{Emoticon: "👍"})
if err != nil {
t.Fatalf("getStickers 👍: %v", err)
}
ids := stickerDocIDs(t, first)
if len(ids) != 2 || ids[0] != 201 || ids[1] != 202 {
t.Fatalf("👍 stickers = %v, want [201 202](归档集 999 排除)", ids)
}
hash := first.(*tg.MessagesStickers).Hash
if hash == 0 {
t.Fatal("hash must be non-zero for cache round-trips")
}
// hash 命中 → NotModified。
if again, err := r.onMessagesGetStickers(ctx, &tg.MessagesGetStickersRequest{Emoticon: "👍", Hash: hash}); err != nil {
t.Fatalf("getStickers 👍 hash: %v", err)
} else if _, ok := again.(*tg.MessagesStickersNotModified); !ok {
t.Fatalf("re-get with hash = %T, want NotModified", again)
}
// 崩溃安全hash=0 永远返回完整响应DrKLO premium 预览强转notModified 会闪退)。
if zero, err := r.onMessagesGetStickers(ctx, &tg.MessagesGetStickersRequest{Emoticon: "👍", Hash: 0}); err != nil {
t.Fatalf("getStickers 👍 hash=0: %v", err)
} else if _, ok := zero.(*tg.MessagesStickers); !ok {
t.Fatalf("hash=0 = %T, want full *tg.MessagesStickers (never NotModified)", zero)
}
// 另一个 emoji。
if ids := stickerDocIDs(t, mustStickers(t, r, ctx, "🔥", 0)); len(ids) != 1 || ids[0] != 301 {
t.Fatalf("🔥 stickers = %v, want [301]", ids)
}
// 变体选择符归一化:👍 + VS16 仍匹配。
if ids := stickerDocIDs(t, mustStickers(t, r, ctx, "👍️", 0)); len(ids) != 2 {
t.Fatalf("👍+VS16 stickers = %v, want 2 (variation selector 归一化)", ids)
}
// 未知 emoji → 空。
if ids := stickerDocIDs(t, mustStickers(t, r, ctx, "🦄", 0)); len(ids) != 0 {
t.Fatalf("unknown emoji stickers = %v, want empty", ids)
}
}
// TestMessagesGetStickersSpecialCategories 固定 TDesktop、DrKLO Android 与
// Telegram-iOS 共用的特殊类别标记。这些标记不是普通复合 emoji服务端应把它们
// 解析到对应的基础目录,同时不能拆分任意多 emoji 查询。
func TestMessagesGetStickersSpecialCategories(t *testing.T) {
r := emojiStickerRouter(t)
ctx := WithUserID(context.Background(), 1000000001)
tests := []struct {
name string
emoticon string
base string
wantDocID int64
}{
{name: "greeting", emoticon: "👋⭐️", base: "👋", wantDocID: 401},
{name: "greeting_without_vs16", emoticon: "👋⭐", base: "👋", wantDocID: 401},
{name: "premium_preview", emoticon: "⭐️⭐️", base: "⭐", wantDocID: 402},
{name: "premium_preview_mixed_vs16", emoticon: "⭐⭐️", base: "⭐", wantDocID: 402},
{name: "all_premium", emoticon: "📂⭐️", base: "📂", wantDocID: 403},
{name: "all_premium_without_vs16", emoticon: "📂⭐", base: "📂", wantDocID: 403},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
base := mustStickers(t, r, ctx, tt.base, 0).(*tg.MessagesStickers)
got := mustStickers(t, r, ctx, tt.emoticon, 0)
ids := stickerDocIDs(t, got)
if len(ids) != 1 || ids[0] != tt.wantDocID {
t.Fatalf("%q stickers = %v, want [%d]", tt.emoticon, ids, tt.wantDocID)
}
full := got.(*tg.MessagesStickers)
if full.Hash == 0 || full.Hash != base.Hash {
t.Fatalf("%q hash = %d, base %q hash = %d", tt.emoticon, full.Hash, tt.base, base.Hash)
}
if again, err := r.onMessagesGetStickers(ctx, &tg.MessagesGetStickersRequest{
Emoticon: tt.emoticon,
Hash: full.Hash,
}); err != nil {
t.Fatalf("getStickers %q hash: %v", tt.emoticon, err)
} else if _, ok := again.(*tg.MessagesStickersNotModified); !ok {
t.Fatalf("getStickers %q with matching hash = %T, want NotModified", tt.emoticon, again)
}
})
}
if ids := stickerDocIDs(t, mustStickers(t, r, ctx, "👋🔥", 0)); len(ids) != 0 {
t.Fatalf("ordinary compound emoji stickers = %v, want empty", ids)
}
}
func mustStickers(t *testing.T, r *Router, ctx context.Context, emoticon string, hash int64) tg.MessagesStickersClass {
t.Helper()
res, err := r.onMessagesGetStickers(ctx, &tg.MessagesGetStickersRequest{Emoticon: emoticon, Hash: hash})
if err != nil {
t.Fatalf("getStickers %q: %v", emoticon, err)
}
return res
}