chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
139
internal/store/memory/channel_poll_fanout_test.go
Normal file
139
internal/store/memory/channel_poll_fanout_test.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
// TestChannelPollFanoutViewsByteEquivalentToGetMessages 锁定 Phase 4 poll 模板化的安全网:批量
|
||||
// ChannelPollFanoutViews 对每个 viewer 产出的 enrich poll,必须与逐 viewer GetChannelMessages(旧
|
||||
// fan-out 路径)字节等价;且可见性判定一致(不可见 viewer 在批量里为 nil、在旧路径里取不到消息)。
|
||||
// 覆盖:作者(创建者)、已投票非作者、未投票非作者、hide_results 期非创建者、late-joiner(AvailableMinID)、
|
||||
// 被封成员、ViewMessages 受限成员、非成员。
|
||||
func TestChannelPollFanoutViewsByteEquivalentToGetMessages(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const (
|
||||
channelID = int64(8000000001)
|
||||
msgID = 50
|
||||
pollID = int64(9000000001)
|
||||
creator = int64(7001)
|
||||
votedA = int64(7002)
|
||||
votedB = int64(7003)
|
||||
unvoted = int64(7004)
|
||||
late = int64(7005) // AvailableMinID >= msgID(pre-history 隐藏)
|
||||
banned = int64(7006)
|
||||
restricted = int64(7007) // active 但 BannedRights.ViewMessages
|
||||
nonMember = int64(7008)
|
||||
)
|
||||
opt1, opt2, opt3 := []byte{1}, []byte{2}, []byte{3}
|
||||
|
||||
for _, hideResults := range []bool{false, true} {
|
||||
t.Run(map[bool]string{false: "visible_counts", true: "hide_results"}[hideResults], func(t *testing.T) {
|
||||
channels := NewChannelStore()
|
||||
polls := NewPollStore()
|
||||
channels.AttachPollStore(polls)
|
||||
|
||||
channels.channels[channelID] = domain.Channel{ID: channelID, CreatorUserID: creator, Megagroup: true, PreHistoryHidden: true}
|
||||
channels.members[channelID] = map[int64]domain.ChannelMember{
|
||||
creator: {UserID: creator, Status: domain.ChannelMemberActive, Role: domain.ChannelRoleCreator},
|
||||
votedA: {UserID: votedA, Status: domain.ChannelMemberActive},
|
||||
votedB: {UserID: votedB, Status: domain.ChannelMemberActive},
|
||||
unvoted: {UserID: unvoted, Status: domain.ChannelMemberActive},
|
||||
late: {UserID: late, Status: domain.ChannelMemberActive, AvailableMinID: msgID + 10},
|
||||
banned: {UserID: banned, Status: domain.ChannelMemberBanned},
|
||||
restricted: {UserID: restricted, Status: domain.ChannelMemberActive, BannedRights: domain.ChannelBannedRights{ViewMessages: true}},
|
||||
}
|
||||
|
||||
def := domain.PollDefinition{
|
||||
ID: pollID,
|
||||
CreatorUserID: creator,
|
||||
Options: [][]byte{opt1, opt2, opt3},
|
||||
PublicVoters: true,
|
||||
MultipleChoice: false,
|
||||
HideResultsUntilClose: hideResults,
|
||||
}
|
||||
if err := polls.CreatePoll(ctx, def); err != nil {
|
||||
t.Fatalf("create poll: %v", err)
|
||||
}
|
||||
basePoll := &domain.MessagePoll{
|
||||
ID: pollID,
|
||||
Question: "Q?",
|
||||
Answers: []domain.MessagePollAnswer{{Text: "A", Option: opt1}, {Text: "B", Option: opt2}, {Text: "C", Option: opt3}},
|
||||
PublicVoters: true,
|
||||
HideResultsUntilClose: hideResults,
|
||||
}
|
||||
channels.messages[channelID] = []domain.ChannelMessage{{
|
||||
ChannelID: channelID,
|
||||
ID: msgID,
|
||||
SenderUserID: creator,
|
||||
Media: &domain.MessageMedia{Kind: domain.MessageMediaKindPoll, Poll: basePoll},
|
||||
}}
|
||||
if err := polls.Vote(pollID, votedA, [][]byte{opt1}, 100); err != nil {
|
||||
t.Fatalf("vote A: %v", err)
|
||||
}
|
||||
if err := polls.Vote(pollID, votedB, [][]byte{opt2}, 101); err != nil {
|
||||
t.Fatalf("vote B: %v", err)
|
||||
}
|
||||
|
||||
viewers := []int64{creator, votedA, votedB, unvoted, late, banned, restricted, nonMember}
|
||||
views, err := channels.ChannelPollFanoutViews(ctx, channelID, msgID, viewers, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("ChannelPollFanoutViews: %v", err)
|
||||
}
|
||||
if !views.Found {
|
||||
t.Fatal("poll fan-out views Found=false, want true")
|
||||
}
|
||||
|
||||
for _, viewer := range viewers {
|
||||
got, evaluated := views.Polls[viewer]
|
||||
if !evaluated {
|
||||
t.Fatalf("viewer %d not evaluated in batch (all passed viewers must be evaluated)", viewer)
|
||||
}
|
||||
// 旧路径参考:GetChannelMessages 按 viewer 取消息(含可见性 + per-viewer poll enrich)。
|
||||
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, GetMessages nil=%v (err=%v)", viewer, got == nil, refPoll == nil, err)
|
||||
}
|
||||
if got != nil && !reflect.DeepEqual(got.Results, refPoll.Results) {
|
||||
t.Fatalf("viewer %d poll Results mismatch:\n batch=%+v\n old=%+v", viewer, got.Results, refPoll.Results)
|
||||
}
|
||||
}
|
||||
|
||||
// 显式断言可见性分类,防 GetMessages 与批量同时漏判。
|
||||
if views.Polls[creator] == nil || views.Polls[votedA] == nil || views.Polls[votedB] == nil || views.Polls[unvoted] == nil {
|
||||
t.Fatalf("active members should be visible: creator/votedA/votedB/unvoted")
|
||||
}
|
||||
if views.Polls[late] != nil || views.Polls[banned] != nil || views.Polls[restricted] != nil || views.Polls[nonMember] != nil {
|
||||
t.Fatalf("late/banned/restricted/nonMember must be invisible (nil)")
|
||||
}
|
||||
// hide_results 期:非创建者已投票者计数应被隐藏(Voters=0),创建者可见真实计数。
|
||||
if hideResults {
|
||||
for _, v := range views.Polls[votedA].Results.Voters {
|
||||
if v.Voters != 0 {
|
||||
t.Fatalf("hide_results: non-creator votedA should see Voters=0, got %d", v.Voters)
|
||||
}
|
||||
}
|
||||
sawCount := false
|
||||
for _, v := range views.Polls[creator].Results.Voters {
|
||||
if v.Voters > 0 {
|
||||
sawCount = true
|
||||
}
|
||||
}
|
||||
if !sawCount {
|
||||
t.Fatal("hide_results: creator should see real counts")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue