package postgres import ( "context" "testing" "telesrv/internal/domain" ) // TestChannelDialogTopMessageCarriesMentionFlags 复现 @ 角标重启回潮: // getDialogs 的 channel top message 必须按 viewer 携带 mentioned/media_unread, // 否则 TDesktop 先入缓存的残缺版永不触发 contents-read,服务端 unread // 提及挂死、每次冷启动 unread_mentions_count 把 @ 角标带回来。 func TestChannelDialogTopMessageCarriesMentionFlags(t *testing.T) { pool := testPool(t) ctx := context.Background() suffix := randomSuffix(t) users := NewUserStore(pool) owner, err := users.Create(ctx, domain.User{AccessHash: 97, Phone: "+1675" + suffix + "01", FirstName: "MentionOwner"}) if err != nil { t.Fatalf("create owner: %v", err) } member, err := users.Create(ctx, domain.User{AccessHash: 98, Phone: "+1675" + suffix + "02", FirstName: "MentionMember"}) if err != nil { t.Fatalf("create member: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, member.ID}) }) channels := NewChannelStore(pool) created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{ CreatorUserID: owner.ID, Title: "MentionDialog " + suffix, Megagroup: true, MemberUserIDs: []int64{member.ID}, Date: 1700000910, }) if err != nil { t.Fatalf("create channel: %v", err) } sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{ UserID: owner.ID, ChannelID: created.Channel.ID, RandomID: 851001, Message: "ping member", MentionUserIDs: []int64{member.ID}, Date: 1700000911, }) if err != nil { t.Fatalf("send mention: %v", err) } assertFlags := func(name string, list domain.ChannelDialogList) { t.Helper() found := false for _, msg := range list.Messages { if msg.ChannelID != created.Channel.ID || msg.ID != sent.Message.ID { continue } found = true if !msg.Mentioned || !msg.MediaUnread { t.Fatalf("%s top message flags = mentioned %v media_unread %v, want both true(缺失会让 @ 角标重启回潮)", name, msg.Mentioned, msg.MediaUnread) } } if !found { t.Fatalf("%s lacks top message %d: %+v", name, sent.Message.ID, list.Messages) } } list, err := channels.ListChannelDialogs(ctx, member.ID, domain.DialogFilter{Limit: 50}) if err != nil { t.Fatalf("list channel dialogs: %v", err) } assertFlags("ListChannelDialogs", list) got, err := channels.GetChannelDialogs(ctx, member.ID, []int64{created.Channel.ID}) if err != nil { t.Fatalf("get channel dialogs: %v", err) } assertFlags("GetChannelDialogs", got) // 提及读掉后:mentioned 永久保留、media_unread 翻为 false。 if _, err := channels.ReadChannelMentions(ctx, domain.ReadChannelMentionsRequest{ UserID: member.ID, ChannelID: created.Channel.ID, }); err != nil { t.Fatalf("read mentions: %v", err) } after, err := channels.GetChannelDialogs(ctx, member.ID, []int64{created.Channel.ID}) if err != nil { t.Fatalf("get channel dialogs after read: %v", err) } for _, msg := range after.Messages { if msg.ID == sent.Message.ID && (!msg.Mentioned || msg.MediaUnread) { t.Fatalf("after read flags = mentioned %v media_unread %v, want mentioned kept with media_unread cleared", msg.Mentioned, msg.MediaUnread) } } }