140 lines
4.6 KiB
Go
140 lines
4.6 KiB
Go
package postgres
|
||
|
||
import (
|
||
"context"
|
||
"reflect"
|
||
"testing"
|
||
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// TestChannelPollFanoutViewsPostgres 验证 Phase 4 poll 模板化的 postgres 路径:新批量 SQL
|
||
// (batchPollViewerOptions / batchChannelMemberAvailableMinID + pollViewerAggregates(viewer=0))
|
||
// 正确运行,且 ChannelPollFanoutViews 对每个 viewer 与逐 viewer GetChannelMessages(旧 fan-out 路径)
|
||
// 的 enrich poll 字节等价、可见性一致。门控于 TELESRV_TEST_POSTGRES_DSN。
|
||
func TestChannelPollFanoutViewsPostgres(t *testing.T) {
|
||
pool := testPool(t)
|
||
ctx := context.Background()
|
||
suffix := randomSuffix(t)
|
||
users := NewUserStore(pool)
|
||
|
||
owner, err := users.Create(ctx, domain.User{AccessHash: 71, Phone: "+1893" + suffix + "01", FirstName: "PollOwner"})
|
||
if err != nil {
|
||
t.Fatalf("create owner: %v", err)
|
||
}
|
||
voted, err := users.Create(ctx, domain.User{AccessHash: 72, Phone: "+1893" + suffix + "02", FirstName: "PollVoted"})
|
||
if err != nil {
|
||
t.Fatalf("create voted: %v", err)
|
||
}
|
||
unvoted, err := users.Create(ctx, domain.User{AccessHash: 73, Phone: "+1893" + suffix + "03", FirstName: "PollUnvoted"})
|
||
if err != nil {
|
||
t.Fatalf("create unvoted: %v", err)
|
||
}
|
||
stranger, err := users.Create(ctx, domain.User{AccessHash: 74, Phone: "+1893" + suffix + "04", FirstName: "PollStranger"})
|
||
if err != nil {
|
||
t.Fatalf("create stranger: %v", err)
|
||
}
|
||
|
||
channels := NewChannelStore(pool)
|
||
var channelID int64
|
||
t.Cleanup(func() {
|
||
if channelID != 0 {
|
||
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", channelID)
|
||
}
|
||
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, voted.ID, unvoted.ID, stranger.ID})
|
||
})
|
||
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
||
CreatorUserID: owner.ID,
|
||
Title: "Poll Fanout " + suffix,
|
||
Megagroup: true,
|
||
MemberUserIDs: []int64{voted.ID, unvoted.ID},
|
||
Date: 1700002000,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("create channel: %v", err)
|
||
}
|
||
channelID = created.Channel.ID
|
||
|
||
pollID := int64(9300000000) + (channelID % 1000)
|
||
opt1, opt2 := []byte{1}, []byte{2}
|
||
polls := NewPollStore(pool)
|
||
if err := polls.CreatePoll(ctx, domain.PollDefinition{
|
||
ID: pollID,
|
||
CreatorUserID: owner.ID,
|
||
Options: [][]byte{opt1, opt2},
|
||
PublicVoters: true,
|
||
}); err != nil {
|
||
t.Fatalf("create poll: %v", err)
|
||
}
|
||
sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
||
UserID: owner.ID,
|
||
ChannelID: channelID,
|
||
RandomID: 93_001,
|
||
Date: 1700002001,
|
||
Media: &domain.MessageMedia{
|
||
Kind: domain.MessageMediaKindPoll,
|
||
Poll: &domain.MessagePoll{
|
||
ID: pollID,
|
||
Question: "Q?",
|
||
Answers: []domain.MessagePollAnswer{{Text: "A", Option: opt1}, {Text: "B", Option: opt2}},
|
||
PublicVoters: true,
|
||
},
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("send poll message: %v", err)
|
||
}
|
||
msgID := sent.Message.ID
|
||
if _, err := channels.VoteChannelMessagePoll(ctx, domain.VoteChannelMessagePollRequest{
|
||
UserID: voted.ID, ChannelID: channelID, MessageID: msgID, Options: [][]byte{opt1}, Date: 1700002002,
|
||
}); err != nil {
|
||
t.Fatalf("vote: %v", err)
|
||
}
|
||
|
||
viewers := []int64{owner.ID, voted.ID, unvoted.ID, stranger.ID}
|
||
views, err := channels.ChannelPollFanoutViews(ctx, channelID, msgID, viewers, 1700002100)
|
||
if err != nil {
|
||
t.Fatalf("ChannelPollFanoutViews: %v", err)
|
||
}
|
||
if !views.Found {
|
||
t.Fatal("Found=false, want true")
|
||
}
|
||
for _, viewer := range viewers {
|
||
got, evaluated := views.Polls[viewer]
|
||
if !evaluated {
|
||
t.Fatalf("viewer %d not evaluated", viewer)
|
||
}
|
||
ref, err := channels.GetChannelMessages(ctx, viewer, channelID, []int{msgID})
|
||
var refPoll *domain.MessagePoll
|
||
if err == nil {
|
||
for _, m := range ref.Messages {
|
||
if m.ID == msgID && m.Media != nil {
|
||
refPoll = m.Media.Poll
|
||
}
|
||
}
|
||
}
|
||
if (got != nil) != (refPoll != nil) {
|
||
t.Fatalf("viewer %d visibility mismatch: batch nil=%v old nil=%v (err=%v)", viewer, got == nil, refPoll == nil, err)
|
||
}
|
||
if got != nil && !reflect.DeepEqual(got.Results, refPoll.Results) {
|
||
t.Fatalf("viewer %d Results mismatch:\n batch=%+v\n old=%+v", viewer, got.Results, refPoll.Results)
|
||
}
|
||
}
|
||
// members 可见、stranger(非成员)不可见。
|
||
if views.Polls[owner.ID] == nil || views.Polls[voted.ID] == nil || views.Polls[unvoted.ID] == nil {
|
||
t.Fatal("members should be visible")
|
||
}
|
||
if views.Polls[stranger.ID] != nil {
|
||
t.Fatal("non-member stranger must be invisible (nil)")
|
||
}
|
||
// voted 视角应见自己 chosen。
|
||
sawChosen := false
|
||
for _, v := range views.Polls[voted.ID].Results.Voters {
|
||
if v.Chosen {
|
||
sawChosen = true
|
||
}
|
||
}
|
||
if !sawChosen {
|
||
t.Fatal("voted viewer should see own chosen option")
|
||
}
|
||
}
|