Implements collectible usernames, official verification workflows, and third-party bot verification after maintainer protocol and migration review. The composite activity/moderation rating remains an admin-only read model; Telegram Stars Rating wire fields stay unset pending a dedicated official-semantics implementation. Reviewed-Head: 2796345775ea0f908fb7734601e5e1dee4b653b9 Original-Head: fa082b892fd5180c9c9bc53c81c21cf5d250a75b Co-authored-by: Egor Egorov <business.egor.sg@gmail.com>
183 lines
6.9 KiB
Go
183 lines
6.9 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
appusers "telesrv/internal/app/users"
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
func TestChannelStoreResolvePublicUsernameRejectsStaleIndex(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
owner := createTestUser(t, ctx, users, "+1886"+suffix+"71", "ResolveOwner", "")
|
|
viewer := createTestUser(t, ctx, users, "+1886"+suffix+"72", "ResolveViewer", "")
|
|
var channelIDs []int64
|
|
usernames := []string{"resolvepub" + suffix, "missing" + suffix}
|
|
t.Cleanup(func() {
|
|
lower := make([]string, 0, len(usernames))
|
|
for _, username := range usernames {
|
|
lower = append(lower, strings.ToLower(username))
|
|
}
|
|
_, _ = pool.Exec(ctx, "DELETE FROM peer_usernames WHERE (peer_type = 'channel' AND peer_id = ANY($1::bigint[])) OR username_lower = ANY($2::text[])", channelIDs, lower)
|
|
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, viewer.ID})
|
|
})
|
|
|
|
channels := NewChannelStore(pool)
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "resolve public " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000910,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
publicUsername := usernames[0]
|
|
publicChannel, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: created.Channel.ID,
|
|
Username: publicUsername,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("set username: %v", err)
|
|
}
|
|
|
|
resolved, found, err := channels.ResolvePublicChannelUsername(ctx, viewer.ID, strings.ToUpper(publicUsername))
|
|
if err != nil || !found || resolved.ID != publicChannel.ID {
|
|
t.Fatalf("resolve public username = %+v found %v err %v, want channel %d", resolved, found, err, publicChannel.ID)
|
|
}
|
|
if _, found, err := channels.ResolvePublicChannelUsername(ctx, viewer.ID, usernames[1]); err != nil || found {
|
|
t.Fatalf("resolve missing username found %v err %v, want not found", found, err)
|
|
}
|
|
anonymous, found, err := channels.ResolvePublicChannelUsername(ctx, 0, strings.ToUpper(publicUsername))
|
|
if err != nil || !found || anonymous.ID != publicChannel.ID {
|
|
t.Fatalf("anonymous resolve public username = %+v found=%v err=%v", anonymous, found, err)
|
|
}
|
|
if _, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: publicChannel.ID,
|
|
Username: "",
|
|
}); err != nil {
|
|
t.Fatalf("clear username: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO peer_usernames (username_lower, username, peer_type, peer_id, active, editable, sort_order)
|
|
VALUES ($1,$1,'channel',$2,true,true,0)
|
|
ON CONFLICT (username_lower) DO UPDATE SET peer_type = EXCLUDED.peer_type, peer_id = EXCLUDED.peer_id, updated_at = now()
|
|
`, strings.ToLower(publicUsername), publicChannel.ID); err != nil {
|
|
t.Fatalf("insert stale username index: %v", err)
|
|
}
|
|
|
|
stale, found, err := channels.ResolvePublicChannelUsername(ctx, viewer.ID, publicUsername)
|
|
if err != nil || found {
|
|
t.Fatalf("resolve stale username = %+v found %v err %v, want not found", stale, found, err)
|
|
}
|
|
}
|
|
|
|
func TestPeerUsernameNamespaceIsGlobal(t *testing.T) {
|
|
pool := testPool(t)
|
|
ctx := context.Background()
|
|
suffix := randomSuffix(t)
|
|
users := NewUserStore(pool)
|
|
channels := NewChannelStore(pool)
|
|
bots := NewBotStore(pool)
|
|
|
|
owner := createTestUser(t, ctx, users, "+1886"+suffix+"81", "GlobalOwner", "")
|
|
viewer := createTestUser(t, ctx, users, "+1886"+suffix+"82", "GlobalViewer", "")
|
|
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, viewer.ID})
|
|
})
|
|
|
|
created, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "global namespace " + suffix,
|
|
Broadcast: true,
|
|
Date: 1700000920,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, created.Channel.ID)
|
|
channelUsername := "globalchan_" + suffix
|
|
if _, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: created.Channel.ID,
|
|
Username: channelUsername,
|
|
}); err != nil {
|
|
t.Fatalf("set channel username: %v", err)
|
|
}
|
|
|
|
userService := appusers.NewService(users)
|
|
ok, err := userService.CheckUsername(ctx, viewer.ID, strings.ToUpper(channelUsername))
|
|
if err != nil || ok {
|
|
t.Fatalf("account check channel username = ok %v err %v, want false/nil", ok, err)
|
|
}
|
|
if _, err := userService.UpdateUsername(ctx, viewer.ID, channelUsername); !errors.Is(err, domain.ErrUsernameOccupied) {
|
|
t.Fatalf("account update to channel username err = %v, want username occupied", err)
|
|
}
|
|
if _, err := users.Create(ctx, domain.User{
|
|
AccessHash: 9001,
|
|
Phone: "+1886" + suffix + "83",
|
|
FirstName: "GlobalCreate",
|
|
Username: channelUsername,
|
|
}); !errors.Is(err, domain.ErrUsernameOccupied) {
|
|
t.Fatalf("create user with channel username err = %v, want username occupied", err)
|
|
}
|
|
if _, _, err := bots.CreateBotAccount(ctx, domain.User{
|
|
AccessHash: 9002,
|
|
FirstName: "GlobalBot",
|
|
Username: channelUsername,
|
|
Bot: true,
|
|
BotInfoVersion: 1,
|
|
}, domain.BotProfile{OwnerUserID: owner.ID, TokenSecret: "secret"}); !errors.Is(err, domain.ErrUsernameOccupied) {
|
|
t.Fatalf("create bot with channel username err = %v, want username occupied", err)
|
|
}
|
|
|
|
userUsername := "globaluser_" + suffix
|
|
if _, err := userService.UpdateUsername(ctx, viewer.ID, userUsername); err != nil {
|
|
t.Fatalf("set user username: %v", err)
|
|
}
|
|
second, err := channels.CreateChannel(ctx, domain.CreateChannelRequest{
|
|
CreatorUserID: owner.ID,
|
|
Title: "global namespace second " + suffix,
|
|
Megagroup: true,
|
|
Date: 1700000921,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create second channel: %v", err)
|
|
}
|
|
channelIDs = append(channelIDs, second.Channel.ID)
|
|
ok, err = channels.CheckUsername(ctx, owner.ID, second.Channel.ID, strings.ToUpper(userUsername))
|
|
if err != nil || ok {
|
|
t.Fatalf("channel check user username = ok %v err %v, want false/nil", ok, err)
|
|
}
|
|
if _, err := channels.UpdateUsername(ctx, domain.UpdateChannelUsernameRequest{
|
|
UserID: owner.ID,
|
|
ChannelID: second.Channel.ID,
|
|
Username: userUsername,
|
|
}); !errors.Is(err, domain.ErrUsernameOccupied) {
|
|
t.Fatalf("channel update to user username err = %v, want username occupied", err)
|
|
}
|
|
|
|
if _, err := userService.UpdateUsername(ctx, viewer.ID, ""); err != nil {
|
|
t.Fatalf("clear user username: %v", err)
|
|
}
|
|
ok, err = channels.CheckUsername(ctx, owner.ID, second.Channel.ID, userUsername)
|
|
if err != nil || !ok {
|
|
t.Fatalf("channel check released user username = ok %v err %v, want true/nil", ok, err)
|
|
}
|
|
}
|