chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -4,7 +4,9 @@ import (
"context"
"testing"
"github.com/gotd/td/clock"
"github.com/gotd/td/tg"
"go.uber.org/zap/zaptest"
"telesrv/internal/domain"
)
@ -74,6 +76,297 @@ func TestMessagesGetAllStickersUsesTDesktopHashForNotModified(t *testing.T) {
}
}
func TestAccountGetDefaultEmojiStatusesServesSynthesizedSet(t *testing.T) {
ctx := context.Background()
files := &fakeFiles{
docs: map[int64]domain.Document{
101: {ID: 101, AccessHash: 1},
102: {ID: 102, AccessHash: 2},
},
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindSystem: {
{
ID: 77,
ShortName: "TelesrvDefaultStatuses",
Kind: domain.StickerSetKindSystem,
SystemKey: domain.StickerSetSystemKeyEmojiDefaultStatuses,
Emojis: true,
Count: 2,
Hash: 12345,
DocumentIDs: []int64{101, 102},
},
},
},
}
r := &Router{deps: Deps{Files: files}}
first, err := r.onAccountGetDefaultEmojiStatuses(ctx, 0)
if err != nil {
t.Fatalf("first getDefaultEmojiStatuses: %v", err)
}
full, ok := first.(*tg.AccountEmojiStatuses)
if !ok {
t.Fatalf("first getDefaultEmojiStatuses = %T, want *tg.AccountEmojiStatuses", first)
}
if len(full.Statuses) != 2 {
t.Fatalf("statuses = %d, want 2", len(full.Statuses))
}
status, ok := full.Statuses[0].(*tg.EmojiStatus)
if !ok || status.DocumentID != 101 {
t.Fatalf("statuses[0] = %#v, want EmojiStatus document 101", full.Statuses[0])
}
if full.Hash == 0 {
t.Fatal("hash must be non-zero for cache round-trips")
}
second, err := r.onAccountGetDefaultEmojiStatuses(ctx, full.Hash)
if err != nil {
t.Fatalf("second getDefaultEmojiStatuses: %v", err)
}
if _, ok := second.(*tg.AccountEmojiStatusesNotModified); !ok {
t.Fatalf("second getDefaultEmojiStatuses = %T, want notModified", second)
}
// getStickerSet(inputStickerSetEmojiDefaultStatuses) 走同一系统集。
resolved, err := r.onMessagesGetStickerSet(ctx, &tg.MessagesGetStickerSetRequest{
Stickerset: &tg.InputStickerSetEmojiDefaultStatuses{},
})
if err != nil {
t.Fatalf("getStickerSet default statuses: %v", err)
}
setFull, ok := resolved.(*tg.MessagesStickerSet)
if !ok {
t.Fatalf("getStickerSet = %T, want *tg.MessagesStickerSet", resolved)
}
if setFull.Set.ID != 77 || len(setFull.Documents) != 2 {
t.Fatalf("resolved set = %d with %d docs, want 77 with 2", setFull.Set.ID, len(setFull.Documents))
}
}
func TestAccountGetDefaultEmojiStatusesFallsBackWhenUnseeded(t *testing.T) {
ctx := context.Background()
r := &Router{deps: Deps{Files: &fakeFiles{}}}
res, err := r.onAccountGetDefaultEmojiStatuses(ctx, 0)
if err != nil {
t.Fatalf("getDefaultEmojiStatuses without seed: %v", err)
}
if _, ok := res.(*tg.AccountEmojiStatusesNotModified); !ok {
t.Fatalf("unseeded result = %T, want compat notModified stub", res)
}
}
func TestMessagesGetStickerSetAndroidPlaceholderUsesSeededSet(t *testing.T) {
ctx := context.Background()
docs := make(map[int64]domain.Document)
documentIDs := make([]int64, 0, androidPlaceholderStickerMinDocuments)
for i := 0; i < androidPlaceholderStickerMinDocuments; i++ {
id := int64(100 + i)
documentIDs = append(documentIDs, id)
docs[id] = domain.Document{ID: id, AccessHash: id + 1000, DCID: 2}
}
files := &fakeFiles{
docs: docs,
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindSystem: {
{
ID: 77,
AccessHash: 7700,
ShortName: "AnimatedEmojies",
Title: "Animated Emoji",
Kind: domain.StickerSetKindSystem,
SystemKey: "animated_emoji",
Emojis: true,
Count: len(documentIDs),
Hash: 12345,
DocumentIDs: documentIDs,
},
},
},
}
r := &Router{deps: Deps{Files: files}}
res, err := r.onMessagesGetStickerSet(ctx, &tg.MessagesGetStickerSetRequest{
Stickerset: &tg.InputStickerSetShortName{ShortName: "tg_placeholders_android"},
})
if err != nil {
t.Fatalf("getStickerSet placeholder: %v", err)
}
full, ok := res.(*tg.MessagesStickerSet)
if !ok {
t.Fatalf("getStickerSet placeholder = %T, want *tg.MessagesStickerSet", res)
}
if full.Set.ID != 77 {
t.Fatalf("placeholder fallback set id = %d, want 77", full.Set.ID)
}
if len(full.Documents) < androidPlaceholderStickerMinDocuments {
t.Fatalf("placeholder fallback documents = %d, want >= %d", len(full.Documents), androidPlaceholderStickerMinDocuments)
}
}
func TestMessagesGetStickerSetAndroidPlaceholderFallsBackToEmptyWithoutSeed(t *testing.T) {
ctx := context.Background()
r := &Router{deps: Deps{Files: &fakeFiles{}}}
res, err := r.onMessagesGetStickerSet(ctx, &tg.MessagesGetStickerSetRequest{
Stickerset: &tg.InputStickerSetShortName{ShortName: "tg_placeholders_android"},
})
if err != nil {
t.Fatalf("getStickerSet placeholder without seed: %v", err)
}
full, ok := res.(*tg.MessagesStickerSet)
if !ok {
t.Fatalf("getStickerSet placeholder without seed = %T, want *tg.MessagesStickerSet", res)
}
if len(full.Documents) != 0 {
t.Fatalf("placeholder without seed documents = %d, want empty compat set", len(full.Documents))
}
}
func TestMessagesGetMaskStickersUsesMaskCatalog(t *testing.T) {
ctx := context.Background()
files := &fakeFiles{
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindStickers: {
{ID: 10, AccessHash: 100, ShortName: "regular", Title: "Regular", Count: 1, Hash: 123, Installed: true},
},
domain.StickerSetKindMasks: {
{ID: 20, AccessHash: 200, ShortName: "masks", Title: "Masks", Count: 1, Hash: 789, Installed: true},
},
},
}
r := &Router{deps: Deps{Files: files}}
first, err := r.onMessagesGetMaskStickers(ctx, 0)
if err != nil {
t.Fatalf("first getMaskStickers: %v", err)
}
full, ok := first.(*tg.MessagesAllStickers)
if !ok {
t.Fatalf("first getMaskStickers = %T, want *tg.MessagesAllStickers", first)
}
if len(full.Sets) != 1 || full.Sets[0].ID != 20 {
t.Fatalf("mask sets = %+v, want only mask set 20", full.Sets)
}
second, err := r.onMessagesGetMaskStickers(ctx, full.Hash)
if err != nil {
t.Fatalf("second getMaskStickers: %v", err)
}
if _, ok := second.(*tg.MessagesAllStickersNotModified); !ok {
t.Fatalf("second getMaskStickers = %T, want *tg.MessagesAllStickersNotModified", second)
}
}
func TestMessagesGetFeaturedStickersSurfacesSeededSets(t *testing.T) {
ctx := context.Background()
files := &fakeFiles{
docs: map[int64]domain.Document{
201: {ID: 201, AccessHash: 1, MimeType: "application/x-tgsticker"},
202: {ID: 202, AccessHash: 2, MimeType: "application/x-tgsticker"},
301: {ID: 301, AccessHash: 3, MimeType: "application/x-tgsticker"},
},
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindStickers: {
{ID: 10, AccessHash: 100, ShortName: "one", Title: "One", Count: 2, Hash: 123, DocumentIDs: []int64{201, 202}},
{ID: 11, AccessHash: 110, ShortName: "two", Title: "Two", Count: 1, Hash: 456, DocumentIDs: []int64{301}},
{ID: 12, AccessHash: 120, ShortName: "arch", Title: "Arch", Hash: 789, Archived: true, DocumentIDs: []int64{301}},
},
domain.StickerSetKindEmoji: {
{ID: 20, AccessHash: 200, ShortName: "emo", Title: "Emo", Count: 1, Hash: 999, Emojis: true, DocumentIDs: []int64{201}},
},
},
}
r := &Router{deps: Deps{Files: files}}
first, err := r.onMessagesGetFeaturedStickers(ctx, 0)
if err != nil {
t.Fatalf("first getFeaturedStickers: %v", err)
}
full, ok := first.(*tg.MessagesFeaturedStickers)
if !ok {
t.Fatalf("first getFeaturedStickers = %T, want *tg.MessagesFeaturedStickers", first)
}
if full.Count != 2 || len(full.Sets) != 2 {
t.Fatalf("featured = count %d / %d sets, want 2/2 (archived excluded)", full.Count, len(full.Sets))
}
covered, ok := full.Sets[0].(*tg.StickerSetMultiCovered)
if !ok {
t.Fatalf("featured set[0] = %T, want *tg.StickerSetMultiCovered", full.Sets[0])
}
if covered.Set.ID != 10 || len(covered.Covers) != 2 {
t.Fatalf("featured set[0] = id %d with %d covers, want 10 with 2", covered.Set.ID, len(covered.Covers))
}
if full.Hash == 0 {
t.Fatal("featured hash must be non-zero for cache round-trips")
}
second, err := r.onMessagesGetFeaturedStickers(ctx, full.Hash)
if err != nil {
t.Fatalf("second getFeaturedStickers: %v", err)
}
if _, ok := second.(*tg.MessagesFeaturedStickersNotModified); !ok {
t.Fatalf("second getFeaturedStickers = %T, want NotModified", second)
}
// emoji featured 走 emoji 集。
emoji, err := r.onMessagesGetFeaturedEmojiStickers(ctx, 0)
if err != nil {
t.Fatalf("getFeaturedEmojiStickers: %v", err)
}
emojiFull, ok := emoji.(*tg.MessagesFeaturedStickers)
if !ok || emojiFull.Count != 1 {
t.Fatalf("featured emoji = %T count %v, want 1 emoji set", emoji, emojiFull)
}
}
// countingStickerFiles 包 *fakeFiles 计数 ListStickerSets,验证目录缓存短路。
type countingStickerFiles struct {
*fakeFiles
listCalls int
}
func (f *countingStickerFiles) ListStickerSets(ctx context.Context, kind domain.StickerSetKind) ([]domain.StickerSet, error) {
f.listCalls++
return f.fakeFiles.ListStickerSets(ctx, kind)
}
// TestStickerCatalogCacheShortCircuitsListStickerSets 回归 P2-5:getAllStickers/
// getFeaturedStickers 经目录缓存——同 kind 重复请求只 ListStickerSets 一次(TTL 内 0 PG)。
func TestStickerCatalogCacheShortCircuitsListStickerSets(t *testing.T) {
ctx := context.Background()
files := &countingStickerFiles{fakeFiles: &fakeFiles{
docs: map[int64]domain.Document{201: {ID: 201, AccessHash: 1}},
sets: map[domain.StickerSetKind][]domain.StickerSet{
domain.StickerSetKindStickers: {{ID: 10, AccessHash: 100, ShortName: "one", Count: 1, Hash: 123, DocumentIDs: []int64{201}}},
},
}}
r := New(Config{}, Deps{Files: files}, zaptest.NewLogger(t), clock.System)
for i := 0; i < 3; i++ {
if _, err := r.onMessagesGetAllStickers(ctx, 0); err != nil {
t.Fatalf("getAllStickers %d: %v", i, err)
}
}
if _, err := r.onMessagesGetFeaturedStickers(ctx, 0); err != nil {
t.Fatalf("getFeaturedStickers: %v", err)
}
if files.listCalls != 1 {
t.Fatalf("ListStickerSets(stickers) calls = %d, want 1 (目录缓存应短路)", files.listCalls)
}
}
func TestMessagesGetFeaturedStickersEmptyWithoutSeed(t *testing.T) {
ctx := context.Background()
r := &Router{deps: Deps{Files: &fakeFiles{}}}
res, err := r.onMessagesGetFeaturedStickers(ctx, 0)
if err != nil {
t.Fatalf("getFeaturedStickers without seed: %v", err)
}
full, ok := res.(*tg.MessagesFeaturedStickers)
if !ok || full.Count != 0 || len(full.Sets) != 0 {
t.Fatalf("unseeded featured = %#v, want empty", res)
}
}
func TestMessagesGetAvailableReactionsNotModified(t *testing.T) {
ctx := context.Background()
reactions := []domain.AvailableReaction{