fix: sync latest telesrv fixes
This commit is contained in:
parent
b7269b135f
commit
b1f74185f0
11 changed files with 203 additions and 72 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:<server-owned-id>
|
||||
// 主体 blob:doc:<document-id>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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++ {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue