diff --git a/internal/app/ai/defaults.go b/internal/app/ai/defaults.go index 99c80b36..9ada9e5c 100644 --- a/internal/app/ai/defaults.go +++ b/internal/app/ai/defaults.go @@ -2,16 +2,29 @@ package ai import "telesrv/internal/domain" +const ( + defaultToneEmojiFormal int64 = 4963195715414131468 + defaultToneEmojiShort int64 = 5089558399201313570 + defaultToneEmojiTribal int64 = 4906965037207257780 + defaultToneEmojiCorp int64 = 5103015433682813448 + defaultToneEmojiZen int64 = 5129871924314243582 + defaultToneEmojiBiblical int64 = 5006296094481580688 + defaultToneEmojiViking int64 = 5102866720440189629 +) + func DefaultTones() []domain.AIComposeTone { return []domain.AIComposeTone{ - defaultTone("neutral", "Polish", "Make the draft clearer, smoother, and chat-ready while keeping the original intent. Avoid returning the exact original text when a safe wording improvement is possible."), - defaultTone("formal", "Formal", "Rewrite in a more professional, polished, and polite tone. Avoid casual wording and contractions. Avoid returning the exact original text when a safe wording improvement is possible."), - defaultTone("friendly", "Friendly", "Rewrite in a warmer, conversational tone with natural phrasing. Light contractions are acceptable. Avoid returning the exact original text when a safe wording improvement is possible."), - defaultTone("concise", "Concise", "Rewrite the draft to be shorter and easier to scan while keeping the key meaning. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("formal", "Formal", defaultToneEmojiFormal, "Rewrite in a more professional, polished, and polite tone. Avoid casual wording and contractions. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("short", "Short", defaultToneEmojiShort, "Rewrite the draft to be shorter and easier to scan while keeping the key meaning. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("tribal", "Tribal", defaultToneEmojiTribal, "Rewrite in a primal, chant-like style with short punchy phrasing and playful energy. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("corp", "Corp", defaultToneEmojiCorp, "Rewrite in a business-corporate style with clear, action-oriented wording. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("zen", "Zen", defaultToneEmojiZen, "Rewrite in a calm, mindful, minimal style with gentle phrasing. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("biblical", "Biblical", defaultToneEmojiBiblical, "Rewrite in a solemn, archaic, scripture-like style while preserving the original meaning. Avoid returning the exact original text when a safe wording improvement is possible."), + defaultTone("viking", "Viking", defaultToneEmojiViking, "Rewrite in a bold, saga-like style with confident phrasing. Avoid returning the exact original text when a safe wording improvement is possible."), } } -func defaultTone(slug, title, prompt string) domain.AIComposeTone { +func defaultTone(slug, title string, emojiID int64, prompt string) domain.AIComposeTone { ex := domain.AIComposeToneExample{ From: domain.AIComposeText{Text: "Can you send me the file when you have time?"}, To: domain.AIComposeText{Text: "Could you send me the file when you have a moment?"}, @@ -20,6 +33,7 @@ func defaultTone(slug, title, prompt string) domain.AIComposeTone { Default: true, Slug: slug, Title: title, + EmojiID: emojiID, Prompt: prompt, ExampleEnglish: &ex, } diff --git a/internal/app/ai/local.go b/internal/app/ai/local.go index f5288ea8..07ed796c 100644 --- a/internal/app/ai/local.go +++ b/internal/app/ai/local.go @@ -33,7 +33,7 @@ func localTransform(text string, req domain.AIComposeRequest, tone domain.AIComp return ensureSentencePunctuation(text) case "friendly": return ensureSentencePunctuation(text) - case "concise": + case "short", "concise": return trimVerboseLead(text) default: return ensureSentencePunctuation(text) diff --git a/internal/app/ai/service_test.go b/internal/app/ai/service_test.go index eca6e4a7..affd73e8 100644 --- a/internal/app/ai/service_test.go +++ b/internal/app/ai/service_test.go @@ -91,6 +91,40 @@ func TestDefaultTonePromptsDiscourageEcho(t *testing.T) { } } +func TestDefaultTonesMatchOfficialDirectory(t *testing.T) { + want := []struct { + slug string + title string + emojiID int64 + }{ + {"formal", "Formal", defaultToneEmojiFormal}, + {"short", "Short", defaultToneEmojiShort}, + {"tribal", "Tribal", defaultToneEmojiTribal}, + {"corp", "Corp", defaultToneEmojiCorp}, + {"zen", "Zen", defaultToneEmojiZen}, + {"biblical", "Biblical", defaultToneEmojiBiblical}, + {"viking", "Viking", defaultToneEmojiViking}, + } + got := DefaultTones() + if len(got) != len(want) { + t.Fatalf("DefaultTones len = %d, want %d", len(got), len(want)) + } + for i, tone := range got { + if tone.Slug != want[i].slug || tone.Title != want[i].title { + t.Fatalf("DefaultTones[%d] = %s/%s, want %s/%s", i, tone.Slug, tone.Title, want[i].slug, want[i].title) + } + if !tone.Default { + t.Fatalf("DefaultTones[%d].Default = false, want true", i) + } + if tone.EmojiID != want[i].emojiID { + t.Fatalf("DefaultTones[%d].EmojiID = %d, want %d", i, tone.EmojiID, want[i].emojiID) + } + if tone.ExampleEnglish == nil { + t.Fatalf("DefaultTones[%d].ExampleEnglish = nil", i) + } + } +} + func TestComposeCallsProviderWithInstruction(t *testing.T) { provider := &fakeProvider{text: "Please send the file when you have a moment."} svc := NewService(memory.NewAIComposeStore(), WithProvider(provider)) diff --git a/internal/app/files/effects_seed.go b/internal/app/files/effects_seed.go index 20c60e2b..f59cdc5e 100644 --- a/internal/app/files/effects_seed.go +++ b/internal/app/files/effects_seed.go @@ -84,27 +84,27 @@ func seedEffectsCatalog(parsed seedEffectsFileJSON) ([]domain.AvailableEffect, [ docByID[d.ID] = d } required := make(map[int64]struct{}, len(docByID)) - storageID := func(sourceID int64) int64 { - if sourceID == 0 { + documentID := func(id int64) int64 { + if id == 0 { return 0 } - if _, ok := docByID[sourceID]; !ok { + if _, ok := docByID[id]; !ok { return 0 } - required[sourceID] = struct{}{} - return seedDocumentStorageID(sourceID) + required[id] = struct{}{} + return id } effects := make([]domain.AvailableEffect, 0, len(parsed.Result.Effects)) for i, ej := range parsed.Result.Effects { if ej.ID == 0 || ej.EffectStickerID == 0 { continue } - staticID := storageID(ej.StaticIconID) - stickerID := storageID(ej.EffectStickerID) + staticID := documentID(ej.StaticIconID) + stickerID := documentID(ej.EffectStickerID) if stickerID == 0 { continue } - animID := storageID(ej.EffectAnimationID) + animID := documentID(ej.EffectAnimationID) effects = append(effects, domain.AvailableEffect{ ID: ej.ID, Emoticon: ej.Emoticon, diff --git a/internal/app/files/seed.go b/internal/app/files/seed.go index 69897402..429549b8 100644 --- a/internal/app/files/seed.go +++ b/internal/app/files/seed.go @@ -322,15 +322,12 @@ func (s *Service) importStickerSetDir(ctx context.Context, setDir, systemKey str docIDs := make([]int64, 0, len(info.Result.Documents)) docs := make([]domain.Document, 0, len(info.Result.Documents)) - docIDBySource := make(map[int64]int64, len(info.Result.Documents)) for _, dj := range info.Result.Documents { - sourceID := dj.ID doc, err := s.importDocument(ctx, dj, stickersDir, index, stats) if err != nil { return err } if doc.ID != 0 { - docIDBySource[sourceID] = doc.ID docIDs = append(docIDs, doc.ID) docs = append(docs, doc) } @@ -351,12 +348,12 @@ func (s *Service) importStickerSetDir(ctx context.Context, setDir, systemKey str Masks: sj.Masks, Archived: sj.Archived, Installed: seedStickerSetInstalled(kind), - ThumbDocumentID: seedDocumentStorageID(derefInt64(sj.ThumbDocumentID)), + ThumbDocumentID: derefInt64(sj.ThumbDocumentID), Thumbs: seedStickerSetPhotoSizes(sj.Thumbs), ThumbDCID: s.dc, ThumbVersion: sj.ThumbVersion, DocumentIDs: docIDs, - Packs: seedStickerPacks(sj.Packs, info.Result.Packs, docIDBySource), + Packs: seedStickerPacks(sj.Packs, info.Result.Packs), SortOrder: order, SystemKey: systemKey, } @@ -377,10 +374,9 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi if dj.ID == 0 { return domain.Document{}, nil } - storageID := seedDocumentStorageID(dj.ID) ref, _ := hex.DecodeString(dj.FileReference) doc := domain.Document{ - ID: storageID, + ID: dj.ID, AccessHash: dj.AccessHash, FileReference: ref, Date: parseSeedDate(dj.Date), @@ -390,7 +386,7 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi Attributes: seedDocumentAttributes(dj.Attributes), } - // 主体 blob:doc: + // 主体 blob:doc: if mainPath, ok := index.main[dj.ID]; ok { data, err := os.ReadFile(mainPath) if err != nil { @@ -401,7 +397,7 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi return domain.Document{}, err } if err := s.media.PutFileBlob(ctx, domain.FileBlob{ - LocationKey: fmt.Sprintf("doc:%d", storageID), + LocationKey: fmt.Sprintf("doc:%d", doc.ID), Backend: domain.MediaBackend(s.blobs.Name()), ObjectKey: objectKey, Size: int64(len(data)), @@ -441,7 +437,7 @@ func (s *Service) importDocument(ctx context.Context, dj seedDocumentJSON, binDi } ps.Size = len(data) if err := s.media.PutFileBlob(ctx, domain.FileBlob{ - LocationKey: fmt.Sprintf("doc:%d:%s", storageID, ps.Type), + LocationKey: fmt.Sprintf("doc:%d:%s", doc.ID, ps.Type), Backend: domain.MediaBackend(s.blobs.Name()), ObjectKey: objectKey, Size: int64(len(data)), @@ -487,24 +483,9 @@ var seedThumbMarker = regexp.MustCompile(`_thumb\d+_`) const seedInlineCachedDocumentThumbMaxBytes = 32 * 1024 const seedSyntheticDocumentThumbType = "m" -// Exported Telegram resources keep their original id in filenames/JSON, but -// telesrv owns the document catalog it serves. Imported high source ids are -// normalized once at seed time so RPC/storage use one server-owned id. -const seedExternalDocumentIDOffset int64 = 4_000_000_000_000_000_000 - var seedThumbType = regexp.MustCompile(`PhotoSize_type([a-z])`) var seedSyntheticTGStickerPreviewThumbPNG = makeSeedSyntheticTGStickerPreviewThumbPNG() -func seedDocumentStorageID(sourceID int64) int64 { - if sourceID <= 0 { - return 0 - } - if sourceID > seedExternalDocumentIDOffset { - return sourceID - seedExternalDocumentIDOffset - } - return sourceID -} - func scanSeedDir(dir string) (seedDirIndex, error) { idx := seedDirIndex{main: map[int64]string{}, thumb: map[int64]map[string]string{}} entries, err := os.ReadDir(dir) @@ -826,7 +807,7 @@ func (s *Service) documentsNeedSeedRepair(ctx context.Context, ids []int64) (boo return false, nil } -func seedStickerPacks(setPacks, resultPacks []seedStickerPackJSON, docIDBySource map[int64]int64) []domain.StickerPack { +func seedStickerPacks(setPacks, resultPacks []seedStickerPackJSON) []domain.StickerPack { packs := setPacks if len(packs) == 0 { packs = resultPacks @@ -834,12 +815,8 @@ func seedStickerPacks(setPacks, resultPacks []seedStickerPackJSON, docIDBySource out := make([]domain.StickerPack, 0, len(packs)) for _, p := range packs { documents := make([]int64, 0, len(p.Documents)) - for _, sourceID := range p.Documents { - if id, ok := docIDBySource[sourceID]; ok { - documents = append(documents, id) - continue - } - if id := seedDocumentStorageID(sourceID); id != 0 { + for _, id := range p.Documents { + if id > 0 { documents = append(documents, id) } } diff --git a/internal/app/files/seed_state.go b/internal/app/files/seed_state.go index 905d0c6c..c674f06a 100644 --- a/internal/app/files/seed_state.go +++ b/internal/app/files/seed_state.go @@ -79,13 +79,12 @@ func writeSeedDirFingerprint(h io.Writer, dir string) error { } func seedDocumentJSONLocationKeys(dj seedDocumentJSON, index seedDirIndex) []string { - storageID := seedDocumentStorageID(dj.ID) - if storageID == 0 { + if dj.ID == 0 { return nil } keys := make([]string, 0, 1+len(dj.Thumbs)) if _, ok := index.main[dj.ID]; ok { - keys = append(keys, fmt.Sprintf("doc:%d", storageID)) + keys = append(keys, fmt.Sprintf("doc:%d", dj.ID)) } for _, tj := range dj.Thumbs { ps, downloadable := seedPhotoSize(tj) @@ -93,11 +92,11 @@ func seedDocumentJSONLocationKeys(dj seedDocumentJSON, index seedDirIndex) []str continue } if _, ok := index.thumb[dj.ID][ps.Type]; ok { - keys = append(keys, fmt.Sprintf("doc:%d:%s", storageID, ps.Type)) + keys = append(keys, fmt.Sprintf("doc:%d:%s", dj.ID, ps.Type)) } } if seedDocumentJSONNeedsSyntheticTGStickerPreviewThumb(dj) { - keys = append(keys, fmt.Sprintf("doc:%d:%s", storageID, seedSyntheticDocumentThumbType)) + keys = append(keys, fmt.Sprintf("doc:%d:%s", dj.ID, seedSyntheticDocumentThumbType)) } return keys } @@ -112,13 +111,12 @@ func (s *Service) seedDocumentJSONsReady(ctx context.Context, docs []seedDocumen locationKeys := make([]string, 0, len(docs)) seenLocationKeys := make(map[string]struct{}, len(docs)) for _, dj := range docs { - storageID := seedDocumentStorageID(dj.ID) - if storageID == 0 { + if dj.ID == 0 { continue } - if _, ok := expected[storageID]; !ok { - expected[storageID] = dj - ids = append(ids, storageID) + if _, ok := expected[dj.ID]; !ok { + expected[dj.ID] = dj + ids = append(ids, dj.ID) } for _, key := range seedDocumentJSONLocationKeys(dj, index) { if _, ok := seenLocationKeys[key]; ok { diff --git a/internal/app/files/seed_test.go b/internal/app/files/seed_test.go index 16ca31ce..1fe801fb 100644 --- a/internal/app/files/seed_test.go +++ b/internal/app/files/seed_test.go @@ -642,9 +642,6 @@ func TestSeedMediaFromRealExport(t *testing.T) { if d, ok, _ := media.GetDocument(context.Background(), first.SelectAnimationID); !ok { t.Error("reaction select animation document missing") } else { - if d.ID > seedExternalDocumentIDOffset { - t.Errorf("reaction document kept external source id: %d", d.ID) - } if d.DCID != 2 { t.Errorf("document dc_id not rewritten: %d", d.DCID) } @@ -678,9 +675,6 @@ func TestSeedMediaFromRealExport(t *testing.T) { if doc, ok, _ := media.GetDocument(context.Background(), sample.DocumentIDs[0]); !ok { t.Fatalf("sample sticker document %d missing", sample.DocumentIDs[0]) } else { - if doc.ID > seedExternalDocumentIDOffset { - t.Fatalf("sample sticker kept external source id: %d", doc.ID) - } thumb, ok := findCachedThumb(doc.Thumbs) if !ok { t.Fatalf("sample sticker document thumbs are not inline cached: %+v", doc.Thumbs) @@ -729,14 +723,37 @@ func writeEffectsSeed(t *testing.T, seedDir string, sourceID int64) { } } -func TestSeedDocumentStorageIDNormalizesExternalIDs(t *testing.T) { +func TestSeedDocumentIDsAreImportedVerbatim(t *testing.T) { + ctx := context.Background() + seedDir := t.TempDir() const sourceID int64 = 5382305375846410902 - const want int64 = 1382305375846410902 - if got := seedDocumentStorageID(sourceID); got != want { - t.Fatalf("seedDocumentStorageID(%d) = %d, want %d", sourceID, got, want) + writeStatusPackWithoutThumbSeed(t, seedDir, sourceID, 17) + + media := newFakeMediaStore() + blobs, err := NewLocalFS(t.TempDir()) + if err != nil { + t.Fatalf("local fs: %v", err) } - if got := seedDocumentStorageID(2222222); got != 2222222 { - t.Fatalf("small server id changed: %d", got) + svc := NewService(media, blobs, 2) + if _, err := svc.SeedMedia(ctx, seedDir, 0); err != nil { + t.Fatalf("seed media: %v", err) + } + set, ok, err := media.GetStickerSetByShortName(ctx, "StatusPack") + if err != nil || !ok { + t.Fatalf("StatusPack ok=%v err=%v", ok, err) + } + if len(set.DocumentIDs) != 1 || set.DocumentIDs[0] != sourceID { + t.Fatalf("set document ids = %+v, want [%d]", set.DocumentIDs, sourceID) + } + doc, ok, err := media.GetDocument(ctx, sourceID) + if err != nil || !ok { + t.Fatalf("GetDocument(%d) ok=%v err=%v", sourceID, ok, err) + } + if doc.ID != sourceID { + t.Fatalf("document id = %d, want %d", doc.ID, sourceID) + } + if _, ok, err := media.GetFileBlob(ctx, fmt.Sprintf("doc:%d", sourceID)); err != nil || !ok { + t.Fatalf("main blob for raw id ok=%v err=%v", ok, err) } } diff --git a/internal/mtprotoedge/encrypted.go b/internal/mtprotoedge/encrypted.go index 8efd4236..2b771a49 100644 --- a/internal/mtprotoedge/encrypted.go +++ b/internal/mtprotoedge/encrypted.go @@ -455,12 +455,17 @@ func (s *Server) enqueueRPC(ctx context.Context, c *Conn, msgID int64, body []by // body 已是 enqueueRPC 入参的独立副本(dispatch 里 b.Copy()),且每个任务只 run 一次, // 无需再 append 拷贝;直接复用,省掉一份 inbound 在途内存。 if err := s.handleRPC(taskCtx, c, msgID, &bin.Buffer{Buf: body}); err != nil { - s.log.Info("RPC async handler failed", + fields := []zap.Field{ zap.Int64("msg_id", msgID), zap.String("auth_key_id", hex.EncodeToString(c.authKeyID[:])), zap.Int64("session_id", c.sessionID), zap.Error(err), - ) + } + if isClientDisconnect(err) { + s.log.Debug("RPC async handler canceled", fields...) + } else { + s.log.Info("RPC async handler failed", fields...) + } return err } return nil @@ -518,6 +523,13 @@ func (s *Server) handleRPC(ctx context.Context, c *Conn, msgID int64, b *bin.Buf } fields = dbtrace.AppendZapFields(fields, "", dbStats.Snapshot()) + if ctxErr := ctx.Err(); ctxErr != nil && err != nil { + // A canceled request context means the result cannot be delivered. Do not + // turn cancellation-derived handler errors into cacheable rpc_error replies. + s.log.Info("RPC canceled", append(fields, zap.NamedError("dispatch_error", err), zap.NamedError("context_error", ctxErr))...) + return ctxErr + } + if err != nil { var rpcErr *tgerr.Error if errors.As(err, &rpcErr) { diff --git a/internal/mtprotoedge/rpc_test.go b/internal/mtprotoedge/rpc_test.go index 24cf00db..8525dac2 100644 --- a/internal/mtprotoedge/rpc_test.go +++ b/internal/mtprotoedge/rpc_test.go @@ -14,6 +14,7 @@ import ( "github.com/gotd/td/mt" "github.com/gotd/td/proto" "github.com/gotd/td/tg" + "github.com/gotd/td/tgerr" "github.com/gotd/td/transport" "telesrv/internal/rpc" @@ -141,6 +142,41 @@ func TestDuplicateRPCResultAcrossReconnectUsesSessionCache(t *testing.T) { } } +func TestCanceledRPCErrorIsNotCachedAcrossReconnect(t *testing.T) { + const dc = 2 + handler := &canceledInternalRPC{ + firstDone: make(chan struct{}), + } + addr, pub, _ := startTestServer(t, Options{DC: dc, RPC: handler}) + conn, auth, cipher := dialHandshake(t, addr, dc, pub) + + clientMsgID := proto.NewMessageIDGen(time.Now) + reqMsgID := clientMsgID.New(proto.MessageFromClient) + sendEncrypted(t, conn, cipher, auth, reqMsgID, &tg.HelpGetConfigRequest{}) + + _ = conn.Close() + select { + case <-handler.firstDone: + case <-time.After(5 * time.Second): + t.Fatal("timed out waiting for canceled first rpc") + } + + replayConn := dialTransportOnly(t, addr) + sendEncrypted(t, replayConn, cipher, auth, reqMsgID, &tg.HelpGetConfigRequest{}) + + result := readRPCResultForRequest(t, replayConn, cipher, auth.AuthKey, reqMsgID) + var cfg tg.Config + if err := cfg.Decode(&bin.Buffer{Buf: result.Result}); err != nil { + t.Fatalf("decode replay config: %v", err) + } + if cfg.ThisDC != dc { + t.Fatalf("replay config.ThisDC = %d, want %d", cfg.ThisDC, dc) + } + if calls := handler.calls.Load(); calls != 2 { + t.Fatalf("handler calls = %d, want 2 (canceled first result must not be cached)", calls) + } +} + type countingConfigRPC struct { calls atomic.Int32 } @@ -172,6 +208,22 @@ func (h *blockingRPC) Dispatch(ctx context.Context, _ [8]byte, _ int64, _ *bin.B func (h *blockingRPC) NegotiatedLayer([8]byte, int64) (int, bool) { return 227, true } +type canceledInternalRPC struct { + calls atomic.Int32 + firstDone chan struct{} +} + +func (h *canceledInternalRPC) Dispatch(ctx context.Context, _ [8]byte, _ int64, _ *bin.Buffer) (bin.Encoder, error) { + if h.calls.Add(1) == 1 { + <-ctx.Done() + close(h.firstDone) + return nil, tgerr.New(500, "INTERNAL_SERVER_ERROR") + } + return &tg.Config{ThisDC: 2}, nil +} + +func (h *canceledInternalRPC) NegotiatedLayer([8]byte, int64) (int, bool) { return 227, true } + func readRPCResultForRequest(t *testing.T, conn transport.Conn, cipher crypto.Cipher, key crypto.AuthKey, reqMsgID int64) proto.Result { t.Helper() for i := 0; i < 12; i++ { diff --git a/internal/rpc/aicompose_rpc_test.go b/internal/rpc/aicompose_rpc_test.go index 3f3af304..babd629a 100644 --- a/internal/rpc/aicompose_rpc_test.go +++ b/internal/rpc/aicompose_rpc_test.go @@ -36,6 +36,33 @@ func TestAIComposeGetTonesReturnsDefaultsAndNotModified(t *testing.T) { if tones.Hash == 0 || len(tones.Tones) == 0 { t.Fatalf("getTones hash/tones = %d/%d, want non-empty", tones.Hash, len(tones.Tones)) } + want := []struct { + slug string + emojiID int64 + }{ + {"formal", 4963195715414131468}, + {"short", 5089558399201313570}, + {"tribal", 4906965037207257780}, + {"corp", 5103015433682813448}, + {"zen", 5129871924314243582}, + {"biblical", 5006296094481580688}, + {"viking", 5102866720440189629}, + } + if len(tones.Tones) < len(want) { + t.Fatalf("getTones defaults = %d, want at least %d", len(tones.Tones), len(want)) + } + for i, expected := range want { + tone, ok := tones.Tones[i].(*tg.AiComposeToneDefault) + if !ok { + t.Fatalf("getTones tones[%d] = %T, want *tg.AiComposeToneDefault", i, tones.Tones[i]) + } + if tone.Tone != expected.slug { + t.Fatalf("getTones tones[%d].Tone = %q, want %q", i, tone.Tone, expected.slug) + } + if tone.EmojiID != expected.emojiID { + t.Fatalf("getTones tones[%d].EmojiID = %d, want %d", i, tone.EmojiID, expected.emojiID) + } + } again, err := r.onAicomposeGetTones(ctx, tones.Hash) if err != nil { t.Fatalf("getTones(hash): %v", err) diff --git a/internal/rpc/upload_test.go b/internal/rpc/upload_test.go index ef6fe62c..3d3fa171 100644 --- a/internal/rpc/upload_test.go +++ b/internal/rpc/upload_test.go @@ -50,13 +50,13 @@ func TestUploadGetFileRejectsInvalidRanges(t *testing.T) { func TestFileLocationKeyUsesDocumentID(t *testing.T) { key, ok := fileLocationKey(&tg.InputDocumentFileLocation{ - ID: 1382305375846410902, + ID: 5382305375846410902, ThumbSize: "m", }) if !ok { t.Fatal("fileLocationKey returned !ok") } - const want = "doc:1382305375846410902:m" + const want = "doc:5382305375846410902:m" if key != want { t.Fatalf("key = %q, want %q", key, want) }