309 lines
11 KiB
Go
309 lines
11 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gotd/td/bin"
|
|
"github.com/gotd/td/clock"
|
|
"github.com/gotd/td/tg"
|
|
"go.uber.org/zap/zaptest"
|
|
|
|
appaccount "telesrv/internal/app/account"
|
|
appmessages "telesrv/internal/app/messages"
|
|
appusers "telesrv/internal/app/users"
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store/memory"
|
|
)
|
|
|
|
func TestSavedMusicRPCLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
userStore := memory.NewUserStore()
|
|
owner, err := userStore.Create(ctx, domain.User{AccessHash: 11, Phone: "15550000011", FirstName: "Owner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
passwords := memory.NewPasswordStore()
|
|
music1 := testMusicDocument(101, 1001, "First")
|
|
music2 := testMusicDocument(102, 1002, "Second")
|
|
voice := domain.Document{ID: 103, AccessHash: 1003, MimeType: "audio/ogg", Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrAudio, Voice: true, AudioDuration: 3}}}
|
|
files := &fakeFiles{docs: map[int64]domain.Document{
|
|
music1.ID: music1,
|
|
music2.ID: music2,
|
|
voice.ID: voice,
|
|
}}
|
|
r := New(Config{}, Deps{
|
|
Account: appaccount.NewService(passwords, appaccount.WithSavedMusic(passwords)),
|
|
Files: files,
|
|
Users: appusers.NewService(userStore),
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
reqCtx := WithUserID(ctx, owner.ID)
|
|
|
|
if ok, err := r.onAccountSaveMusic(reqCtx, &tg.AccountSaveMusicRequest{
|
|
ID: &tg.InputDocument{ID: music1.ID, AccessHash: music1.AccessHash},
|
|
}); err != nil || !ok {
|
|
t.Fatalf("save first music = ok %v err %v, want true/nil", ok, err)
|
|
}
|
|
afterFirst := &tg.AccountSaveMusicRequest{
|
|
ID: &tg.InputDocument{ID: music2.ID, AccessHash: music2.AccessHash},
|
|
}
|
|
afterFirst.SetAfterID(&tg.InputDocument{ID: music1.ID, AccessHash: music1.AccessHash})
|
|
if ok, err := r.onAccountSaveMusic(reqCtx, afterFirst); err != nil || !ok {
|
|
t.Fatalf("save second after first = ok %v err %v, want true/nil", ok, err)
|
|
}
|
|
idsBox, err := r.onAccountGetSavedMusicIDs(reqCtx, 0)
|
|
if err != nil {
|
|
t.Fatalf("get saved music ids: %v", err)
|
|
}
|
|
ids, ok := idsBox.(*tg.AccountSavedMusicIDs)
|
|
if !ok || !int64SliceEqual(ids.IDs, []int64{music1.ID, music2.ID}) {
|
|
t.Fatalf("ids = %T %+v, want [first second]", idsBox, idsBox)
|
|
}
|
|
if notModified, err := r.onAccountGetSavedMusicIDs(reqCtx, int64(tdesktopCountHash(ids.IDs))); err != nil {
|
|
t.Fatalf("get saved music ids hash: %v", err)
|
|
} else if _, ok := notModified.(*tg.AccountSavedMusicIDsNotModified); !ok {
|
|
t.Fatalf("hash result = %T, want notModified", notModified)
|
|
}
|
|
|
|
page, err := r.onUsersGetSavedMusic(reqCtx, &tg.UsersGetSavedMusicRequest{ID: &tg.InputUserSelf{}, Limit: 2})
|
|
if err != nil {
|
|
t.Fatalf("users.getSavedMusic: %v", err)
|
|
}
|
|
saved, ok := page.(*tg.UsersSavedMusic)
|
|
if !ok || saved.Count != 2 || len(saved.Documents) != 2 || documentClassID(saved.Documents[0]) != music1.ID {
|
|
t.Fatalf("saved music page = %T %+v, want two docs with first first", page, page)
|
|
}
|
|
hashPage, err := r.onUsersGetSavedMusic(reqCtx, &tg.UsersGetSavedMusicRequest{
|
|
ID: &tg.InputUserSelf{},
|
|
Limit: 2,
|
|
Hash: int64(tdesktopCountHash([]int64{music1.ID, music2.ID})),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("users.getSavedMusic hash: %v", err)
|
|
}
|
|
if nm, ok := hashPage.(*tg.UsersSavedMusicNotModified); !ok || nm.Count != 2 {
|
|
t.Fatalf("hash page = %T %+v, want notModified count=2", hashPage, hashPage)
|
|
}
|
|
|
|
full, err := r.onUsersGetFullUser(reqCtx, &tg.InputUserSelf{})
|
|
if err != nil {
|
|
t.Fatalf("users.getFullUser: %v", err)
|
|
}
|
|
if doc, ok := full.FullUser.GetSavedMusicAsNotEmpty(); !ok || doc.ID != music1.ID {
|
|
t.Fatalf("full saved_music = %+v ok=%v, want first document", doc, ok)
|
|
}
|
|
byID, err := r.onUsersGetSavedMusicByID(reqCtx, &tg.UsersGetSavedMusicByIDRequest{
|
|
ID: &tg.InputUserSelf{},
|
|
Documents: []tg.InputDocumentClass{&tg.InputDocument{ID: music2.ID, AccessHash: music2.AccessHash}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("users.getSavedMusicByID: %v", err)
|
|
}
|
|
byIDMusic, ok := byID.(*tg.UsersSavedMusic)
|
|
if !ok || len(byIDMusic.Documents) != 1 || documentClassID(byIDMusic.Documents[0]) != music2.ID {
|
|
t.Fatalf("by id = %T %+v, want second document", byID, byID)
|
|
}
|
|
|
|
if ok, err := r.onAccountSaveMusic(reqCtx, &tg.AccountSaveMusicRequest{
|
|
ID: &tg.InputDocument{ID: music2.ID, AccessHash: music2.AccessHash},
|
|
}); err != nil || !ok {
|
|
t.Fatalf("move second to top = ok %v err %v, want true/nil", ok, err)
|
|
}
|
|
idsBox, err = r.onAccountGetSavedMusicIDs(reqCtx, 0)
|
|
if err != nil {
|
|
t.Fatalf("get reordered ids: %v", err)
|
|
}
|
|
ids = idsBox.(*tg.AccountSavedMusicIDs)
|
|
if !int64SliceEqual(ids.IDs, []int64{music2.ID, music1.ID}) {
|
|
t.Fatalf("reordered ids = %v, want [second first]", ids.IDs)
|
|
}
|
|
unsave := &tg.AccountSaveMusicRequest{ID: &tg.InputDocument{ID: music2.ID, AccessHash: music2.AccessHash}}
|
|
unsave.SetUnsave(true)
|
|
if ok, err := r.onAccountSaveMusic(reqCtx, unsave); err != nil || !ok {
|
|
t.Fatalf("unsave second = ok %v err %v, want true/nil", ok, err)
|
|
}
|
|
idsBox, err = r.onAccountGetSavedMusicIDs(reqCtx, 0)
|
|
if err != nil {
|
|
t.Fatalf("get ids after unsave: %v", err)
|
|
}
|
|
ids = idsBox.(*tg.AccountSavedMusicIDs)
|
|
if !int64SliceEqual(ids.IDs, []int64{music1.ID}) {
|
|
t.Fatalf("ids after unsave = %v, want [first]", ids.IDs)
|
|
}
|
|
if _, err := r.onAccountSaveMusic(reqCtx, &tg.AccountSaveMusicRequest{
|
|
ID: &tg.InputDocument{ID: voice.ID, AccessHash: voice.AccessHash},
|
|
}); err == nil || !strings.Contains(err.Error(), "DOCUMENT_INVALID") {
|
|
t.Fatalf("save voice err = %v, want DOCUMENT_INVALID", err)
|
|
}
|
|
}
|
|
|
|
func TestMessagesSearchMusicFiltersAudioDocuments(t *testing.T) {
|
|
ctx := context.Background()
|
|
userStore := memory.NewUserStore()
|
|
alice, err := userStore.Create(ctx, domain.User{AccessHash: 21, Phone: "15550000021", FirstName: "Alice"})
|
|
if err != nil {
|
|
t.Fatalf("create alice: %v", err)
|
|
}
|
|
bob, err := userStore.Create(ctx, domain.User{AccessHash: 22, Phone: "15550000022", FirstName: "Bob"})
|
|
if err != nil {
|
|
t.Fatalf("create bob: %v", err)
|
|
}
|
|
dialogs := memory.NewDialogStore()
|
|
messageStore := memory.NewMessageStore(dialogs)
|
|
music := testMusicDocument(201, 2001, "Song")
|
|
voice := domain.Document{ID: 202, AccessHash: 2002, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrAudio, Voice: true, AudioDuration: 5}}}
|
|
plain := domain.Document{ID: 203, AccessHash: 2003, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrFilename, FileName: "file.bin"}}}
|
|
for i, media := range []*domain.MessageMedia{
|
|
{Kind: domain.MessageMediaKindDocument, Document: &plain},
|
|
{Kind: domain.MessageMediaKindDocument, Document: &voice, Voice: true},
|
|
{Kind: domain.MessageMediaKindDocument, Document: &music},
|
|
} {
|
|
if _, err := messageStore.SendPrivateText(ctx, domain.SendPrivateTextRequest{
|
|
SenderUserID: alice.ID,
|
|
RecipientUserID: bob.ID,
|
|
RandomID: int64(300 + i),
|
|
Message: "",
|
|
Media: media,
|
|
Date: 1700000000 + i,
|
|
}); err != nil {
|
|
t.Fatalf("send media %d: %v", i, err)
|
|
}
|
|
}
|
|
r := New(Config{}, Deps{
|
|
Users: appusers.NewService(userStore),
|
|
Messages: appmessages.NewService(messageStore, dialogs),
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
|
|
var payload bin.Buffer
|
|
if err := (&tg.MessagesSearchRequest{
|
|
Peer: &tg.InputPeerUser{UserID: alice.ID, AccessHash: alice.AccessHash},
|
|
Filter: &tg.InputMessagesFilterMusic{},
|
|
Limit: 10,
|
|
}).Encode(&payload); err != nil {
|
|
t.Fatalf("encode messages.search: %v", err)
|
|
}
|
|
got, err := r.Dispatch(WithUserID(ctx, bob.ID), [8]byte{}, 0, &payload)
|
|
if err != nil {
|
|
t.Fatalf("messages.search music: %v", err)
|
|
}
|
|
messages, _, _ := searchMessagesPayload(t, got)
|
|
if len(messages) != 1 {
|
|
t.Fatalf("music search messages = %d, want 1", len(messages))
|
|
}
|
|
msg, ok := messages[0].(*tg.Message)
|
|
if !ok {
|
|
t.Fatalf("music search message = %T, want *tg.Message", messages[0])
|
|
}
|
|
docMedia, ok := msg.Media.(*tg.MessageMediaDocument)
|
|
if !ok || documentClassID(docMedia.Document) != music.ID {
|
|
t.Fatalf("music search media = %T %+v, want music document", msg.Media, msg.Media)
|
|
}
|
|
}
|
|
|
|
func TestMessagesSearchGlobalMusicAllowsEmptyQuery(t *testing.T) {
|
|
ctx := context.Background()
|
|
userStore := memory.NewUserStore()
|
|
alice, err := userStore.Create(ctx, domain.User{AccessHash: 31, Phone: "15550000031", FirstName: "Alice"})
|
|
if err != nil {
|
|
t.Fatalf("create alice: %v", err)
|
|
}
|
|
bob, err := userStore.Create(ctx, domain.User{AccessHash: 32, Phone: "15550000032", FirstName: "Bob"})
|
|
if err != nil {
|
|
t.Fatalf("create bob: %v", err)
|
|
}
|
|
dialogs := memory.NewDialogStore()
|
|
messageStore := memory.NewMessageStore(dialogs)
|
|
music := testMusicDocument(301, 3001, "Global Song")
|
|
voice := domain.Document{ID: 302, AccessHash: 3002, Attributes: []domain.DocumentAttribute{{Kind: domain.DocAttrAudio, Voice: true, AudioDuration: 5}}}
|
|
for i, media := range []*domain.MessageMedia{
|
|
{Kind: domain.MessageMediaKindDocument, Document: &voice, Voice: true},
|
|
{Kind: domain.MessageMediaKindDocument, Document: &music},
|
|
} {
|
|
if _, err := messageStore.SendPrivateText(ctx, domain.SendPrivateTextRequest{
|
|
SenderUserID: alice.ID,
|
|
RecipientUserID: bob.ID,
|
|
RandomID: int64(400 + i),
|
|
Media: media,
|
|
Date: 1700000100 + i,
|
|
}); err != nil {
|
|
t.Fatalf("send media %d: %v", i, err)
|
|
}
|
|
}
|
|
r := New(Config{}, Deps{
|
|
Users: appusers.NewService(userStore),
|
|
Messages: appmessages.NewService(messageStore, dialogs),
|
|
}, zaptest.NewLogger(t), clock.System)
|
|
reqCtx := WithUserID(ctx, bob.ID)
|
|
|
|
if _, err := r.onMessagesSearchGlobal(reqCtx, &tg.MessagesSearchGlobalRequest{
|
|
Filter: &tg.InputMessagesFilterEmpty{},
|
|
OffsetPeer: &tg.InputPeerEmpty{},
|
|
Limit: 10,
|
|
}); err == nil || !strings.Contains(err.Error(), "SEARCH_QUERY_EMPTY") {
|
|
t.Fatalf("empty non-music searchGlobal err = %v, want SEARCH_QUERY_EMPTY", err)
|
|
}
|
|
got, err := r.onMessagesSearchGlobal(reqCtx, &tg.MessagesSearchGlobalRequest{
|
|
Filter: &tg.InputMessagesFilterMusic{},
|
|
OffsetPeer: &tg.InputPeerEmpty{},
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("empty music searchGlobal: %v", err)
|
|
}
|
|
messages := messagesFromMessagesClass(t, got)
|
|
if len(messages) != 1 {
|
|
t.Fatalf("global music messages = %d, want 1", len(messages))
|
|
}
|
|
msg, ok := messages[0].(*tg.Message)
|
|
if !ok {
|
|
t.Fatalf("global music message = %T, want *tg.Message", messages[0])
|
|
}
|
|
docMedia, ok := msg.Media.(*tg.MessageMediaDocument)
|
|
if !ok || documentClassID(docMedia.Document) != music.ID {
|
|
t.Fatalf("global music media = %T %+v, want music document", msg.Media, msg.Media)
|
|
}
|
|
}
|
|
|
|
func testMusicDocument(id, accessHash int64, title string) domain.Document {
|
|
return domain.Document{
|
|
ID: id,
|
|
AccessHash: accessHash,
|
|
MimeType: "audio/mpeg",
|
|
Attributes: []domain.DocumentAttribute{
|
|
{Kind: domain.DocAttrAudio, AudioDuration: 180, Title: title, Performer: "Artist"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func documentClassID(doc tg.DocumentClass) int64 {
|
|
if d, ok := doc.(*tg.Document); ok {
|
|
return d.ID
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func int64SliceEqual(a, b []int64) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func messagesFromMessagesClass(t *testing.T, got tg.MessagesMessagesClass) []tg.MessageClass {
|
|
t.Helper()
|
|
switch v := got.(type) {
|
|
case *tg.MessagesMessages:
|
|
return v.Messages
|
|
case *tg.MessagesMessagesSlice:
|
|
return v.Messages
|
|
default:
|
|
t.Fatalf("messages class = %T, want messages/messagesSlice", got)
|
|
return nil
|
|
}
|
|
}
|