chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
256
internal/store/postgres/channel_member_rank_integration_test.go
Normal file
256
internal/store/postgres/channel_member_rank_integration_test.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"telesrv/internal/domain"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestChannelStoreEditMemberRank 验证 rank-only 编辑在 PG 上的持久化、权限矩阵、
|
||||
// 以及 participant_edit_rank 通过 channel_admin_log_events_type_check 约束(0086)。
|
||||
func TestChannelStoreEditMemberRank(t *testing.T) {
|
||||
pool := testPool(t)
|
||||
ctx := context.Background()
|
||||
suffix := randomSuffix(t)
|
||||
|
||||
users := NewUserStore(pool)
|
||||
owner, err := users.Create(ctx, domain.User{AccessHash: 141, Phone: "+1889" + suffix + "01", FirstName: "RankOwner"})
|
||||
if err != nil {
|
||||
t.Fatalf("create owner: %v", err)
|
||||
}
|
||||
member, err := users.Create(ctx, domain.User{AccessHash: 142, Phone: "+1889" + suffix + "02", FirstName: "RankMember"})
|
||||
if err != nil {
|
||||
t.Fatalf("create member: %v", err)
|
||||
}
|
||||
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, member.ID})
|
||||
})
|
||||
|
||||
channels := NewChannelStore(pool)
|
||||
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: owner.ID,
|
||||
Title: "Rank Integration",
|
||||
Megagroup: true,
|
||||
MemberUserIDs: []int64{member.ID},
|
||||
Date: 1700009000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create channel: %v", err)
|
||||
}
|
||||
channelID = created.Channel.ID
|
||||
|
||||
// creator 给普通成员设 tag:角色不变、rank 持久化。
|
||||
res, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: member.ID,
|
||||
Rank: "navigator",
|
||||
Date: 1700009001,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("creator edits member rank: %v", err)
|
||||
}
|
||||
if res.Participant.Role != domain.ChannelRoleMember || res.Participant.Rank != "navigator" {
|
||||
t.Fatalf("participant after rank edit = %+v, want member role with navigator rank", res.Participant)
|
||||
}
|
||||
stored, err := channels.GetParticipant(ctx, owner.ID, channelID, member.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get participant: %v", err)
|
||||
}
|
||||
if stored.Role != domain.ChannelRoleMember || stored.Rank != "navigator" {
|
||||
t.Fatalf("stored participant = %+v, want member role with navigator rank", stored)
|
||||
}
|
||||
|
||||
// 普通成员改自己:开关开 → 成功;default_banned_rights.edit_rank=true → 拒绝。
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: member.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: member.ID,
|
||||
Rank: "pilot",
|
||||
Date: 1700009002,
|
||||
}); err != nil {
|
||||
t.Fatalf("member edits own rank: %v", err)
|
||||
}
|
||||
if _, err := channels.EditChannelDefaultBannedRights(ctx, domain.EditChannelDefaultBannedRightsRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
BannedRights: domain.ChannelBannedRights{EditRank: true},
|
||||
Date: 1700009003,
|
||||
}); err != nil {
|
||||
t.Fatalf("disable member tags: %v", err)
|
||||
}
|
||||
view, err := channels.GetChannel(ctx, member.ID, channelID)
|
||||
if err != nil {
|
||||
t.Fatalf("get channel: %v", err)
|
||||
}
|
||||
if !view.Channel.DefaultBannedRights.EditRank {
|
||||
t.Fatalf("default banned rights = %+v, want edit_rank=true round-trip", view.Channel.DefaultBannedRights)
|
||||
}
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: member.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: member.ID,
|
||||
Rank: "pilot2",
|
||||
Date: 1700009004,
|
||||
}); !errors.Is(err, domain.ErrChannelRightForbidden) {
|
||||
t.Fatalf("member self edit with switch off err = %v, want ErrChannelRightForbidden", err)
|
||||
}
|
||||
// 普通成员改别人 → 需要管理权限。
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: member.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: owner.ID,
|
||||
Rank: "x",
|
||||
Date: 1700009005,
|
||||
}); !errors.Is(err, domain.ErrChannelUserCreator) && !errors.Is(err, domain.ErrChannelAdminRequired) {
|
||||
t.Fatalf("member edits creator err = %v, want creator/admin-required rejection", err)
|
||||
}
|
||||
|
||||
// manage_ranks 管理员权限位经 PG round-trip。
|
||||
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: member.ID,
|
||||
AdminRights: domain.ChannelAdminRights{ChangeInfo: true, ManageRanks: true},
|
||||
Date: 1700009006,
|
||||
}); err != nil {
|
||||
t.Fatalf("promote with manage_ranks: %v", err)
|
||||
}
|
||||
promoted, err := channels.GetParticipant(ctx, owner.ID, channelID, member.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get promoted participant: %v", err)
|
||||
}
|
||||
if !promoted.AdminRights.ManageRanks {
|
||||
t.Fatalf("promoted admin rights = %+v, want manage_ranks=true round-trip", promoted.AdminRights)
|
||||
}
|
||||
|
||||
// admin log:participant_edit_rank 行已通过 0086 CHECK 落库,filter 可检索。
|
||||
log, err := channels.ListAdminLog(ctx, domain.ChannelAdminLogRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
Limit: 20,
|
||||
Filter: domain.ChannelAdminLogFilter{EditRank: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("list admin log: %v", err)
|
||||
}
|
||||
if len(log.Events) != 2 {
|
||||
t.Fatalf("edit rank admin log events = %d (%+v), want 2", len(log.Events), log.Events)
|
||||
}
|
||||
for _, event := range log.Events {
|
||||
if event.Type != domain.ChannelAdminLogParticipantEditRank {
|
||||
t.Fatalf("admin log event type = %s, want participant_edit_rank", event.Type)
|
||||
}
|
||||
if event.Participant == nil || event.Participant.UserID == 0 {
|
||||
t.Fatalf("admin log event participant missing: %+v", event)
|
||||
}
|
||||
}
|
||||
if log.Events[0].NewString != "pilot" || log.Events[1].NewString != "navigator" {
|
||||
t.Fatalf("admin log ranks = %q,%q, want pilot,navigator (desc order)", log.Events[0].NewString, log.Events[1].NewString)
|
||||
}
|
||||
|
||||
// 重进是全新 participant:admin(manage_ranks + rank)退群重进后为普通成员、
|
||||
// 无 admin rights、无 rank;部分限制名单独立于在群与否、重进后仍生效。
|
||||
if _, err := channels.EditChannelBanned(ctx, domain.EditChannelBannedRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
Participant: domain.Peer{Type: domain.PeerTypeUser, ID: member.ID},
|
||||
BannedRights: domain.ChannelBannedRights{SendMessages: true},
|
||||
Date: 1700009007,
|
||||
}); err != nil {
|
||||
t.Fatalf("restrict member: %v", err)
|
||||
}
|
||||
if _, err := channels.LeaveChannel(ctx, channelID, member.ID, 1700009008); err != nil {
|
||||
t.Fatalf("member leaves: %v", err)
|
||||
}
|
||||
if _, err := channels.JoinChannel(ctx, channelID, member.ID, 1700009009); err != nil {
|
||||
t.Fatalf("member rejoins: %v", err)
|
||||
}
|
||||
rejoined, err := channels.GetParticipant(ctx, owner.ID, channelID, member.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get rejoined member: %v", err)
|
||||
}
|
||||
if rejoined.Role != domain.ChannelRoleMember || rejoined.Rank != "" || rejoined.AdminRights != (domain.ChannelAdminRights{}) {
|
||||
t.Fatalf("rejoined member = %+v, want plain member without rank/admin rights", rejoined)
|
||||
}
|
||||
if !rejoined.BannedRights.SendMessages {
|
||||
t.Fatalf("rejoined member banned rights = %+v, want partial restriction kept", rejoined.BannedRights)
|
||||
}
|
||||
if rejoined.JoinedAt != 1700009009 || rejoined.InviterUserID != 0 {
|
||||
t.Fatalf("rejoined member joined_at/inviter = %d/%d, want fresh join date and no inviter", rejoined.JoinedAt, rejoined.InviterUserID)
|
||||
}
|
||||
|
||||
// creator 离开会把 owner 交给其他活跃成员;旧 creator 重进是普通成员,
|
||||
// 不保留 creator tag。
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: channelID,
|
||||
MemberID: owner.ID,
|
||||
Rank: "captain",
|
||||
Date: 1700009010,
|
||||
}); err != nil {
|
||||
t.Fatalf("creator sets own rank: %v", err)
|
||||
}
|
||||
if _, err := channels.LeaveChannel(ctx, channelID, owner.ID, 1700009011); err != nil {
|
||||
t.Fatalf("creator leaves: %v", err)
|
||||
}
|
||||
newCreator, err := channels.GetParticipant(ctx, member.ID, channelID, member.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get future creator: %v", err)
|
||||
}
|
||||
if newCreator.Role != domain.ChannelRoleCreator {
|
||||
t.Fatalf("future creator = %+v, want creator role", newCreator)
|
||||
}
|
||||
if _, err := channels.JoinChannel(ctx, channelID, owner.ID, 1700009012); err != nil {
|
||||
t.Fatalf("creator rejoins: %v", err)
|
||||
}
|
||||
creatorBack, err := channels.GetParticipant(ctx, member.ID, channelID, owner.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get rejoined old creator: %v", err)
|
||||
}
|
||||
if creatorBack.Role != domain.ChannelRoleMember || creatorBack.Rank != "" {
|
||||
t.Fatalf("rejoined old creator = %+v, want plain member without rank", creatorBack)
|
||||
}
|
||||
|
||||
// 成员 Tag 是 megagroup 专属:broadcast 上 creator/self 一律 MEGAGROUP_ID_INVALID。
|
||||
var broadcastID int64
|
||||
t.Cleanup(func() {
|
||||
if broadcastID != 0 {
|
||||
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", broadcastID)
|
||||
}
|
||||
})
|
||||
broadcast, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
||||
CreatorUserID: owner.ID,
|
||||
Title: "Rank Broadcast",
|
||||
Broadcast: true,
|
||||
MemberUserIDs: []int64{member.ID},
|
||||
Date: 1700009100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create broadcast: %v", err)
|
||||
}
|
||||
broadcastID = broadcast.Channel.ID
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: owner.ID,
|
||||
ChannelID: broadcastID,
|
||||
MemberID: member.ID,
|
||||
Rank: "vip",
|
||||
Date: 1700009101,
|
||||
}); !errors.Is(err, domain.ErrMegagroupIDInvalid) {
|
||||
t.Fatalf("creator tags broadcast subscriber err = %v, want ErrMegagroupIDInvalid", err)
|
||||
}
|
||||
if _, err := channels.EditChannelMemberRank(ctx, domain.EditChannelMemberRankRequest{
|
||||
UserID: member.ID,
|
||||
ChannelID: broadcastID,
|
||||
MemberID: member.ID,
|
||||
Rank: "fan",
|
||||
Date: 1700009102,
|
||||
}); !errors.Is(err, domain.ErrMegagroupIDInvalid) {
|
||||
t.Fatalf("broadcast subscriber tags self err = %v, want ErrMegagroupIDInvalid", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue