owpengram-server/internal/rpc/photos_push_test.go

216 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"strings"
"testing"
"github.com/gotd/td/clock"
"github.com/gotd/td/tg"
"go.uber.org/zap/zaptest"
appusers "telesrv/internal/app/users"
"telesrv/internal/domain"
"telesrv/internal/store/memory"
)
// TestUploadProfilePhotoPushesUpdateToOtherDevices 守护审计修复:换头像后必须向该账号其它在线
// 设备推送updateUser 信号 + Updates.Users 带新 self user否则其它设备头像不刷新。
// 原因updateUserName 不含 photo 无法刷新头像,唯有带 user 对象最可靠(见 photos.go
// pushSelfPhotoUpdate 注释)。曾完全不推送,本测试防回归。
func TestUploadProfilePhotoPushesUpdateToOtherDevices(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, _ := userStore.Create(ctx, domain.User{AccessHash: 11, Phone: "15550001001", FirstName: "Owner"})
sessions := &captureSessions{}
files := &fakeFiles{}
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
Users: appusers.NewService(userStore),
Files: files,
Sessions: sessions,
}, zaptest.NewLogger(t), clock.System)
req := &tg.PhotosUploadProfilePhotoRequest{}
req.SetFile(&tg.InputFile{ID: 42, Parts: 1, Name: "a.jpg"}) // File 是 flags 可选字段,须 SetFile 置位
got, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), req)
if err != nil {
t.Fatalf("uploadProfilePhoto: %v", err)
}
returnedPhoto, ok := firstPhotosUser(t, got).Photo.(*tg.UserProfilePhoto)
if !ok || returnedPhoto.PhotoID != 778 || returnedPhoto.DCID != 2 {
t.Fatalf("returned self photo = %+v, want photo_id=778 dc_id=2", firstPhotosUser(t, got).Photo)
}
snap := sessions.snapshot()
if snap.userID != owner.ID {
t.Fatalf("push target user = %d, want %d", snap.userID, owner.ID)
}
updates, ok := snap.message.(*tg.Updates)
if !ok {
t.Fatalf("pushed message = %T, want *tg.Updates", snap.message)
}
hasUserUpdate := false
for _, u := range updates.Updates {
if uu, ok := u.(*tg.UpdateUser); ok && uu.UserID == owner.ID {
hasUserUpdate = true
}
}
if !hasUserUpdate {
t.Fatalf("updates = %+v, want UpdateUser for self", updates.Updates)
}
if len(updates.Users) == 0 {
t.Fatal("pushed updates missing self user — other devices cannot refresh avatar")
}
pushedUser, ok := updates.Users[0].(*tg.User)
if !ok {
t.Fatalf("pushed user = %T, want *tg.User", updates.Users[0])
}
pushedPhoto, ok := pushedUser.Photo.(*tg.UserProfilePhoto)
if !ok || pushedPhoto.PhotoID != 778 || pushedPhoto.DCID != 2 {
t.Fatalf("pushed self photo = %+v, want photo_id=778 dc_id=2", pushedUser.Photo)
}
}
func TestUploadProfilePhotoSupportsAnimatedVideoAndEmojiMarkup(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, _ := userStore.Create(ctx, domain.User{AccessHash: 11, Phone: "15550001002", FirstName: "Owner"})
sessions := &captureSessions{}
files := &fakeFiles{}
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
Users: appusers.NewService(userStore, appusers.WithPhotoProvider(files)),
Files: files,
Sessions: sessions,
}, zaptest.NewLogger(t), clock.System)
videoReq := &tg.PhotosUploadProfilePhotoRequest{}
videoReq.SetVideo(&tg.InputFile{ID: 43, Parts: 1, Name: "avatar.mp4"})
videoReq.SetVideoStartTs(0.25)
videoGot, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), videoReq)
if err != nil {
t.Fatalf("upload animated profile video: %v", err)
}
videoPhoto, ok := videoGot.Photo.(*tg.Photo)
if !ok || videoPhoto.ID != 779 || len(videoPhoto.VideoSizes) != 1 {
t.Fatalf("video profile photo = %+v, want photo 779 with video_sizes", videoGot.Photo)
}
videoSize, ok := videoPhoto.VideoSizes[0].(*tg.VideoSize)
if !ok || videoSize.Type != "u" || videoSize.VideoStartTs != 0.25 {
t.Fatalf("video size = %+v, want videoSize u start 0.25", videoPhoto.VideoSizes[0])
}
selfPhoto := firstPhotosUser(t, videoGot).Photo
userPhoto, ok := selfPhoto.(*tg.UserProfilePhoto)
if !ok || userPhoto.PhotoID != 779 || userPhoto.DCID != 2 || !userPhoto.HasVideo {
t.Fatalf("returned self photo = %+v, want photo 779 dc 2 has_video", selfPhoto)
}
if cur, ok, err := files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, owner.ID, domain.ProfilePhotoKindProfile); err != nil || !ok || cur.ID != 779 {
t.Fatalf("current profile photo = %+v ok=%v err=%v, want 779", cur, ok, err)
}
markupReq := &tg.PhotosUploadProfilePhotoRequest{}
markupReq.SetVideoEmojiMarkup(&tg.VideoSizeEmojiMarkup{EmojiID: 99, BackgroundColors: []int{0xffffff}})
markupGot, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), markupReq)
if err != nil {
t.Fatalf("upload emoji profile photo: %v", err)
}
markupPhoto, ok := markupGot.Photo.(*tg.Photo)
if !ok || markupPhoto.ID != 780 || len(markupPhoto.VideoSizes) != 1 {
t.Fatalf("emoji profile photo = %+v, want photo 780 with video_sizes", markupGot.Photo)
}
if _, ok := markupPhoto.VideoSizes[0].(*tg.VideoSizeEmojiMarkup); !ok {
t.Fatalf("emoji video size = %T, want *tg.VideoSizeEmojiMarkup", markupPhoto.VideoSizes[0])
}
selfPhoto = firstPhotosUser(t, markupGot).Photo
userPhoto, ok = selfPhoto.(*tg.UserProfilePhoto)
if !ok || userPhoto.PhotoID != 780 || userPhoto.DCID != 2 || !userPhoto.HasVideo {
t.Fatalf("returned self photo after emoji = %+v, want photo 780 dc 2 has_video", selfPhoto)
}
stickerReq := &tg.PhotosUploadProfilePhotoRequest{}
stickerReq.SetVideoEmojiMarkup(&tg.VideoSizeStickerMarkup{
Stickerset: &tg.InputStickerSetAnimatedEmoji{},
StickerID: 123,
BackgroundColors: []int{0x112233, 0x445566},
})
stickerGot, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), stickerReq)
if err != nil {
t.Fatalf("upload animated emoji sticker profile photo: %v", err)
}
stickerPhoto, ok := stickerGot.Photo.(*tg.Photo)
if !ok || len(stickerPhoto.VideoSizes) != 1 {
t.Fatalf("sticker profile photo = %+v, want photo with video_sizes", stickerGot.Photo)
}
stickerSize, ok := stickerPhoto.VideoSizes[0].(*tg.VideoSizeStickerMarkup)
if !ok {
t.Fatalf("sticker video size = %T, want *tg.VideoSizeStickerMarkup", stickerPhoto.VideoSizes[0])
}
if _, ok := stickerSize.Stickerset.(*tg.InputStickerSetAnimatedEmoji); !ok {
t.Fatalf("sticker markup set = %T, want inputStickerSetAnimatedEmoji", stickerSize.Stickerset)
}
comboReq := &tg.PhotosUploadProfilePhotoRequest{}
comboReq.SetVideo(&tg.InputFile{ID: 45, Parts: 9, Name: "avatar.mp4"})
comboReq.SetVideoStartTs(0)
comboReq.SetVideoEmojiMarkup(&tg.VideoSizeEmojiMarkup{
EmojiID: 1258816259753929,
BackgroundColors: []int{0x536dfe, 0x29b6f6, 0x26a69a, 0x66bb6a},
})
comboGot, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), comboReq)
if err != nil {
t.Fatalf("upload Android video+emoji profile photo: %v", err)
}
comboPhoto, ok := comboGot.Photo.(*tg.Photo)
if !ok || len(comboPhoto.VideoSizes) != 2 {
t.Fatalf("combo profile photo = %+v, want two video_sizes", comboGot.Photo)
}
if _, ok := comboPhoto.VideoSizes[0].(*tg.VideoSize); !ok {
t.Fatalf("combo first video size = %T, want *tg.VideoSize", comboPhoto.VideoSizes[0])
}
if _, ok := comboPhoto.VideoSizes[1].(*tg.VideoSizeEmojiMarkup); !ok {
t.Fatalf("combo second video size = %T, want *tg.VideoSizeEmojiMarkup", comboPhoto.VideoSizes[1])
}
}
func TestUploadProfilePhotoFallbackEmojiAndInvalidFlags(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, _ := userStore.Create(ctx, domain.User{AccessHash: 11, Phone: "15550001003", FirstName: "Owner"})
files := &fakeFiles{}
r := New(Config{DC: 2, IP: "127.0.0.1", Port: 2398}, Deps{
Users: appusers.NewService(userStore, appusers.WithPhotoProvider(files)),
Files: files,
}, zaptest.NewLogger(t), clock.System)
profileReq := &tg.PhotosUploadProfilePhotoRequest{}
profileReq.SetFile(&tg.InputFile{ID: 42, Parts: 1, Name: "avatar.jpg"})
if _, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), profileReq); err != nil {
t.Fatalf("seed profile photo: %v", err)
}
fallbackReq := &tg.PhotosUploadProfilePhotoRequest{}
fallbackReq.SetFallback(true)
fallbackReq.SetVideoEmojiMarkup(&tg.VideoSizeEmojiMarkup{EmojiID: 100, BackgroundColors: []int{0x112233}})
fallbackGot, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), fallbackReq)
if err != nil {
t.Fatalf("upload fallback emoji profile photo: %v", err)
}
if photo, ok := fallbackGot.Photo.(*tg.Photo); !ok || photo.ID != 780 {
t.Fatalf("fallback photo = %+v, want photo 780", fallbackGot.Photo)
}
if cur, ok, err := files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, owner.ID, domain.ProfilePhotoKindFallback); err != nil || !ok || cur.ID != 780 {
t.Fatalf("current fallback photo = %+v ok=%v err=%v, want 780", cur, ok, err)
}
if cur, ok, err := files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, owner.ID, domain.ProfilePhotoKindProfile); err != nil || !ok || cur.ID != 778 {
t.Fatalf("current profile photo after fallback = %+v ok=%v err=%v, want preserved 778", cur, ok, err)
}
invalidReq := &tg.PhotosUploadProfilePhotoRequest{}
invalidReq.SetFile(&tg.InputFile{ID: 44, Parts: 1, Name: "avatar.jpg"})
invalidReq.SetVideoEmojiMarkup(&tg.VideoSizeEmojiMarkup{EmojiID: 101, BackgroundColors: []int{0x445566}})
if _, err := r.onPhotosUploadProfilePhoto(WithUserID(ctx, owner.ID), invalidReq); err == nil || !strings.Contains(err.Error(), "PHOTO_INVALID") {
t.Fatalf("invalid mixed profile photo error = %v, want PHOTO_INVALID", err)
}
if cur, ok, err := files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, owner.ID, domain.ProfilePhotoKindProfile); err != nil || !ok || cur.ID != 778 {
t.Fatalf("current profile photo after invalid = %+v ok=%v err=%v, want preserved 778", cur, ok, err)
}
}