Files
scrabble-game/backend/internal/inttest/userlist_test.go
T
Ilia Denisov a404513037
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 56s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m0s
feat(telegram): chat-gate observability + grant on first registration
Two follow-ups from a contour test where a user joined the chat, then
registered, and got no write access — with silent logs.

Observability: log every chat_member update (chat id, configured id, user,
old->new status), the eligibility result and the grant outcome; plus a startup
self-check that warns loudly when the bot is not an administrator in the chat
with the restrict-members ("Ban users") right — the common misconfiguration,
previously invisible in the logs.

Grant on first registration: a user who joins the moderated chat BEFORE
registering is covered by no chat_member event, so the join-time grant never
fires for them. ProvisionTelegram now reports first contact, and the Telegram
auth handler emits chat_access_changed on it, so the gateway re-evaluates and
grants write access if the user is already in the chat.
2026-06-21 15:19:21 +02:00

87 lines
2.7 KiB
Go

//go:build integration
package inttest
import (
"context"
"testing"
"github.com/google/uuid"
"scrabble/backend/internal/account"
)
// TestUserListFilter checks the admin user-list filter: the people/robots split (by a
// robot identity) and the case-insensitive glob masks on display name and external id.
func TestUserListFilter(t *testing.T) {
ctx := context.Background()
st := account.NewStore(testDB)
uniq := uuid.NewString()
human, _, err := st.ProvisionTelegram(ctx, "tg-"+uniq, "en", "", "Zzqxhuman")
if err != nil {
t.Fatalf("provision human: %v", err)
}
robot, err := st.ProvisionRobot(ctx, "robot-uxz-"+uniq, "Zzqxbot")
if err != nil {
t.Fatalf("provision robot: %v", err)
}
guest, err := st.ProvisionGuest(ctx)
if err != nil {
t.Fatalf("provision guest: %v", err)
}
collect := func(f account.UserFilter) map[uuid.UUID]account.UserListItem {
items, err := st.ListUsers(ctx, f, 5000, 0)
if err != nil {
t.Fatalf("list users %+v: %v", f, err)
}
m := make(map[uuid.UUID]account.UserListItem, len(items))
for _, it := range items {
m[it.ID] = it
}
return m
}
people := collect(account.UserFilter{})
if _, ok := people[human.ID]; !ok {
t.Error("human missing from people")
}
if _, ok := people[guest.ID]; !ok {
t.Error("guest missing from people")
}
if _, ok := people[robot.ID]; ok {
t.Error("robot must not appear in people")
}
if it := people[human.ID]; it.IsRobot || it.IsGuest {
t.Errorf("human flags wrong: robot=%v guest=%v (want both false)", it.IsRobot, it.IsGuest)
}
robots := collect(account.UserFilter{Robots: true})
if it, ok := robots[robot.ID]; !ok || !it.IsRobot {
t.Errorf("robot missing from robots or IsRobot=false (ok=%v)", ok)
}
if _, ok := robots[human.ID]; ok {
t.Error("human must not appear in robots")
}
// Name mask (people).
if _, ok := collect(account.UserFilter{NameMask: "Zzqx*"})[human.ID]; !ok {
t.Error("name mask Zzqx* should match the human")
}
if _, ok := collect(account.UserFilter{NameMask: "nomatch*"})[human.ID]; ok {
t.Error("name mask nomatch* should not match the human")
}
// External-id mask (robots).
if _, ok := collect(account.UserFilter{Robots: true, ExternalIDMask: "robot-uxz-*"})[robot.ID]; !ok {
t.Error("external-id mask robot-uxz-* should match the robot")
}
if _, ok := collect(account.UserFilter{Robots: true, ExternalIDMask: "robot-zzz-*"})[robot.ID]; ok {
t.Error("external-id mask robot-zzz-* should not match the robot")
}
// CountUsers agrees that robots exist.
if n, err := st.CountUsers(ctx, account.UserFilter{Robots: true, ExternalIDMask: "robot-uxz-*"}); err != nil || n != 1 {
t.Errorf("count robots robot-uxz-* = (%d, %v), want 1", n, err)
}
}