169 lines
7.2 KiB
Go
169 lines
7.2 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestCommunityStoreLifecycleIsAtomicInPostgres(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner, err := users.Create(ctx, domain.User{AccessHash: 801, Phone: "+1888" + suffix + "01", FirstName: "CommunityOwner"})
|
|
if err != nil {
|
|
t.Fatalf("create owner: %v", err)
|
|
}
|
|
member, err := users.Create(ctx, domain.User{AccessHash: 802, Phone: "+1888" + suffix + "02", FirstName: "SearchableMember"})
|
|
if err != nil {
|
|
t.Fatalf("create member: %v", err)
|
|
}
|
|
channels := NewChannelStore(pool)
|
|
initial, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Community Initial " + suffix,
|
|
Megagroup: true,
|
|
MemberUserIDs: []int64{member.ID},
|
|
Date: 1_800_200_000,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create initial channel: %v", err)
|
|
}
|
|
owned, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: member.ID,
|
|
Title: "Community Owned " + suffix,
|
|
Megagroup: true,
|
|
Date: 1_800_200_001,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create owned channel: %v", err)
|
|
}
|
|
owned.Channel, err = channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: member.ID, ChannelID: owned.Channel.ID, Username: "communitypreview" + suffix,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("make owned channel public: %v", err)
|
|
}
|
|
if _, err := channels.SendChannelMessage(ctx, domain.SendChannelMessageRequest{
|
|
UserID: member.ID, ChannelID: owned.Channel.ID, RandomID: 9_900_000_001,
|
|
Message: "community public preview search", Date: 1_800_200_001,
|
|
}); err != nil {
|
|
t.Fatalf("send public preview message: %v", err)
|
|
}
|
|
var communityID int64
|
|
t.Cleanup(func() {
|
|
if communityID != 0 {
|
|
_, _ = pool.Exec(ctx, "DELETE FROM communities WHERE id=$1", communityID)
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM channels WHERE id=ANY($1::bigint[])", []int64{initial.Channel.ID, owned.Channel.ID})
|
|
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id=ANY($1::bigint[])", []int64{owner.ID, member.ID})
|
|
})
|
|
|
|
store := NewCommunityStore(pool, nil, nil)
|
|
created, err := store.CreateCommunity(ctx, domain.CreateCommunityRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "Postgres Community " + suffix,
|
|
InitialPeer: domain.Peer{Type: domain.PeerTypeChannel, ID: initial.Channel.ID},
|
|
Visibility: domain.CommunityPeerHidden,
|
|
Date: 1_800_200_002,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create community: %v", err)
|
|
}
|
|
communityID = created.Community.ID
|
|
if len(created.ServiceMessages) != 1 || created.ServiceMessages[0].Event.Pts == 0 {
|
|
t.Fatalf("create service messages = %+v", created.ServiceMessages)
|
|
}
|
|
var linkedID int64
|
|
if err := pool.QueryRow(ctx, "SELECT linked_community_id FROM channels WHERE id=$1", initial.Channel.ID).Scan(&linkedID); err != nil || linkedID != communityID {
|
|
t.Fatalf("initial linked_community_id = %d err=%v, want %d", linkedID, err, communityID)
|
|
}
|
|
|
|
requested, err := store.ToggleCommunityPeerLink(ctx, domain.CommunityTogglePeerLinkRequest{
|
|
ActorUserID: member.ID,
|
|
CommunityID: communityID,
|
|
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: owned.Channel.ID},
|
|
Visibility: domain.CommunityPeerVisible,
|
|
Date: 1_800_200_003,
|
|
})
|
|
if err != nil || !requested.RequestCreated {
|
|
t.Fatalf("create peer link request = %+v err=%v", requested, err)
|
|
}
|
|
approved, err := store.DecideCommunityPeerLinkRequest(ctx, owner.ID, communityID, requested.Peer, false, 1_800_200_004)
|
|
if err != nil || approved.Link == nil || approved.RequestedBy != member.ID || approved.ServiceMessage == nil {
|
|
t.Fatalf("approve link request = %+v err=%v", approved, err)
|
|
}
|
|
search, err := channels.SearchJoinedMessages(ctx, owner.ID, domain.ChannelGlobalSearchRequest{
|
|
Query: "public preview", ChannelIDs: []int64{owned.Channel.ID}, RestrictChannelIDs: true,
|
|
AllowPublicPreview: true, Limit: 20,
|
|
})
|
|
if err != nil || len(search.Messages) != 1 || search.Messages[0].ChannelID != owned.Channel.ID {
|
|
t.Fatalf("community public-preview search = %+v err=%v", search.Messages, err)
|
|
}
|
|
|
|
participants, err := store.ListCommunityParticipants(ctx, owner.ID, communityID, domain.ChannelParticipantsFilter{
|
|
Kind: domain.ChannelParticipantsSearch, Query: "SEARCHABLE",
|
|
}, 0, 20)
|
|
if err != nil || participants.Count != 1 || len(participants.Participants) != 1 || participants.Participants[0].UserID != member.ID {
|
|
t.Fatalf("participant search = %+v err=%v", participants, err)
|
|
}
|
|
admins, err := store.ListCommunityParticipants(ctx, member.ID, communityID, domain.ChannelParticipantsFilter{
|
|
Kind: domain.ChannelParticipantsAdmins,
|
|
}, 0, 100)
|
|
if err != nil || admins.Count != 1 || len(admins.Participants) != 1 || admins.Participants[0].UserID != owner.ID {
|
|
t.Fatalf("member-visible Community admins = %+v err=%v", admins, err)
|
|
}
|
|
if _, err := store.ListCommunityParticipants(ctx, member.ID, communityID, domain.ChannelParticipantsFilter{
|
|
Kind: domain.ChannelParticipantsBanned,
|
|
}, 0, 100); !errors.Is(err, domain.ErrCommunityAdminRequired) {
|
|
t.Fatalf("member Community banned list error = %v, want admin required", err)
|
|
}
|
|
welcomeContent := domain.WelcomeMessageContent{Message: "community welcome cleanup"}
|
|
welcomePeer := domain.Peer{Type: domain.PeerTypeChannel, ID: initial.Channel.ID}
|
|
welcomeFingerprint, err := domain.WelcomeCreateFingerprint(welcomePeer, owner.ID, 8_020_005, welcomeContent)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, err := NewWelcomeMessageStore(pool).CreateWelcomeMessage(ctx, domain.CreateWelcomeMessageRequest{
|
|
Peer: welcomePeer, CreatorUserID: owner.ID, Date: 1_800_200_004, RandomID: 8_020_005,
|
|
Content: welcomeContent, CreateFingerprint: welcomeFingerprint,
|
|
}); err != nil {
|
|
t.Fatalf("create community cleanup welcome: %v", err)
|
|
}
|
|
memberRow, err := channels.getChannelMember(ctx, pool, initial.Channel.ID, member.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
welcomeTx, err := pool.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := enqueueWelcomeMessageDeliveriesTx(ctx, welcomeTx, initial.Channel.ID, []domain.ChannelMember{memberRow}); err != nil {
|
|
_ = welcomeTx.Rollback(ctx)
|
|
t.Fatal(err)
|
|
}
|
|
if err := welcomeTx.Commit(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ban, err := store.ToggleCommunityParticipantBanned(ctx, owner.ID, communityID, member.ID, false, 1_800_200_005)
|
|
if err != nil {
|
|
t.Fatalf("ban participant: %v", err)
|
|
}
|
|
if !ban.Changed || len(ban.ChannelBans) != 1 || len(ban.RemovedLinks) != 1 {
|
|
t.Fatalf("ban result = %+v", ban)
|
|
}
|
|
var welcomeDeliveries int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM welcome_message_deliveries WHERE channel_id=$1 AND target_user_id=$2`, initial.Channel.ID, member.ID).Scan(&welcomeDeliveries); err != nil || welcomeDeliveries != 0 {
|
|
t.Fatalf("community ban welcome deliveries=%d err=%v", welcomeDeliveries, err)
|
|
}
|
|
if err := pool.QueryRow(ctx, "SELECT linked_community_id FROM channels WHERE id=$1", owned.Channel.ID).Scan(&linkedID); err != nil || linkedID != 0 {
|
|
t.Fatalf("owned linked_community_id after ban = %d err=%v, want 0", linkedID, err)
|
|
}
|
|
if _, err := store.GetCommunity(ctx, member.ID, communityID); !errors.Is(err, domain.ErrCommunityPrivate) {
|
|
t.Fatalf("banned member get community error = %v, want private", err)
|
|
}
|
|
}
|