856 lines
30 KiB
Go
856 lines
30 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"telesrv/internal/domain"
|
|
"testing"
|
|
)
|
|
|
|
func TestChannelStoreEditAboutPersistsAndChecksPermission(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 131,
|
|
Phone: "+1888" + suffix + "01",
|
|
FirstName: "AboutOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
member, err := users.Create(ctx, domain.User{
|
|
AccessHash: 132,
|
|
Phone: "+1888" + suffix + "02",
|
|
FirstName: "AboutMember",
|
|
})
|
|
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: "About " + suffix,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{member.ID},
|
|
Date: 1700000600,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelID = created.Channel.ID
|
|
|
|
if _, err := channels.EditChannelAbout(ctx, domain.EditChannelAboutRequest{
|
|
UserID: member.ID,
|
|
ChannelID: channelID,
|
|
About: "member cannot edit",
|
|
Date: 1700000601,
|
|
}); !errors.Is(err, domain.ErrChannelAdminRequired) {
|
|
t.Fatalf("EditChannelAbout by member err = %v, want ErrChannelAdminRequired", err)
|
|
}
|
|
|
|
updated, err := channels.EditChannelAbout(ctx, domain.EditChannelAboutRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
About: "owner about",
|
|
Date: 1700000602,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("EditChannelAbout by owner: %v", err)
|
|
}
|
|
if updated.About != "owner about" {
|
|
t.Fatalf("updated about = %q, want owner about", updated.About)
|
|
}
|
|
view, err := channels.GetChannel(ctx, member.ID, channelID)
|
|
if err != nil {
|
|
t.Fatalf("GetChannel by member: %v", err)
|
|
}
|
|
if view.Channel.About != "owner about" {
|
|
t.Fatalf("member view about = %q, want owner about", view.Channel.About)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreAdminLogFiltersAndSearch(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{
|
|
AccessHash: 51,
|
|
Phone: "+1999" + suffix + "01",
|
|
FirstName: "AdminLogOwner",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
friend, err := users.Create(ctx, domain.User{
|
|
AccessHash: 52,
|
|
Phone: "+1999" + suffix + "02",
|
|
FirstName: "AdminLogFriend",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create friend: %v", err)
|
|
}
|
|
invited, err := users.Create(ctx, domain.User{
|
|
AccessHash: 53,
|
|
Phone: "+1999" + suffix + "03",
|
|
FirstName: "AdminLogInvited",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create invited: %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, friend.ID, invited.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Admin Log " + suffix,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{friend.ID},
|
|
Date: 1700000500,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelID = created.Channel.ID
|
|
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
MemberID: friend.ID,
|
|
AdminRights: domain.ChannelAdminRights{
|
|
ChangeInfo: true,
|
|
InviteUsers: true,
|
|
PinMessages: true,
|
|
},
|
|
Rank: "ops",
|
|
Date: 1700000501,
|
|
}); err != nil {
|
|
t.Fatalf("edit admin: %v", err)
|
|
}
|
|
sent, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
RandomID: 501,
|
|
Message: "needle admin log body",
|
|
Date: 1700000502,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("send channel message: %v", err)
|
|
}
|
|
if _, err := channels.UpdatePinnedMessage(ctx, domain.UpdateChannelPinnedMessageRequest{
|
|
UserID: friend.ID,
|
|
ChannelID: channelID,
|
|
MessageID: sent.Message.ID,
|
|
Pinned: true,
|
|
Date: 1700000503,
|
|
}); err != nil {
|
|
t.Fatalf("pin message: %v", err)
|
|
}
|
|
if _, err := channels.InviteToChannel(ctx, channelID, friend.ID, []int64{invited.ID}, 1700000504); err != nil {
|
|
t.Fatalf("invite to channel: %v", err)
|
|
}
|
|
|
|
searched, err := channels.ListAdminLog(ctx, domain.ChannelAdminLogRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
Query: "needle",
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("search admin log: %v", err)
|
|
}
|
|
if len(searched.Events) == 0 {
|
|
t.Fatalf("search admin log returned no events, want message body match")
|
|
}
|
|
|
|
pinned, err := channels.ListAdminLog(ctx, domain.ChannelAdminLogRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
Filter: domain.ChannelAdminLogFilter{Pinned: true},
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("pinned admin log: %v", err)
|
|
}
|
|
if len(pinned.Events) != 1 || pinned.Events[0].Type != domain.ChannelAdminLogUpdatePinned || pinned.Events[0].Message == nil {
|
|
t.Fatalf("pinned events = %+v, want one update_pinned with message", pinned.Events)
|
|
}
|
|
|
|
byFriend, err := channels.ListAdminLog(ctx, domain.ChannelAdminLogRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: channelID,
|
|
AdminUserIDs: []int64{friend.ID},
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("friend admin log: %v", err)
|
|
}
|
|
if len(byFriend.Events) == 0 {
|
|
t.Fatalf("friend admin log returned no events, want pin/invite")
|
|
}
|
|
for _, event := range byFriend.Events {
|
|
if event.UserID != friend.ID {
|
|
t.Fatalf("friend admin log event actor = %d, want %d in %+v", event.UserID, friend.ID, byFriend.Events)
|
|
}
|
|
}
|
|
|
|
if _, err := channels.ListAdminLog(ctx, domain.ChannelAdminLogRequest{
|
|
UserID: invited.ID,
|
|
ChannelID: channelID,
|
|
Limit: 10,
|
|
}); err != domain.ErrChannelAdminRequired {
|
|
t.Fatalf("member admin log err = %v, want ErrChannelAdminRequired", err)
|
|
}
|
|
}
|
|
|
|
// TestChannelStoreListSendAsChannelsPostgres exercises the postgres ListSendAsChannels query against a
|
|
// real database: creator-owned and PostMessages-admin broadcast channels are included; an admin without
|
|
// PostMessages, an owned megagroup, and a member-only broadcast are excluded.
|
|
func TestChannelStoreListSendAsChannelsPostgres(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 211, Phone: "+1444" + suffix + "01", FirstName: "SendAsOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
host, err := users.Create(ctx, domain.User{AccessHash: 212, Phone: "+1444" + suffix + "02", FirstName: "SendAsHost"})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
|
|
channels := NewChannelStore(pool)
|
|
channelIDs := make([]int64, 0, 5)
|
|
t.Cleanup(func() {
|
|
for _, id := range channelIDs {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = $1", id)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, host.ID})
|
|
})
|
|
mk := func(req domain.CreateChannelRequest) domain.CreateChannelResult {
|
|
created, err := channels.CreateChannel(ctx, req)
|
|
if err != nil {
|
|
t.Fatalf("create channel %q: %v", req.Title, err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
return created
|
|
}
|
|
|
|
owned := mk(domain.CreateChannelRequest{CreatorUserID: owner.ID, Title: "SA Owned " + suffix, Broadcast: true, Date: 1700000700})
|
|
postAdmin := mk(domain.CreateChannelRequest{CreatorUserID: host.ID, Title: "SA PostAdmin " + suffix, Broadcast: true, MemberUserIDs: []int64{owner.ID}, Date: 1700000701})
|
|
editAdmin := mk(domain.CreateChannelRequest{CreatorUserID: host.ID, Title: "SA EditAdmin " + suffix, Broadcast: true, MemberUserIDs: []int64{owner.ID}, Date: 1700000702})
|
|
megagroup := mk(domain.CreateChannelRequest{CreatorUserID: owner.ID, Title: "SA Megagroup " + suffix, Megagroup: true, Date: 1700000703})
|
|
memberOnly := mk(domain.CreateChannelRequest{CreatorUserID: host.ID, Title: "SA Member " + suffix, Broadcast: true, MemberUserIDs: []int64{owner.ID}, Date: 1700000704})
|
|
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
UserID: host.ID,
|
|
ChannelID: postAdmin.Channel.ID,
|
|
MemberID: owner.ID,
|
|
AdminRights: domain.ChannelAdminRights{PostMessages: true},
|
|
Date: 1700000711,
|
|
}); err != nil {
|
|
t.Fatalf("edit post-messages admin: %v", err)
|
|
}
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
UserID: host.ID,
|
|
ChannelID: editAdmin.Channel.ID,
|
|
MemberID: owner.ID,
|
|
AdminRights: domain.ChannelAdminRights{EditMessages: true, DeleteMessages: true},
|
|
Date: 1700000712,
|
|
}); err != nil {
|
|
t.Fatalf("edit edit-only admin: %v", err)
|
|
}
|
|
|
|
list, err := channels.ListSendAsChannels(ctx, owner.ID)
|
|
if err != nil {
|
|
t.Fatalf("ListSendAsChannels: %v", err)
|
|
}
|
|
got := make(map[int64]bool, len(list))
|
|
for _, ch := range list {
|
|
got[ch.ID] = true
|
|
}
|
|
if !got[owned.Channel.ID] || !got[postAdmin.Channel.ID] {
|
|
t.Fatalf("send-as channels %v missing creator-owned %d or post-admin %d", got, owned.Channel.ID, postAdmin.Channel.ID)
|
|
}
|
|
if got[editAdmin.Channel.ID] || got[megagroup.Channel.ID] || got[memberOnly.Channel.ID] {
|
|
t.Fatalf("send-as channels %v should exclude edit-admin %d, megagroup %d, member-only %d", got, editAdmin.Channel.ID, megagroup.Channel.ID, memberOnly.Channel.ID)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreCommonChannelsOnlySharedMegagroups(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 51, Phone: "+1888" + suffix + "01", FirstName: "CommonOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
friend, err := users.Create(ctx, domain.User{AccessHash: 52, Phone: "+1888" + suffix + "02", FirstName: "CommonFriend"})
|
|
if err != nil {
|
|
t.Fatalf("create friend: %v", err)
|
|
}
|
|
other, err := users.Create(ctx, domain.User{AccessHash: 53, Phone: "+1888" + suffix + "03", FirstName: "CommonOther"})
|
|
if err != nil {
|
|
t.Fatalf("create other: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, friend.ID, other.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
create := func(title string, broadcast bool, memberIDs []int64, date int) domain.CreateChannelResult {
|
|
t.Helper()
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: title,
|
|
Broadcast: broadcast,
|
|
Megagroup: !broadcast,
|
|
MemberUserIDs: memberIDs,
|
|
Date: date,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create %s: %v", title, err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
return created
|
|
}
|
|
first := create("common one "+suffix, false, []int64{friend.ID}, 1700000800)
|
|
second := create("common two "+suffix, false, []int64{friend.ID}, 1700000801)
|
|
create("broadcast excluded "+suffix, true, []int64{friend.ID}, 1700000802)
|
|
left := create("left excluded "+suffix, false, []int64{friend.ID}, 1700000803)
|
|
if _, err := channels.LeaveChannel(ctx, left.Channel.ID, friend.ID, 1700000804); err != nil {
|
|
t.Fatalf("leave channel: %v", err)
|
|
}
|
|
create("not shared "+suffix, false, []int64{other.ID}, 1700000805)
|
|
|
|
page, err := channels.ListCommonChannels(ctx, domain.CommonChannelsRequest{
|
|
UserID: owner.ID,
|
|
TargetUserID: friend.ID,
|
|
Limit: 10,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list common channels: %v", err)
|
|
}
|
|
if page.Count != 2 || len(page.Channels) != 2 || page.Channels[0].ID != first.Channel.ID || page.Channels[1].ID != second.Channel.ID {
|
|
t.Fatalf("common channels = %+v, want two shared megagroups in id order", page)
|
|
}
|
|
|
|
next, err := channels.ListCommonChannels(ctx, domain.CommonChannelsRequest{
|
|
UserID: owner.ID,
|
|
TargetUserID: friend.ID,
|
|
MaxID: first.Channel.ID,
|
|
Limit: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("list common channels after max id: %v", err)
|
|
}
|
|
if next.Count != 2 || len(next.Channels) != 1 || next.Channels[0].ID != second.Channel.ID {
|
|
t.Fatalf("paged common channels = %+v, want second channel with full count", next)
|
|
}
|
|
|
|
countOnly, err := channels.ListCommonChannels(ctx, domain.CommonChannelsRequest{
|
|
UserID: owner.ID,
|
|
TargetUserID: friend.ID,
|
|
CountOnly: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("count common channels: %v", err)
|
|
}
|
|
if countOnly.Count != 2 || len(countOnly.Channels) != 0 {
|
|
t.Fatalf("count-only common channels = %+v, want count without channels", countOnly)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListActiveChannelIDsForUserUsesMembershipIndexState(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 81, Phone: "+1888" + suffix + "41", FirstName: "ActiveIndexOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
friend, err := users.Create(ctx, domain.User{AccessHash: 82, Phone: "+1888" + suffix + "42", FirstName: "ActiveIndexFriend"})
|
|
if err != nil {
|
|
t.Fatalf("create friend: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, friend.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
create := func(title string, date int) domain.CreateChannelResult {
|
|
t.Helper()
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: title,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{friend.ID},
|
|
Date: date,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create %s: %v", title, err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
return created
|
|
}
|
|
activeA := create("active index one "+suffix, 1700000830)
|
|
left := create("left index excluded "+suffix, 1700000831)
|
|
activeB := create("active index two "+suffix, 1700000832)
|
|
deleted := create("deleted index excluded "+suffix, 1700000833)
|
|
if _, err := channels.LeaveChannel(ctx, left.Channel.ID, friend.ID, 1700000834); err != nil {
|
|
t.Fatalf("leave channel: %v", err)
|
|
}
|
|
if _, err := channels.DeleteChannel(ctx, domain.DeleteChannelRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: deleted.Channel.ID,
|
|
Date: 1700000835,
|
|
}); err != nil {
|
|
t.Fatalf("delete channel: %v", err)
|
|
}
|
|
|
|
want := []int64{activeA.Channel.ID, activeB.Channel.ID}
|
|
if want[0] > want[1] {
|
|
want[0], want[1] = want[1], want[0]
|
|
}
|
|
got, err := channels.ListActiveChannelIDsForUser(ctx, friend.ID, 0, 10)
|
|
if err != nil {
|
|
t.Fatalf("list active channel ids: %v", err)
|
|
}
|
|
if len(got) != 2 || got[0] != want[0] || got[1] != want[1] {
|
|
t.Fatalf("active channel ids = %v, want %v", got, want)
|
|
}
|
|
|
|
firstPage, err := channels.ListActiveChannelIDsForUser(ctx, friend.ID, 0, 1)
|
|
if err != nil {
|
|
t.Fatalf("list first active channel id: %v", err)
|
|
}
|
|
if len(firstPage) != 1 || firstPage[0] != want[0] {
|
|
t.Fatalf("first active page = %v, want [%d]", firstPage, want[0])
|
|
}
|
|
nextPage, err := channels.ListActiveChannelIDsForUser(ctx, friend.ID, want[0], 10)
|
|
if err != nil {
|
|
t.Fatalf("list next active channel ids: %v", err)
|
|
}
|
|
if len(nextPage) != 1 || nextPage[0] != want[1] {
|
|
t.Fatalf("next active page = %v, want [%d]", nextPage, want[1])
|
|
}
|
|
empty, err := channels.ListActiveChannelIDsForUser(ctx, friend.ID, want[1], 10)
|
|
if err != nil {
|
|
t.Fatalf("list active channel ids after last: %v", err)
|
|
}
|
|
if len(empty) != 0 {
|
|
t.Fatalf("active ids after last = %v, want empty", empty)
|
|
}
|
|
if _, err := channels.ListActiveChannelIDsForUser(ctx, friend.ID, -1, 10); !errors.Is(err, domain.ErrChannelInvalid) {
|
|
t.Fatalf("negative cursor err = %v, want ErrChannelInvalid", err)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreUserChannelMemberIndexListPaths(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 91, Phone: "+1888" + suffix + "51", FirstName: "IndexListOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
friend, err := users.Create(ctx, domain.User{AccessHash: 92, Phone: "+1888" + suffix + "52", FirstName: "IndexListFriend"})
|
|
if err != nil {
|
|
t.Fatalf("create friend: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, friend.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
publicCandidate, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "index public " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000840,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create public candidate: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, publicCandidate.Channel.ID)
|
|
if _, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: publicCandidate.Channel.ID,
|
|
Username: "idxpublic" + suffix,
|
|
}); err != nil {
|
|
t.Fatalf("set public username: %v", err)
|
|
}
|
|
admined, err := channels.ListAdminedPublicChannels(ctx, owner.ID)
|
|
if err != nil {
|
|
t.Fatalf("list admined public channels: %v", err)
|
|
}
|
|
if len(admined) != 1 || admined[0].ID != publicCandidate.Channel.ID {
|
|
t.Fatalf("admined public = %+v, want public candidate", admined)
|
|
}
|
|
if _, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: publicCandidate.Channel.ID,
|
|
Username: "",
|
|
}); err != nil {
|
|
t.Fatalf("clear public username: %v", err)
|
|
}
|
|
admined, err = channels.ListAdminedPublicChannels(ctx, owner.ID)
|
|
if err != nil {
|
|
t.Fatalf("list admined public channels after clear: %v", err)
|
|
}
|
|
if len(admined) != 0 {
|
|
t.Fatalf("admined public after clear = %+v, want empty", admined)
|
|
}
|
|
|
|
discussionCandidate, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "index discussion " + suffix,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{friend.ID},
|
|
Date: 1700000841,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create discussion candidate: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, discussionCandidate.Channel.ID)
|
|
friendGroups, err := channels.ListDiscussionGroups(ctx, friend.ID, 10)
|
|
if err != nil {
|
|
t.Fatalf("list friend discussion groups before admin: %v", err)
|
|
}
|
|
if len(friendGroups) != 0 {
|
|
t.Fatalf("friend discussion groups before admin = %+v, want empty", friendGroups)
|
|
}
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: discussionCandidate.Channel.ID,
|
|
MemberID: friend.ID,
|
|
AdminRights: domain.ChannelAdminRights{
|
|
PinMessages: true,
|
|
},
|
|
Date: 1700000842,
|
|
}); err != nil {
|
|
t.Fatalf("grant pin admin: %v", err)
|
|
}
|
|
friendGroups, err = channels.ListDiscussionGroups(ctx, friend.ID, 10)
|
|
if err != nil {
|
|
t.Fatalf("list friend discussion groups after admin: %v", err)
|
|
}
|
|
if len(friendGroups) != 1 || friendGroups[0].ID != discussionCandidate.Channel.ID {
|
|
t.Fatalf("friend discussion groups after admin = %+v, want candidate", friendGroups)
|
|
}
|
|
if _, err := channels.SetForum(ctx, owner.ID, discussionCandidate.Channel.ID, true, false); err != nil {
|
|
t.Fatalf("enable forum: %v", err)
|
|
}
|
|
friendGroups, err = channels.ListDiscussionGroups(ctx, friend.ID, 10)
|
|
if err != nil {
|
|
t.Fatalf("list friend discussion groups after forum: %v", err)
|
|
}
|
|
if len(friendGroups) != 0 {
|
|
t.Fatalf("friend discussion groups after forum = %+v, want empty", friendGroups)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreListStoryPostableChannelsPostgres(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 9901, Phone: "+1888" + suffix + "41", FirstName: "StoryPostOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
admin, err := users.Create(ctx, domain.User{AccessHash: 9902, Phone: "+1888" + suffix + "42", FirstName: "StoryPostAdmin"})
|
|
if err != nil {
|
|
t.Fatalf("create admin: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) > 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, admin.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
owned, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: admin.ID,
|
|
Title: "pg owned story " + suffix,
|
|
Broadcast: true,
|
|
Date: 1700003400,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owned channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, owned.Channel.ID)
|
|
postable, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "pg postable story " + suffix,
|
|
Broadcast: true,
|
|
MemberUserIDs: []int64{admin.ID},
|
|
Date: 1700003401,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create postable channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, postable.Channel.ID)
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
ChannelID: postable.Channel.ID,
|
|
UserID: owner.ID,
|
|
MemberID: admin.ID,
|
|
AdminRights: domain.ChannelAdminRights{
|
|
PostStories: true,
|
|
},
|
|
Date: 1700003402,
|
|
}); err != nil {
|
|
t.Fatalf("grant post stories: %v", err)
|
|
}
|
|
editOnly, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "pg edit-only story " + suffix,
|
|
Broadcast: true,
|
|
MemberUserIDs: []int64{admin.ID},
|
|
Date: 1700003403,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create edit-only channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, editOnly.Channel.ID)
|
|
if _, err := channels.EditChannelAdmin(ctx, domain.EditChannelAdminRequest{
|
|
ChannelID: editOnly.Channel.ID,
|
|
UserID: owner.ID,
|
|
MemberID: admin.ID,
|
|
AdminRights: domain.ChannelAdminRights{
|
|
EditStories: true,
|
|
},
|
|
Date: 1700003404,
|
|
}); err != nil {
|
|
t.Fatalf("grant edit stories only: %v", err)
|
|
}
|
|
|
|
list, err := channels.ListStoryPostableChannels(ctx, admin.ID)
|
|
if err != nil {
|
|
t.Fatalf("ListStoryPostableChannels: %v", err)
|
|
}
|
|
got := make([]int64, 0, len(list))
|
|
for _, channel := range list {
|
|
got = append(got, channel.ID)
|
|
}
|
|
want := []int64{postable.Channel.ID, owned.Channel.ID}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("story postable channel ids = %v, want %v; excluded edit-only=%d", got, want, editOnly.Channel.ID)
|
|
}
|
|
if got[0] != want[0] || got[1] != want[1] {
|
|
t.Fatalf("story postable channel ids = %v, want %v; excluded edit-only=%d", got, want, editOnly.Channel.ID)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreLeftChannelsReturnsPagedLeftMemberships(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 61, Phone: "+1889" + suffix + "01", FirstName: "LeftOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
friend, err := users.Create(ctx, domain.User{AccessHash: 62, Phone: "+1889" + suffix + "02", FirstName: "LeftFriend"})
|
|
if err != nil {
|
|
t.Fatalf("create friend: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = ANY($1::bigint[])", []int64{owner.ID, friend.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
create := func(title string, broadcast bool, date int) domain.CreateChannelResult {
|
|
t.Helper()
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: title,
|
|
Broadcast: broadcast,
|
|
Megagroup: !broadcast,
|
|
MemberUserIDs: []int64{friend.ID},
|
|
Date: date,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create %s: %v", title, err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
return created
|
|
}
|
|
older := create("older left "+suffix, false, 1700000810)
|
|
newer := create("newer left "+suffix, true, 1700000811)
|
|
create("active excluded "+suffix, false, 1700000812)
|
|
if _, err := channels.LeaveChannel(ctx, older.Channel.ID, friend.ID, 1700000813); err != nil {
|
|
t.Fatalf("leave older channel: %v", err)
|
|
}
|
|
if _, err := channels.LeaveChannel(ctx, newer.Channel.ID, friend.ID, 1700000814); err != nil {
|
|
t.Fatalf("leave newer channel: %v", err)
|
|
}
|
|
|
|
page, err := channels.ListLeftChannels(ctx, friend.ID, 0, 1)
|
|
if err != nil {
|
|
t.Fatalf("list left channels: %v", err)
|
|
}
|
|
if page.Count != 2 || len(page.Channels) != 1 || page.Channels[0].Channel.ID != newer.Channel.ID || page.Channels[0].Self.Status != domain.ChannelMemberLeft {
|
|
t.Fatalf("first left page = %+v, want newest left channel and full count", page)
|
|
}
|
|
next, err := channels.ListLeftChannels(ctx, friend.ID, 1, 1)
|
|
if err != nil {
|
|
t.Fatalf("list next left channels: %v", err)
|
|
}
|
|
if next.Count != 2 || len(next.Channels) != 1 || next.Channels[0].Channel.ID != older.Channel.ID {
|
|
t.Fatalf("second left page = %+v, want older left channel", next)
|
|
}
|
|
empty, err := channels.ListLeftChannels(ctx, friend.ID, 2, 1)
|
|
if err != nil {
|
|
t.Fatalf("list empty left page: %v", err)
|
|
}
|
|
if empty.Count != 2 || len(empty.Channels) != 0 {
|
|
t.Fatalf("empty left page = %+v, want full count and no chats", empty)
|
|
}
|
|
if _, err := channels.ListLeftChannels(ctx, friend.ID, domain.MaxLeftChannelsOffset+1, 1); !errors.Is(err, domain.ErrChannelInvalid) {
|
|
t.Fatalf("huge offset err = %v, want ErrChannelInvalid", err)
|
|
}
|
|
}
|
|
|
|
func TestChannelStoreDiscussionGroupLinksAreBidirectional(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: "+1890" + suffix + "01", FirstName: "DiscussionOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
var channelIDs []int64
|
|
t.Cleanup(func() {
|
|
if len(channelIDs) != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id = ANY($1::bigint[])", channelIDs)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", owner.ID)
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
create := func(title string, broadcast bool, date int) domain.CreateChannelResult {
|
|
t.Helper()
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: title,
|
|
Broadcast: broadcast,
|
|
Megagroup: !broadcast,
|
|
Date: date,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create %s: %v", title, err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
return created
|
|
}
|
|
broadcast := create("discussion broadcast "+suffix, true, 1700000820)
|
|
firstGroup := create("discussion first "+suffix, false, 1700000821)
|
|
secondGroup := create("discussion second "+suffix, false, 1700000822)
|
|
|
|
candidates, err := channels.ListDiscussionGroups(ctx, owner.ID, 10)
|
|
if err != nil {
|
|
t.Fatalf("list discussion groups: %v", err)
|
|
}
|
|
if len(candidates) < 2 || candidates[0].ID != secondGroup.Channel.ID || candidates[1].ID != firstGroup.Channel.ID {
|
|
t.Fatalf("discussion groups = %+v, want newest creator megagroups", candidates)
|
|
}
|
|
|
|
linked, err := channels.SetDiscussionGroup(ctx, owner.ID, broadcast.Channel.ID, firstGroup.Channel.ID)
|
|
if err != nil {
|
|
t.Fatalf("link first discussion group: %v", err)
|
|
}
|
|
if len(linked.Channels) != 2 {
|
|
t.Fatalf("linked changed channels = %+v, want broadcast and group", linked.Channels)
|
|
}
|
|
gotBroadcast, err := channels.GetChannelByID(ctx, broadcast.Channel.ID)
|
|
if err != nil {
|
|
t.Fatalf("get linked broadcast: %v", err)
|
|
}
|
|
gotFirst, err := channels.GetChannelByID(ctx, firstGroup.Channel.ID)
|
|
if err != nil {
|
|
t.Fatalf("get linked first group: %v", err)
|
|
}
|
|
if gotBroadcast.LinkedChatID != firstGroup.Channel.ID || gotFirst.LinkedChatID != broadcast.Channel.ID {
|
|
t.Fatalf("first link = broadcast %d group %d, want bidirectional", gotBroadcast.LinkedChatID, gotFirst.LinkedChatID)
|
|
}
|
|
|
|
replaced, err := channels.SetDiscussionGroup(ctx, owner.ID, broadcast.Channel.ID, secondGroup.Channel.ID)
|
|
if err != nil {
|
|
t.Fatalf("replace discussion group: %v", err)
|
|
}
|
|
if len(replaced.Channels) != 3 {
|
|
t.Fatalf("replace changed channels = %+v, want broadcast old group new group", replaced.Channels)
|
|
}
|
|
gotBroadcast, _ = channels.GetChannelByID(ctx, broadcast.Channel.ID)
|
|
gotFirst, _ = channels.GetChannelByID(ctx, firstGroup.Channel.ID)
|
|
gotSecond, err := channels.GetChannelByID(ctx, secondGroup.Channel.ID)
|
|
if err != nil {
|
|
t.Fatalf("get linked second group: %v", err)
|
|
}
|
|
if gotBroadcast.LinkedChatID != secondGroup.Channel.ID || gotSecond.LinkedChatID != broadcast.Channel.ID || gotFirst.LinkedChatID != 0 {
|
|
t.Fatalf("replace link = broadcast %d first %d second %d, want old cleared and new bidirectional",
|
|
gotBroadcast.LinkedChatID, gotFirst.LinkedChatID, gotSecond.LinkedChatID)
|
|
}
|
|
|
|
if _, err := channels.SetDiscussionGroup(ctx, owner.ID, 0, secondGroup.Channel.ID); err != nil {
|
|
t.Fatalf("unlink from group side: %v", err)
|
|
}
|
|
gotBroadcast, _ = channels.GetChannelByID(ctx, broadcast.Channel.ID)
|
|
gotSecond, _ = channels.GetChannelByID(ctx, secondGroup.Channel.ID)
|
|
if gotBroadcast.LinkedChatID != 0 || gotSecond.LinkedChatID != 0 {
|
|
t.Fatalf("unlink = broadcast %d group %d, want both cleared", gotBroadcast.LinkedChatID, gotSecond.LinkedChatID)
|
|
}
|
|
if _, err := channels.SetDiscussionGroup(ctx, owner.ID, 0, secondGroup.Channel.ID); !errors.Is(err, domain.ErrLinkNotModified) {
|
|
t.Fatalf("repeat unlink err = %v, want ErrLinkNotModified", err)
|
|
}
|
|
if _, err := channels.SetPreHistoryHidden(ctx, owner.ID, firstGroup.Channel.ID, true); err != nil {
|
|
t.Fatalf("hide first group prehistory: %v", err)
|
|
}
|
|
if _, err := channels.SetDiscussionGroup(ctx, owner.ID, broadcast.Channel.ID, firstGroup.Channel.ID); !errors.Is(err, domain.ErrMegagroupPrehistoryHidden) {
|
|
t.Fatalf("hidden prehistory err = %v, want ErrMegagroupPrehistoryHidden", err)
|
|
}
|
|
}
|