fix: sync WebA labels and forward fixes
This commit is contained in:
parent
7bb7e11603
commit
4d3bbeabd8
11 changed files with 422 additions and 10 deletions
66
data/langpack/weba/weba_en_v12000002.strings
Normal file
66
data/langpack/weba/weba_en_v12000002.strings
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
"AccDescrMentionDown" = "Go to next mention";
|
||||||
|
"AccDescrPageDown" = "Go to bottom";
|
||||||
|
"AccDescrPollVoteDown" = "Go to next unread poll vote";
|
||||||
|
"AccDescrReactionMentionDown" = "Go to next unread reactions";
|
||||||
|
"ArchivedChats" = "Archived Chats";
|
||||||
|
"MenuArchivedChats" = "Archived Chats";
|
||||||
|
"NewMessageTitle" = "New Message";
|
||||||
|
"NewChannel" = "New Channel";
|
||||||
|
"NewGroup" = "New Group";
|
||||||
|
"MessageUnsupported" = "Unsupported message";
|
||||||
|
|
||||||
|
"AttachTodo" = "Checklist";
|
||||||
|
"TitleNewToDoList" = "New Checklist";
|
||||||
|
"TitleEditToDoList" = "Edit Checklist";
|
||||||
|
"TitleAppendToDoList" = "Add Task";
|
||||||
|
"TitleToDoList" = "Checklist";
|
||||||
|
"TitleTask" = "Task";
|
||||||
|
"TitleAddTask" = "Add a task";
|
||||||
|
"AllowOthersAddTasks" = "Allow Others to Add Tasks";
|
||||||
|
"AriaToDoCancel" = "Cancel checklist creation";
|
||||||
|
"TitleGroupToDoList" = "Group Checklist";
|
||||||
|
"TitleUserToDoList" = "{peer}'s Checklist";
|
||||||
|
"TitleYourToDoList" = "Your Checklist";
|
||||||
|
"DescriptionCompletedToDoTasks" = "{number} of {count} completed";
|
||||||
|
"MessageActionTodoCompletionsAsDone" = "{peer} marked \"{task}\" as done";
|
||||||
|
"MessageActionTodoCompletionsAsDoneYou" = "You marked \"{task}\" as done";
|
||||||
|
"MessageActionTodoCompletionsAsDoneMultiple" = "{peer} marked {tasks} as done";
|
||||||
|
"MessageActionTodoCompletionsAsDoneMultipleYou" = "You marked {tasks} as done";
|
||||||
|
"MessageActionTodoCompletionsAsNotDone" = "{peer} marked \"{task}\" as not done";
|
||||||
|
"MessageActionTodoCompletionsAsNotDoneYou" = "You marked \"{task}\" as not done";
|
||||||
|
"MessageActionTodoCompletionsAsNotDoneMultiple" = "{peer} marked {tasks} as not done";
|
||||||
|
"MessageActionTodoCompletionsAsNotDoneMultipleYou" = "You marked {tasks} as not done";
|
||||||
|
"MessageActionTodoTaskCount#one" = "{count} task";
|
||||||
|
"MessageActionTodoTaskCount#other" = "{count} tasks";
|
||||||
|
"MenuButtonAppendTodoList" = "Add a Task";
|
||||||
|
"MessageActionAppendTodo" = "{peer} added a new task \"{task}\" to {list}";
|
||||||
|
"MessageActionAppendTodoYou" = "You added a new task \"{task}\" to {list}";
|
||||||
|
"MessageActionAppendTodoMultiple" = "{peer} added {tasks} to {list}";
|
||||||
|
"MessageActionAppendTodoMultipleYou" = "You added {tasks} to {list}";
|
||||||
|
"SubscribeToTelegramPremiumForToggleTask" = "Subscribe to **Telegram Premium** to toggle tasks";
|
||||||
|
"SubscribeToTelegramPremiumForCreateToDo" = "Subscribe to **Telegram Premium** to create Checklists";
|
||||||
|
"SubscribeToTelegramPremiumForAppendToDo" = "Subscribe to **Telegram Premium** to add tasks";
|
||||||
|
"HintTodoListTasksCount2#one" = "You can add {count} more task";
|
||||||
|
"HintTodoListTasksCount2#other" = "You can add {count} more tasks";
|
||||||
|
"ToDoListErrorChooseTitle" = "Please enter a title.";
|
||||||
|
"ToDoListErrorChooseTasks" = "Please enter at least one task.";
|
||||||
|
"PremiumPreviewTodo" = "Checklists";
|
||||||
|
"PremiumPreviewTodoDescription" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
|
||||||
|
|
||||||
|
"Weekday.ShortSunday" = "Sun";
|
||||||
|
"Weekday.ShortMonday" = "Mon";
|
||||||
|
"Weekday.ShortTuesday" = "Tue";
|
||||||
|
"Weekday.ShortWednesday" = "Wed";
|
||||||
|
"Weekday.ShortThursday" = "Thu";
|
||||||
|
"Weekday.ShortFriday" = "Fri";
|
||||||
|
"Weekday.ShortSaturday" = "Sat";
|
||||||
|
|
||||||
|
"Weekday.Sunday" = "Sunday";
|
||||||
|
"Weekday.Monday" = "Monday";
|
||||||
|
"Weekday.Tuesday" = "Tuesday";
|
||||||
|
"Weekday.Wednesday" = "Wednesday";
|
||||||
|
"Weekday.Thursday" = "Thursday";
|
||||||
|
"Weekday.Friday" = "Friday";
|
||||||
|
"Weekday.Saturday" = "Saturday";
|
||||||
|
"Weekday.Today" = "Today";
|
||||||
|
"Weekday.Yesterday" = "Yesterday";
|
||||||
|
|
@ -2,6 +2,7 @@ package langpack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"telesrv/internal/domain"
|
"telesrv/internal/domain"
|
||||||
"telesrv/internal/store"
|
"telesrv/internal/store"
|
||||||
|
|
@ -24,18 +25,41 @@ func (s *Service) GetLangPack(ctx context.Context, langPack, langCode string) (d
|
||||||
|
|
||||||
// GetDifference 返回从 fromVersion 到当前版本的语言包差异。
|
// GetDifference 返回从 fromVersion 到当前版本的语言包差异。
|
||||||
func (s *Service) GetDifference(ctx context.Context, langPack, langCode string, fromVersion int) (domain.LangPack, error) {
|
func (s *Service) GetDifference(ctx context.Context, langPack, langCode string, fromVersion int) (domain.LangPack, error) {
|
||||||
|
packName := normalizePack(langPack)
|
||||||
|
code := normalizeCode(langCode)
|
||||||
if s == nil || s.packs == nil {
|
if s == nil || s.packs == nil {
|
||||||
return domain.LangPack{LangPack: langPack, LangCode: langCode, FromVersion: fromVersion}, nil
|
return domain.LangPack{LangPack: packName, LangCode: code, FromVersion: fromVersion}, nil
|
||||||
}
|
}
|
||||||
return s.packs.GetPack(ctx, normalizePack(langPack), normalizeCode(langCode), fromVersion)
|
pack, err := s.packs.GetPack(ctx, packName, code, fromVersion)
|
||||||
|
if err != nil {
|
||||||
|
return domain.LangPack{}, err
|
||||||
|
}
|
||||||
|
return s.overlayWebAStrings(ctx, pack, packName, code, fromVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStrings 返回指定 key 的语言包字符串。
|
// GetStrings 返回指定 key 的语言包字符串。
|
||||||
func (s *Service) GetStrings(ctx context.Context, langPack, langCode string, keys []string) (domain.LangPack, error) {
|
func (s *Service) GetStrings(ctx context.Context, langPack, langCode string, keys []string) (domain.LangPack, error) {
|
||||||
|
packName := normalizePack(langPack)
|
||||||
|
code := normalizeCode(langCode)
|
||||||
if s == nil || s.packs == nil {
|
if s == nil || s.packs == nil {
|
||||||
return domain.LangPack{LangPack: langPack, LangCode: langCode}, nil
|
return domain.LangPack{LangPack: packName, LangCode: code}, nil
|
||||||
}
|
}
|
||||||
return s.packs.GetStrings(ctx, normalizePack(langPack), normalizeCode(langCode), keys)
|
pack, err := s.packs.GetStrings(ctx, packName, code, keys)
|
||||||
|
if err != nil {
|
||||||
|
return domain.LangPack{}, err
|
||||||
|
}
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return s.overlayWebAStrings(ctx, pack, packName, code, 0)
|
||||||
|
}
|
||||||
|
missing := missingLangPackKeys(keys, pack.Strings)
|
||||||
|
if len(missing) == 0 || !shouldOverlayWebA(packName) {
|
||||||
|
return pack, nil
|
||||||
|
}
|
||||||
|
overlay, err := s.packs.GetStrings(ctx, "weba", code, missing)
|
||||||
|
if err != nil {
|
||||||
|
return domain.LangPack{}, err
|
||||||
|
}
|
||||||
|
return mergeMissingLangPackStrings(pack, overlay), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizePack(langPack string) string {
|
func normalizePack(langPack string) string {
|
||||||
|
|
@ -46,8 +70,76 @@ func normalizePack(langPack string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeCode(langCode string) string {
|
func normalizeCode(langCode string) string {
|
||||||
if langCode == "" {
|
code := strings.ToLower(strings.TrimSpace(langCode))
|
||||||
|
if code == "" {
|
||||||
return "en"
|
return "en"
|
||||||
}
|
}
|
||||||
return langCode
|
return strings.TrimSuffix(code, "-raw")
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldOverlayWebA(langPack string) bool {
|
||||||
|
switch strings.ToLower(langPack) {
|
||||||
|
case "android", "ios", "tdesktop", "macos":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) overlayWebAStrings(ctx context.Context, pack domain.LangPack, langPack, langCode string, fromVersion int) (domain.LangPack, error) {
|
||||||
|
if fromVersion != 0 || !shouldOverlayWebA(langPack) {
|
||||||
|
return pack, nil
|
||||||
|
}
|
||||||
|
overlay, err := s.packs.GetPack(ctx, "weba", langCode, fromVersion)
|
||||||
|
if err != nil {
|
||||||
|
return domain.LangPack{}, err
|
||||||
|
}
|
||||||
|
return mergeMissingLangPackStrings(pack, overlay), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeMissingLangPackStrings(pack, overlay domain.LangPack) domain.LangPack {
|
||||||
|
if len(overlay.Strings) == 0 {
|
||||||
|
return pack
|
||||||
|
}
|
||||||
|
if pack.LangCode == "" {
|
||||||
|
pack.LangCode = overlay.LangCode
|
||||||
|
}
|
||||||
|
if overlay.Version > pack.Version {
|
||||||
|
pack.Version = overlay.Version
|
||||||
|
}
|
||||||
|
seen := make(map[string]struct{}, len(pack.Strings)+len(overlay.Strings))
|
||||||
|
for _, item := range pack.Strings {
|
||||||
|
seen[item.Key] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, item := range overlay.Strings {
|
||||||
|
if _, ok := seen[item.Key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pack.Strings = append(pack.Strings, item)
|
||||||
|
seen[item.Key] = struct{}{}
|
||||||
|
}
|
||||||
|
return pack
|
||||||
|
}
|
||||||
|
|
||||||
|
func missingLangPackKeys(keys []string, strings []domain.LangPackString) []string {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
have := make(map[string]struct{}, len(strings))
|
||||||
|
for _, item := range strings {
|
||||||
|
have[item.Key] = struct{}{}
|
||||||
|
}
|
||||||
|
missing := make([]string, 0)
|
||||||
|
seenMissing := make(map[string]struct{}, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
if _, ok := have[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seenMissing[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
missing = append(missing, key)
|
||||||
|
seenMissing[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return missing
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
internal/app/langpack/service_test.go
Normal file
81
internal/app/langpack/service_test.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package langpack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"telesrv/internal/domain"
|
||||||
|
"telesrv/internal/store/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceNormalizesWebARawLangCode(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
packs := memory.NewLangPackStore()
|
||||||
|
svc := NewService(packs)
|
||||||
|
seed := domain.LangPack{
|
||||||
|
LangPack: "android",
|
||||||
|
LangCode: "en",
|
||||||
|
Version: 7,
|
||||||
|
Strings: []domain.LangPackString{
|
||||||
|
{Key: "LogOutTitle", Value: "Log Out"},
|
||||||
|
{Key: "NewMessageTitle", Value: "New Message"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := packs.UpsertPack(ctx, seed); err != nil {
|
||||||
|
t.Fatalf("seed langpack: %v", err)
|
||||||
|
}
|
||||||
|
webASeed := domain.LangPack{
|
||||||
|
LangPack: "weba",
|
||||||
|
LangCode: "en",
|
||||||
|
Version: 12,
|
||||||
|
Strings: []domain.LangPackString{
|
||||||
|
{Key: "AccDescrPollVoteDown", Value: "Go to next unread poll vote"},
|
||||||
|
{Key: "NewMessageTitle", Value: "New Message from WebA"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := packs.UpsertPack(ctx, webASeed); err != nil {
|
||||||
|
t.Fatalf("seed weba langpack: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pack, err := svc.GetLangPack(ctx, "android", "EN-raw")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get langpack: %v", err)
|
||||||
|
}
|
||||||
|
if pack.LangCode != "en" || pack.Version != webASeed.Version || len(pack.Strings) != len(seed.Strings)+1 {
|
||||||
|
t.Fatalf("pack = %+v, want normalized en pack", pack)
|
||||||
|
}
|
||||||
|
if got := stringValue(pack.Strings, "AccDescrPollVoteDown"); got != "Go to next unread poll vote" {
|
||||||
|
t.Fatalf("AccDescrPollVoteDown = %q, want WebA fallback", got)
|
||||||
|
}
|
||||||
|
if got := stringValue(pack.Strings, "NewMessageTitle"); got != "New Message" {
|
||||||
|
t.Fatalf("NewMessageTitle = %q, want source pack to keep precedence", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := svc.GetStrings(ctx, "android", "en-raw", []string{"LogOutTitle", "AccDescrPollVoteDown"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get strings: %v", err)
|
||||||
|
}
|
||||||
|
if got := stringValue(selected.Strings, "LogOutTitle"); got != "Log Out" {
|
||||||
|
t.Fatalf("LogOutTitle = %q, want source pack value", got)
|
||||||
|
}
|
||||||
|
if got := stringValue(selected.Strings, "AccDescrPollVoteDown"); got != "Go to next unread poll vote" {
|
||||||
|
t.Fatalf("AccDescrPollVoteDown = %q, want WebA fallback", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
notModified, err := svc.GetDifference(ctx, "android", "en-raw", seed.Version)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get difference: %v", err)
|
||||||
|
}
|
||||||
|
if notModified.LangCode != "en" || len(notModified.Strings) != 0 {
|
||||||
|
t.Fatalf("difference = %+v, want normalized not-modified source pack", notModified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringValue(strings []domain.LangPackString, key string) string {
|
||||||
|
for _, item := range strings {
|
||||||
|
if item.Key == key {
|
||||||
|
return item.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ func (r *Router) langpackLanguage(ctx context.Context, langPack, langCode string
|
||||||
langCode = "en"
|
langCode = "en"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
langCode = strings.ToLower(langCode)
|
langCode = normalizeLangpackCode(langCode)
|
||||||
languages := r.langpackLanguages(ctx, langPack)
|
languages := r.langpackLanguages(ctx, langPack)
|
||||||
for _, lang := range languages {
|
for _, lang := range languages {
|
||||||
if strings.ToLower(lang.LangCode) == langCode {
|
if strings.ToLower(lang.LangCode) == langCode {
|
||||||
|
|
@ -150,3 +150,11 @@ func langPackFromClient(ctx context.Context) string {
|
||||||
}
|
}
|
||||||
return "tdesktop"
|
return "tdesktop"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeLangpackCode(langCode string) string {
|
||||||
|
code := strings.ToLower(strings.TrimSpace(langCode))
|
||||||
|
if code == "" {
|
||||||
|
return "en"
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(code, "-raw")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,11 @@ func TestLangpackGetLanguage(t *testing.T) {
|
||||||
if lang.LangCode != "zh-hans" || lang.PluralCode != "zh" {
|
if lang.LangCode != "zh-hans" || lang.PluralCode != "zh" {
|
||||||
t.Fatalf("language = %+v, want zh-hans", lang)
|
t.Fatalf("language = %+v, want zh-hans", lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
raw := r.langpackLanguage(context.Background(), "tdesktop", "en-raw")
|
||||||
|
if raw.LangCode != "en" {
|
||||||
|
t.Fatalf("language(en-raw) = %+v, want en", raw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLangpackAndroidPersianLanguage(t *testing.T) {
|
func TestLangpackAndroidPersianLanguage(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ const (
|
||||||
maxPollVotesOffsetLength = 128
|
maxPollVotesOffsetLength = 128
|
||||||
maxTodoItems = 30
|
maxTodoItems = 30
|
||||||
maxTodoTitleLength = 200
|
maxTodoTitleLength = 200
|
||||||
// maxTodoItemID 是清单项 id 的防御上限:协议只要求列表内唯一正整数(客户端通常
|
// maxTodoItemID 是清单项 id 的防御上限:协议字段是 int32,WebA 会用 8 位左右
|
||||||
// 顺序分配),不能用条目数上限当 id 边界,否则非顺序分配的合法 id 被误拒。
|
// 的稀疏本地 id,不能用条目数上限或顺序分配假设当 id 边界。
|
||||||
maxTodoItemID = 1 << 16
|
maxTodoItemID = 1<<31 - 1
|
||||||
maxVenueTitleLength = 256
|
maxVenueTitleLength = 256
|
||||||
maxVenueAddressLength = 512
|
maxVenueAddressLength = 512
|
||||||
maxVenueProviderLength = 64
|
maxVenueProviderLength = 64
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ func (r *Router) onMessagesForwardMessages(ctx context.Context, req *tg.Messages
|
||||||
if !topMsgIDSet && req.TopMsgID != 0 {
|
if !topMsgIDSet && req.TopMsgID != 0 {
|
||||||
topMsgID, topMsgIDSet = req.TopMsgID, true
|
topMsgID, topMsgIDSet = req.TopMsgID, true
|
||||||
}
|
}
|
||||||
|
if topMsgIDSet && topMsgID == -1 {
|
||||||
|
topMsgID, topMsgIDSet = 0, false
|
||||||
|
}
|
||||||
if topMsgIDSet && (topMsgID < 0 || topMsgID > domain.MaxMessageBoxID) {
|
if topMsgIDSet && (topMsgID < 0 || topMsgID > domain.MaxMessageBoxID) {
|
||||||
return nil, replyMessageIDInvalidErr()
|
return nil, replyMessageIDInvalidErr()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,52 @@ func TestMessagesForwardMessagesRecordsRequestAndReturnsUpdates(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMessagesForwardMessagesTreatsMainThreadTopMsgSentinelAsAbsent(t *testing.T) {
|
||||||
|
const (
|
||||||
|
ownerID = int64(1000000101)
|
||||||
|
fromID = int64(1000000102)
|
||||||
|
toID = int64(1000000103)
|
||||||
|
)
|
||||||
|
ctx := context.Background()
|
||||||
|
messages := &captureMessages{list: domain.MessageList{Messages: []domain.Message{
|
||||||
|
{
|
||||||
|
ID: 8,
|
||||||
|
OwnerUserID: ownerID,
|
||||||
|
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: fromID},
|
||||||
|
From: domain.Peer{Type: domain.PeerTypeUser, ID: fromID},
|
||||||
|
Date: 1700000108,
|
||||||
|
Body: "main thread source",
|
||||||
|
},
|
||||||
|
}}}
|
||||||
|
r := New(Config{}, Deps{
|
||||||
|
Messages: messages,
|
||||||
|
Users: mapUsersService{users: map[int64]domain.User{
|
||||||
|
ownerID: {ID: ownerID, FirstName: "Owner"},
|
||||||
|
fromID: {ID: fromID, FirstName: "From"},
|
||||||
|
toID: {ID: toID, FirstName: "To"},
|
||||||
|
}},
|
||||||
|
}, zaptest.NewLogger(t), clock.System)
|
||||||
|
req := &tg.MessagesForwardMessagesRequest{
|
||||||
|
FromPeer: &tg.InputPeerUser{UserID: fromID},
|
||||||
|
ToPeer: &tg.InputPeerUser{UserID: toID},
|
||||||
|
ID: []int{8},
|
||||||
|
RandomID: []int64{8001},
|
||||||
|
}
|
||||||
|
req.SetTopMsgID(-1)
|
||||||
|
|
||||||
|
updatesClass, err := r.onMessagesForwardMessages(WithUserID(ctx, ownerID), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("forward with main thread top_msg_id sentinel: %v", err)
|
||||||
|
}
|
||||||
|
if messages.sendReq.ReplyTo != nil {
|
||||||
|
t.Fatalf("reply = %+v, want nil for main thread sentinel", messages.sendReq.ReplyTo)
|
||||||
|
}
|
||||||
|
updates, ok := updatesClass.(*tg.Updates)
|
||||||
|
if !ok || len(updates.Updates) != 2 {
|
||||||
|
t.Fatalf("updates = %T %+v, want updateMessageID + updateNewMessage", updatesClass, updatesClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMessagesForwardMessagesLoadsPrivateSourcesInSingleBatch(t *testing.T) {
|
func TestMessagesForwardMessagesLoadsPrivateSourcesInSingleBatch(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
userStore := memory.NewUserStore()
|
userStore := memory.NewUserStore()
|
||||||
|
|
@ -312,6 +358,30 @@ func TestMessagesForwardMessagesInputPeerEmptyRejectsBadIDsBeforeLookup(t *testi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMessagesForwardMessagesRejectsOtherNegativeTopMsgID(t *testing.T) {
|
||||||
|
const ownerID = int64(1780243210)
|
||||||
|
ctx := context.Background()
|
||||||
|
messages := &captureMessages{}
|
||||||
|
r := New(Config{}, Deps{
|
||||||
|
Messages: messages,
|
||||||
|
}, zaptest.NewLogger(t), clock.System)
|
||||||
|
req := &tg.MessagesForwardMessagesRequest{
|
||||||
|
FromPeer: &tg.InputPeerUser{UserID: 1780243211},
|
||||||
|
ToPeer: &tg.InputPeerUser{UserID: 1780243212},
|
||||||
|
ID: []int{1},
|
||||||
|
RandomID: []int64{10001},
|
||||||
|
}
|
||||||
|
req.SetTopMsgID(-2)
|
||||||
|
|
||||||
|
_, err := r.onMessagesForwardMessages(WithUserID(ctx, ownerID), req)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "REPLY_MESSAGE_ID_INVALID") {
|
||||||
|
t.Fatalf("forward negative top_msg_id err = %v, want REPLY_MESSAGE_ID_INVALID", err)
|
||||||
|
}
|
||||||
|
if messages.getMessagesCalls != 0 {
|
||||||
|
t.Fatalf("GetMessages calls = %d, want no source lookup for invalid top_msg_id", messages.getMessagesCalls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMessagesForwardMessagesNormalizesAndroidDuplicateIDRetry(t *testing.T) {
|
func TestMessagesForwardMessagesNormalizesAndroidDuplicateIDRetry(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
ownerID = int64(1780243210)
|
ownerID = int64(1780243210)
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,9 @@ func (r *Router) messageReplyFromInput(ctx context.Context, userID int64, peer d
|
||||||
return nil, inputConstructorInvalidErr()
|
return nil, inputConstructorInvalidErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if reply.Zero() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
if _, ok := reply.GetMonoforumPeerID(); ok {
|
if _, ok := reply.GetMonoforumPeerID(); ok {
|
||||||
return nil, replyToMonoforumPeerInvalidErr()
|
return nil, replyToMonoforumPeerInvalidErr()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,37 @@ func TestMessageReplyFromInputStorySucceedsAndProjectsStoryHeader(t *testing.T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMessageReplyFromInputEmptyMessageIsAbsent(t *testing.T) {
|
||||||
|
const userID = int64(1000000001)
|
||||||
|
ctx := WithUserID(context.Background(), userID)
|
||||||
|
r := New(Config{}, Deps{}, zaptest.NewLogger(t), clock.System)
|
||||||
|
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 1000000002}
|
||||||
|
|
||||||
|
reply, err := r.messageReplyFromInput(ctx, userID, peer, &tg.InputReplyToMessage{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("empty reply err = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if reply != nil {
|
||||||
|
t.Fatalf("empty reply = %+v, want nil", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
topicReply := &tg.InputReplyToMessage{}
|
||||||
|
topicReply.SetTopMsgID(123)
|
||||||
|
reply, err = r.messageReplyFromInput(ctx, userID, peer, topicReply)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("topic-only reply err = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if reply == nil || reply.MessageID != 0 || reply.TopMessageID != 123 {
|
||||||
|
t.Fatalf("topic-only reply = %+v, want top_msg_id=123", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteOnly := &tg.InputReplyToMessage{}
|
||||||
|
quoteOnly.SetQuoteText("orphan quote")
|
||||||
|
if _, err := r.messageReplyFromInput(ctx, userID, peer, quoteOnly); err == nil || !strings.Contains(err.Error(), "REPLY_MESSAGE_ID_INVALID") {
|
||||||
|
t.Fatalf("quote-only reply err = %v, want REPLY_MESSAGE_ID_INVALID", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMessageReplyFromInputUnsupportedShapesReturnExplicitErrors(t *testing.T) {
|
func TestMessageReplyFromInputUnsupportedShapesReturnExplicitErrors(t *testing.T) {
|
||||||
const userID = int64(1000000001)
|
const userID = int64(1000000001)
|
||||||
ctx := WithUserID(context.Background(), userID)
|
ctx := WithUserID(context.Background(), userID)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,59 @@ func TestSendMediaTodoEcho(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSendMediaTodoWithEmptyReplyToTreatsReplyAsAbsent(t *testing.T) {
|
||||||
|
r, owner, friend := newMediaTestRouter(t)
|
||||||
|
req := &tg.MessagesSendMediaRequest{
|
||||||
|
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||||||
|
Media: &tg.InputMediaTodo{Todo: tg.TodoList{
|
||||||
|
Title: tg.TextWithEntities{Text: "web checklist", Entities: []tg.MessageEntityClass{}},
|
||||||
|
List: []tg.TodoItem{
|
||||||
|
{ID: 1, Title: tg.TextWithEntities{Text: "send item", Entities: []tg.MessageEntityClass{}}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RandomID: 7005,
|
||||||
|
}
|
||||||
|
req.SetReplyTo(&tg.InputReplyToMessage{})
|
||||||
|
|
||||||
|
updates, err := r.onMessagesSendMedia(WithUserID(context.Background(), owner.ID), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sendMedia todo with empty reply: %v", err)
|
||||||
|
}
|
||||||
|
msg := newMessageFromUpdates(t, updates)
|
||||||
|
if msg.ReplyTo != nil {
|
||||||
|
t.Fatalf("reply_to = %T, want nil for empty input reply", msg.ReplyTo)
|
||||||
|
}
|
||||||
|
if media, ok := msg.Media.(*tg.MessageMediaToDo); !ok || media.Todo.Title.Text != "web checklist" {
|
||||||
|
t.Fatalf("media = %#v, want todo checklist", msg.Media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendMediaTodoAcceptsSparseLargeItemIDs(t *testing.T) {
|
||||||
|
r, owner, friend := newMediaTestRouter(t)
|
||||||
|
updates, err := r.onMessagesSendMedia(WithUserID(context.Background(), owner.ID), &tg.MessagesSendMediaRequest{
|
||||||
|
Peer: &tg.InputPeerUser{UserID: friend.ID, AccessHash: friend.AccessHash},
|
||||||
|
Media: &tg.InputMediaTodo{Todo: tg.TodoList{
|
||||||
|
Title: tg.TextWithEntities{Text: "web sparse ids", Entities: []tg.MessageEntityClass{}},
|
||||||
|
List: []tg.TodoItem{
|
||||||
|
{ID: 56151290, Title: tg.TextWithEntities{Text: "first", Entities: []tg.MessageEntityClass{}}},
|
||||||
|
{ID: 56151305, Title: tg.TextWithEntities{Text: "second", Entities: []tg.MessageEntityClass{}}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
RandomID: 7006,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sendMedia todo sparse ids: %v", err)
|
||||||
|
}
|
||||||
|
msg := newMessageFromUpdates(t, updates)
|
||||||
|
media, ok := msg.Media.(*tg.MessageMediaToDo)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("media = %T, want MessageMediaToDo", msg.Media)
|
||||||
|
}
|
||||||
|
if got := media.Todo.List[0].ID; got != 56151290 {
|
||||||
|
t.Fatalf("first todo id = %d, want 56151290", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestToggleTodoCompletedAndAppend(t *testing.T) {
|
func TestToggleTodoCompletedAndAppend(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
r, owner, friend := newMediaTestRouter(t)
|
r, owner, friend := newMediaTestRouter(t)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue