owpengram-server/internal/store/postgres/message_saved_reactions_integration_test.go
2026-07-25 19:23:19 +08:00

177 lines
5.9 KiB
Go

package postgres
import (
"context"
"testing"
"time"
"telesrv/internal/domain"
)
func TestSavedMessageTagsPostgresAssignmentCountsSearchAndDelete(t *testing.T) {
pool := testPool(t)
ctx := context.Background()
suffix := randomSuffix(t)
users := NewUserStore(pool)
user, err := users.Create(ctx, domain.User{
AccessHash: 1,
Phone: "+1777" + suffix + "01",
FirstName: "SavedTags",
})
if err != nil {
t.Fatalf("create saved-tag user: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(ctx, "DELETE FROM saved_message_reaction_tags WHERE user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM user_saved_reaction_tags WHERE user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM message_boxes WHERE owner_user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM private_messages WHERE sender_user_id = $1 OR recipient_user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM user_update_events WHERE user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM dialogs WHERE user_id = $1", user.ID)
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", user.ID)
})
messages := NewMessageStore(pool)
self := domain.Peer{Type: domain.PeerTypeUser, ID: user.ID}
peerA := domain.Peer{Type: domain.PeerTypeUser, ID: user.ID}
peerB := domain.Peer{Type: domain.PeerTypeChannel, ID: 90001}
create := func(body string, savedPeer domain.Peer) domain.Message {
msg, err := messages.Create(ctx, domain.Message{
OwnerUserID: user.ID,
Peer: self,
From: self,
Date: int(time.Now().Unix()),
Body: body,
})
if err != nil {
t.Fatalf("create saved message: %v", err)
}
if _, err := pool.Exec(ctx, `
UPDATE message_boxes
SET saved_peer_type = $3, saved_peer_id = $4
WHERE owner_user_id = $1 AND box_id = $2`,
user.ID, msg.ID, string(savedPeer.Type), savedPeer.ID); err != nil {
t.Fatalf("set saved peer: %v", err)
}
msg.SavedPeer = savedPeer
return msg
}
first := create("first", peerA)
second := create("second", peerA)
third := create("third", peerB)
thumb := domain.MessageReaction{Type: domain.MessageReactionEmoji, Emoticon: "👍"}
custom := domain.MessageReaction{Type: domain.MessageReactionCustomEmoji, DocumentID: 70001}
set := func(msg domain.Message, reactions ...domain.MessageReaction) {
t.Helper()
result, err := messages.SetMessageReactions(ctx, domain.SetPrivateMessageReactionsRequest{
UserID: user.ID,
Peer: self,
MessageID: msg.ID,
Reactions: reactions,
ReactionsPerUserMax: 3,
})
if err != nil {
t.Fatalf("set saved tags on %d: %v", msg.ID, err)
}
if len(result.Messages) != 1 || result.Messages[0].Reactions == nil ||
!result.Messages[0].Reactions.AsTags {
t.Fatalf("set saved tags result = %+v", result)
}
}
set(first, thumb)
set(second, thumb, custom)
set(third, custom)
if err := messages.UpsertSavedReactionTag(ctx, domain.SavedReactionTag{
UserID: user.ID, Reaction: custom, Title: "Custom",
}); err != nil {
t.Fatalf("rename custom saved tag: %v", err)
}
global, err := messages.ListSavedReactionTags(ctx, domain.SavedReactionTagsRequest{
UserID: user.ID, Limit: 100,
})
if err != nil {
t.Fatalf("list global saved tags: %v", err)
}
assertPostgresSavedTag(t, global, thumb, 2, "")
assertPostgresSavedTag(t, global, custom, 2, "Custom")
perPeer, err := messages.ListSavedReactionTags(ctx, domain.SavedReactionTagsRequest{
UserID: user.ID, SavedPeer: peerA, Limit: 100,
})
if err != nil {
t.Fatalf("list per-peer saved tags: %v", err)
}
assertPostgresSavedTag(t, perPeer, thumb, 2, "")
assertPostgresSavedTag(t, perPeer, custom, 1, "")
search, err := messages.ListByUser(ctx, user.ID, domain.MessageFilter{
HasPeer: true,
Peer: self,
SavedPeer: peerA,
SavedReactions: []domain.MessageReaction{custom},
NeedTotalCount: true,
Limit: 10,
})
if err != nil {
t.Fatalf("search saved tag: %v", err)
}
if len(search.Messages) != 1 || search.Messages[0].ID != second.ID ||
search.Messages[0].Reactions == nil || !search.Messages[0].Reactions.AsTags {
t.Fatalf("saved tag search = %+v, want second message", search.Messages)
}
searchAny, err := messages.ListByUser(ctx, user.ID, domain.MessageFilter{
HasPeer: true,
Peer: self,
SavedReactions: []domain.MessageReaction{thumb, custom},
NeedTotalCount: true,
Limit: 10,
})
if err != nil {
t.Fatalf("search any saved tag: %v", err)
}
if len(searchAny.Messages) != 3 || searchAny.Count != 3 {
t.Fatalf("saved tag OR search = count %d messages %+v, want all three", searchAny.Count, searchAny.Messages)
}
var reactionEvents int
if err := pool.QueryRow(ctx, `
SELECT COUNT(*)::int
FROM user_update_events
WHERE user_id = $1 AND event_type = 'message_reactions'`, user.ID).Scan(&reactionEvents); err != nil {
t.Fatalf("count reaction events: %v", err)
}
if reactionEvents != 0 {
t.Fatalf("reaction durable events = %d, want 0", reactionEvents)
}
if _, err := messages.DeleteMessages(ctx, domain.DeleteMessagesRequest{
OwnerUserID: user.ID,
IDs: []int{second.ID},
Date: int(time.Now().Unix()),
}); err != nil {
t.Fatalf("delete tagged saved message: %v", err)
}
global, err = messages.ListSavedReactionTags(ctx, domain.SavedReactionTagsRequest{
UserID: user.ID, Limit: 100,
})
if err != nil {
t.Fatalf("list tags after delete: %v", err)
}
assertPostgresSavedTag(t, global, thumb, 1, "")
assertPostgresSavedTag(t, global, custom, 1, "Custom")
}
func assertPostgresSavedTag(t *testing.T, tags []domain.SavedReactionTag, reaction domain.MessageReaction, count int, title string) {
t.Helper()
for _, tag := range tags {
if tag.Reaction.Key() == reaction.Key() {
if tag.Count != count || tag.Title != title {
t.Fatalf("tag %s = %+v, want count=%d title=%q", reaction.Key(), tag, count, title)
}
return
}
}
t.Fatalf("tag %s not found in %+v", reaction.Key(), tags)
}