ef2c2d1eb9
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 54s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
A new account's time_zone defaulted to 'UTC' until the player saved a profile, so the robot's sleep window and the turn-timeout away-window sweeper — both anchored to the account zone via account.ResolveZone — ran on UTC for every fresh player, skewing robot-game timing until a manual Settings save. Seed the zone at creation instead, from the client's detected "±HH:MM" offset. - Carry browser_tz on the three account-creating auth requests (TelegramLoginRequest, GuestLoginRequest, EmailRequestRequest — the email account is provisioned at the code-request step, not at login) through the fbs envelope (+ Go/TS codegen), the gateway transcode + backend client, and the backend auth handlers into ProvisionTelegram / ProvisionGuest / ProvisionEmail. - create() now writes time_zone explicitly: the validated detected offset, or 'UTC' (equal to the column default) when absent or malformed — deterministic, never guessed. The column is already NOT NULL DEFAULT 'UTC', so no migration is needed and existing accounts keep 'UTC'. An existing account is never overwritten on re-login. - A detected zero offset is stored as "+00:00" (the zone is known and equals UTC), distinct from the "UTC" default that means "unknown" — which the feedback console's three-zone Filed display already reflects. - Guard the guest handler against an empty payload (the bootstrap historically carried none) so it degrades to no-seed rather than panicking in GetRootAs*. - Tests: zone seeding across Telegram/guest/email plus the "+00:00"/malformed/empty cases and the not-overwrite rule; codec round-trip for the three auth encoders. ARCHITECTURE + FUNCTIONAL(+ru) updated.
87 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|