package rpc import ( "context" "strings" "testing" "github.com/iamxvbaba/td/clock" "github.com/iamxvbaba/td/tg" "go.uber.org/zap/zaptest" appchannels "telesrv/internal/app/channels" appdialogs "telesrv/internal/app/dialogs" appmessages "telesrv/internal/app/messages" appusers "telesrv/internal/app/users" "telesrv/internal/domain" "telesrv/internal/seed/appearance" "telesrv/internal/store/memory" ) func TestMessagesSetChatThemePrivatePersistsServiceMessageAndUserFull(t *testing.T) { ctx := context.Background() userStore := memory.NewUserStore() alice, _ := userStore.Create(ctx, domain.User{AccessHash: 1001, Phone: "15550003101", FirstName: "Alice"}) bob, _ := userStore.Create(ctx, domain.User{AccessHash: 1002, Phone: "15550003102", FirstName: "Bob"}) dialogStore := memory.NewDialogStore() messageStore := memory.NewMessageStore(dialogStore) router := New(Config{}, Deps{ Users: appusers.NewService(userStore), Dialogs: appdialogs.NewService(dialogStore), Messages: appmessages.NewService(messageStore, dialogStore), }, zaptest.NewLogger(t), clock.System) theme := "\U0001f338" if cts := appearance.Default().ChatThemes; len(cts) > 0 { theme = cts[0].Emoticon } set, err := router.onMessagesSetChatTheme(WithUserID(ctx, alice.ID), &tg.MessagesSetChatThemeRequest{ Peer: &tg.InputPeerUser{UserID: bob.ID, AccessHash: bob.AccessHash}, Theme: &tg.InputChatTheme{Emoticon: theme}, }) if err != nil { t.Fatalf("set chat theme: %v", err) } setAction := chatThemeActionFromUpdates(t, set) if got := setAction.Theme.(*tg.ChatTheme).Emoticon; got != theme { t.Fatalf("set action emoticon = %q, want %q", got, theme) } assertDialogTheme(t, dialogStore, alice.ID, bob.ID, theme) assertDialogTheme(t, dialogStore, bob.ID, alice.ID, theme) full, err := router.onUsersGetFullUser(WithUserID(ctx, alice.ID), &tg.InputUser{UserID: bob.ID, AccessHash: bob.AccessHash}) if err != nil { t.Fatalf("get full user after set: %v", err) } if gotTheme, ok := full.FullUser.GetTheme(); !ok || gotTheme.(*tg.ChatTheme).Emoticon != theme { t.Fatalf("full user theme = %#v ok=%v, want %q", gotTheme, ok, theme) } clear, err := router.onMessagesSetChatTheme(WithUserID(ctx, alice.ID), &tg.MessagesSetChatThemeRequest{ Peer: &tg.InputPeerUser{UserID: bob.ID, AccessHash: bob.AccessHash}, Theme: &tg.InputChatTheme{Emoticon: ""}, }) if err != nil { t.Fatalf("clear chat theme: %v", err) } clearAction := chatThemeActionFromUpdates(t, clear) if got := clearAction.Theme.(*tg.ChatTheme).Emoticon; got != "" { t.Fatalf("clear action emoticon = %q, want empty", got) } assertDialogTheme(t, dialogStore, alice.ID, bob.ID, "") assertDialogTheme(t, dialogStore, bob.ID, alice.ID, "") full, err = router.onUsersGetFullUser(WithUserID(ctx, alice.ID), &tg.InputUser{UserID: bob.ID, AccessHash: bob.AccessHash}) if err != nil { t.Fatalf("get full user after clear: %v", err) } if gotTheme, ok := full.FullUser.GetTheme(); ok { t.Fatalf("full user theme after clear = %#v, want absent", gotTheme) } } func TestMessagesSetChatThemeRejectsUnsupportedThemes(t *testing.T) { router := New(Config{}, Deps{}, zaptest.NewLogger(t), clock.System) ctx := WithUserID(context.Background(), 1000000001) for _, req := range []*tg.MessagesSetChatThemeRequest{ {Peer: &tg.InputPeerUser{UserID: 1000000002}, Theme: &tg.InputChatTheme{Emoticon: "\U0001f47b"}}, {Peer: &tg.InputPeerUser{UserID: 1000000002}, Theme: &tg.InputChatThemeUniqueGift{Slug: "gift"}}, } { _, err := router.onMessagesSetChatTheme(ctx, req) if err == nil || !strings.Contains(err.Error(), "THEME_INVALID") { t.Fatalf("set unsupported theme err = %v, want THEME_INVALID", err) } } } func TestMessagesSetChatWallPaperAcksPrivatePeerAndSupportsChannel(t *testing.T) { router := New(Config{}, Deps{}, zaptest.NewLogger(t), clock.System) ctx := WithUserID(context.Background(), 1000000001) okReq := &tg.MessagesSetChatWallPaperRequest{ Peer: &tg.InputPeerUser{UserID: 1000000002}, } okReq.SetWallpaper(&tg.InputWallPaperNoFile{ID: 930000000000000000}) if got, err := router.onMessagesSetChatWallPaper(ctx, okReq); err != nil { t.Fatalf("set chat wallpaper: %v", err) } else if updates, ok := got.(*tg.Updates); !ok || len(updates.Updates) != 0 || updates.Date == 0 { t.Fatalf("set chat wallpaper updates = %#v, want dated empty updates", got) } for _, req := range []*tg.MessagesSetChatWallPaperRequest{ nil, {Peer: &tg.InputPeerEmpty{}}, {Peer: &tg.InputPeerChannel{ChannelID: 10}}, } { _, err := router.onMessagesSetChatWallPaper(ctx, req) if err == nil { t.Fatalf("set chat wallpaper with %#v succeeded, want error", req) } } userStore := memory.NewUserStore() alice, _ := userStore.Create(context.Background(), domain.User{AccessHash: 2001, Phone: "15550003201", FirstName: "Alice"}) bob, _ := userStore.Create(context.Background(), domain.User{AccessHash: 2002, Phone: "15550003202", FirstName: "Bob"}) channelStore := memory.NewChannelStore() channels := appchannels.NewService(channelStore) created, err := channels.CreateChannel(context.Background(), alice.ID, domain.CreateChannelRequest{ Title: "Wallpaper Channel", Broadcast: true, MemberUserIDs: []int64{bob.ID}, Date: 1700000100, }) if err != nil { t.Fatalf("create channel: %v", err) } router = New(Config{}, Deps{ Users: appusers.NewService(userStore), Channels: channels, }, zaptest.NewLogger(t), clock.System) settings := tg.WallPaperSettings{} settings.SetBackgroundColor(0) settings.SetSecondBackgroundColor(0x95c46a) settings.SetRotation(45) channelReq := &tg.MessagesSetChatWallPaperRequest{ Peer: &tg.InputPeerChannel{ChannelID: created.Channel.ID, AccessHash: created.Channel.AccessHash}, } channelReq.SetWallpaper(&tg.InputWallPaperNoFile{ID: 930000000000000001}) channelReq.SetSettings(settings) got, err := router.onMessagesSetChatWallPaper(WithUserID(context.Background(), alice.ID), channelReq) if err != nil { t.Fatalf("set channel wallpaper: %v", err) } updates, ok := got.(*tg.Updates) if !ok { t.Fatalf("set channel wallpaper updates = %T, want *tg.Updates", got) } action := channelWallpaperActionFromUpdates(t, updates) actionWallpaper, ok := action.Wallpaper.(*tg.WallPaperNoFile) if !ok || actionWallpaper.ID != 930000000000000001 { t.Fatalf("service wallpaper = %T %#v, want wallPaperNoFile id", action.Wallpaper, action.Wallpaper) } peerWallpaper := channelPeerWallpaperFromUpdates(t, updates) if gotID := peerWallpaper.ID; gotID != 930000000000000001 { t.Fatalf("updatePeerWallpaper id = %d, want selected wallpaper id", gotID) } if color, ok := peerWallpaper.Settings.GetBackgroundColor(); !ok || color != 0 { t.Fatalf("background color = %d ok=%v, want explicit black", color, ok) } view, err := channels.GetChannel(context.Background(), alice.ID, created.Channel.ID) if err != nil { t.Fatalf("get channel after wallpaper: %v", err) } if view.Channel.Wallpaper == nil || !view.Channel.Wallpaper.NoFile || !view.Channel.Wallpaper.Settings.HasBackgroundColor { t.Fatalf("stored channel wallpaper = %#v, want no-file with explicit settings", view.Channel.Wallpaper) } full := tgChannelFull(view) fullWallpaper, ok := full.GetWallpaper() if !ok { t.Fatalf("channelFull wallpaper missing") } fullNoFile, ok := fullWallpaper.(*tg.WallPaperNoFile) if !ok || fullNoFile.ID != 930000000000000001 { t.Fatalf("channelFull wallpaper = %T %#v, want selected no-file", fullWallpaper, fullWallpaper) } clearReq := &tg.MessagesSetChatWallPaperRequest{Peer: channelReq.Peer} clearGot, err := router.onMessagesSetChatWallPaper(WithUserID(context.Background(), alice.ID), clearReq) if err != nil { t.Fatalf("clear channel wallpaper: %v", err) } clearUpdates, ok := clearGot.(*tg.Updates) if !ok { t.Fatalf("clear channel wallpaper updates = %T, want *tg.Updates", clearGot) } if channelPeerWallpaperPresent(clearUpdates) { t.Fatalf("clear updatePeerWallpaper carried wallpaper, want absent wallpaper") } view, err = channels.GetChannel(context.Background(), alice.ID, created.Channel.ID) if err != nil { t.Fatalf("get channel after clear wallpaper: %v", err) } if view.Channel.Wallpaper != nil { t.Fatalf("stored channel wallpaper after clear = %#v, want nil", view.Channel.Wallpaper) } if _, ok := tgChannelFull(view).GetWallpaper(); ok { t.Fatalf("channelFull wallpaper after clear present, want absent") } _, err = router.onMessagesSetChatWallPaper(WithUserID(context.Background(), bob.ID), channelReq) if err == nil || !strings.Contains(err.Error(), "CHAT_ADMIN_REQUIRED") { t.Fatalf("non-admin set channel wallpaper err = %v, want CHAT_ADMIN_REQUIRED", err) } } func channelWallpaperActionFromUpdates(t *testing.T, updates *tg.Updates) *tg.MessageActionSetChatWallPaper { t.Helper() for _, update := range updates.Updates { newMessage, ok := update.(*tg.UpdateNewChannelMessage) if !ok { continue } service, ok := newMessage.Message.(*tg.MessageService) if !ok { continue } action, ok := service.Action.(*tg.MessageActionSetChatWallPaper) if !ok { t.Fatalf("channel service action = %T, want MessageActionSetChatWallPaper", service.Action) } return action } t.Fatalf("updates = %#v, want UpdateNewChannelMessage wallpaper service action", updates.Updates) return nil } func channelPeerWallpaperFromUpdates(t *testing.T, updates *tg.Updates) *tg.WallPaperNoFile { t.Helper() for _, update := range updates.Updates { peerWallpaper, ok := update.(*tg.UpdatePeerWallpaper) if !ok { continue } wallpaper, ok := peerWallpaper.GetWallpaper() if !ok { t.Fatalf("updatePeerWallpaper missing wallpaper") } noFile, ok := wallpaper.(*tg.WallPaperNoFile) if !ok { t.Fatalf("updatePeerWallpaper wallpaper = %T, want WallPaperNoFile", wallpaper) } return noFile } t.Fatalf("updates = %#v, want UpdatePeerWallpaper", updates.Updates) return nil } func channelPeerWallpaperPresent(updates *tg.Updates) bool { for _, update := range updates.Updates { peerWallpaper, ok := update.(*tg.UpdatePeerWallpaper) if !ok { continue } _, ok = peerWallpaper.GetWallpaper() return ok } return false } func chatThemeActionFromUpdates(t *testing.T, updates tg.UpdatesClass) *tg.MessageActionSetChatTheme { t.Helper() got, ok := updates.(*tg.Updates) if !ok { t.Fatalf("updates = %T, want *tg.Updates", updates) } for _, update := range got.Updates { newMessage, ok := update.(*tg.UpdateNewMessage) if !ok { continue } service, ok := newMessage.Message.(*tg.MessageService) if !ok { continue } action, ok := service.Action.(*tg.MessageActionSetChatTheme) if !ok { t.Fatalf("service action = %T, want MessageActionSetChatTheme", service.Action) } return action } t.Fatalf("updates = %#v, want UpdateNewMessage service action", got.Updates) return nil } func assertDialogTheme(t *testing.T, dialogs *memory.DialogStore, ownerID, peerID int64, want string) { t.Helper() list, err := dialogs.ListByPeers(context.Background(), ownerID, []domain.Peer{{Type: domain.PeerTypeUser, ID: peerID}}) if err != nil { t.Fatalf("list dialogs for %d/%d: %v", ownerID, peerID, err) } if len(list.Dialogs) != 1 { t.Fatalf("dialogs for %d/%d = %d, want 1", ownerID, peerID, len(list.Dialogs)) } if got := list.Dialogs[0].ThemeEmoticon; got != want { t.Fatalf("dialog theme for %d/%d = %q, want %q", ownerID, peerID, got, want) } }