feat: game lobby service

This commit is contained in:
Ilia Denisov
2026-04-25 23:20:55 +02:00
committed by GitHub
parent 32dc29359a
commit 48b0056b49
336 changed files with 57074 additions and 1418 deletions
@@ -2,7 +2,6 @@ package selfservice
import (
"context"
"strings"
"testing"
"time"
@@ -156,74 +155,63 @@ func TestProfileUpdaterExecuteBlockedBySanction(t *testing.T) {
},
fakeLimitStore{},
fixedClock{now: now},
stubRaceNamePolicy{},
)
require.NoError(t, err)
_, err = service.Execute(context.Background(), UpdateMyProfileInput{
UserID: "user-123",
RaceName: "Nova Prime",
UserID: "user-123",
DisplayName: "NovaPrime",
})
require.Error(t, err)
require.Equal(t, shared.ErrorCodeConflict, shared.CodeOf(err))
require.Equal(t, 0, accountStore.renameCalls)
require.Equal(t, 0, accountStore.updateCalls)
}
func TestProfileUpdaterExecuteSuccessNoOpAndConflict(t *testing.T) {
func TestProfileUpdaterExecuteDisplayNameUpdates(t *testing.T) {
t.Parallel()
tests := []struct {
name string
inputRaceName string
renameErr error
inputDisplay string
updateErr error
wantCode string
wantRaceName string
wantRenameCalls int
wantCurrentKey account.RaceNameCanonicalKey
wantNewKey account.RaceNameCanonicalKey
wantDisplay string
wantUpdateCalls int
}{
{
name: "success",
inputRaceName: "Nova Prime",
wantRaceName: "Nova Prime",
wantRenameCalls: 1,
wantCurrentKey: canonicalKey(common.RaceName("Pilot Nova")),
wantNewKey: canonicalKey(common.RaceName("Nova Prime")),
name: "set display name",
inputDisplay: "NovaPrime",
wantDisplay: "NovaPrime",
wantUpdateCalls: 1,
},
{
name: "same canonical different exact",
inputRaceName: "P1lot Nova",
wantRaceName: "P1lot Nova",
wantRenameCalls: 1,
wantCurrentKey: canonicalKey(common.RaceName("Pilot Nova")),
wantNewKey: canonicalKey(common.RaceName("P1lot Nova")),
name: "trims input",
inputDisplay: " NovaPrime ",
wantDisplay: "NovaPrime",
wantUpdateCalls: 1,
},
{
name: "no-op",
inputRaceName: " Pilot Nova ",
wantRaceName: "Pilot Nova",
wantRenameCalls: 0,
name: "reset to empty",
inputDisplay: " ",
wantDisplay: "",
wantUpdateCalls: 0,
},
{
name: "conflict",
inputRaceName: "Taken Name",
renameErr: ports.ErrConflict,
wantCode: shared.ErrorCodeConflict,
wantRaceName: "Pilot Nova",
wantRenameCalls: 1,
wantCurrentKey: canonicalKey(common.RaceName("Pilot Nova")),
wantNewKey: canonicalKey(common.RaceName("Taken Name")),
name: "invalid display name rejected",
inputDisplay: "Nova Prime",
wantCode: shared.ErrorCodeInvalidRequest,
wantDisplay: "",
wantUpdateCalls: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
now := time.Unix(1_775_240_500, 0).UTC()
accountStore := newFakeAccountStore(validUserAccount())
accountStore.renameErr = tt.renameErr
accountStore.updateErr = tt.updateErr
service, err := NewProfileUpdater(
accountStore,
&fakeEntitlementSnapshotStore{
@@ -234,13 +222,12 @@ func TestProfileUpdaterExecuteSuccessNoOpAndConflict(t *testing.T) {
fakeSanctionStore{},
fakeLimitStore{},
fixedClock{now: now},
stubRaceNamePolicy{},
)
require.NoError(t, err)
result, err := service.Execute(context.Background(), UpdateMyProfileInput{
UserID: "user-123",
RaceName: tt.inputRaceName,
UserID: "user-123",
DisplayName: tt.inputDisplay,
})
if tt.wantCode != "" {
require.Error(t, err)
@@ -249,17 +236,13 @@ func TestProfileUpdaterExecuteSuccessNoOpAndConflict(t *testing.T) {
require.NoError(t, err)
}
require.Equal(t, tt.wantRenameCalls, accountStore.renameCalls)
if tt.wantRenameCalls > 0 {
require.Equal(t, tt.wantCurrentKey, accountStore.lastRenameInput.CurrentCanonicalKey)
require.Equal(t, tt.wantNewKey, accountStore.lastRenameInput.NewReservation.CanonicalKey)
}
require.Equal(t, tt.wantUpdateCalls, accountStore.updateCalls)
storedAccount, err := accountStore.GetByUserID(context.Background(), common.UserID("user-123"))
require.NoError(t, err)
require.Equal(t, tt.wantRaceName, storedAccount.RaceName.String())
require.Equal(t, tt.wantDisplay, storedAccount.DisplayName.String())
if tt.wantCode == "" {
require.Equal(t, tt.wantRaceName, result.Account.RaceName)
require.Equal(t, tt.wantDisplay, result.Account.DisplayName)
}
})
}
@@ -326,7 +309,7 @@ func TestSettingsUpdaterExecuteCanonicalizedNoOpAndInvalidInputs(t *testing.T) {
accountRecord: account.UserAccount{
UserID: common.UserID("user-123"),
Email: common.Email("pilot@example.com"),
RaceName: common.RaceName("Pilot Nova"),
UserName: common.UserName("player-abcdefgh"),
PreferredLanguage: common.LanguageTag("en-US"),
TimeZone: common.TimeZoneName("UTC"),
DeclaredCountry: common.CountryCode("DE"),
@@ -406,12 +389,9 @@ func TestSettingsUpdaterExecuteCanonicalizedNoOpAndInvalidInputs(t *testing.T) {
}
type fakeAccountStore struct {
records map[common.UserID]account.UserAccount
renameErr error
updateErr error
renameCalls int
updateCalls int
lastRenameInput ports.RenameRaceNameInput
records map[common.UserID]account.UserAccount
updateErr error
updateCalls int
}
func newFakeAccountStore(records ...account.UserAccount) *fakeAccountStore {
@@ -424,7 +404,7 @@ func newFakeAccountStore(records ...account.UserAccount) *fakeAccountStore {
}
func (store *fakeAccountStore) Create(_ context.Context, input ports.CreateAccountInput) error {
if input.Account.Validate() != nil || input.Reservation.Validate() != nil {
if input.Account.Validate() != nil {
return ports.ErrConflict
}
@@ -450,9 +430,9 @@ func (store *fakeAccountStore) GetByEmail(_ context.Context, email common.Email)
return account.UserAccount{}, ports.ErrNotFound
}
func (store *fakeAccountStore) GetByRaceName(_ context.Context, raceName common.RaceName) (account.UserAccount, error) {
func (store *fakeAccountStore) GetByUserName(_ context.Context, userName common.UserName) (account.UserAccount, error) {
for _, record := range store.records {
if record.RaceName == raceName {
if record.UserName == userName {
return record, nil
}
}
@@ -465,27 +445,6 @@ func (store *fakeAccountStore) ExistsByUserID(_ context.Context, userID common.U
return ok, nil
}
func (store *fakeAccountStore) RenameRaceName(_ context.Context, input ports.RenameRaceNameInput) error {
store.renameCalls++
store.lastRenameInput = input
if store.renameErr != nil {
return store.renameErr
}
if err := input.Validate(); err != nil {
return err
}
record, ok := store.records[input.UserID]
if !ok {
return ports.ErrNotFound
}
record.RaceName = input.NewRaceName
record.UpdatedAt = input.UpdatedAt.UTC()
store.records[input.UserID] = record
return nil
}
func (store *fakeAccountStore) Update(_ context.Context, record account.UserAccount) error {
store.updateCalls++
if store.updateErr != nil {
@@ -553,7 +512,7 @@ func (generator readerIDGenerator) NewUserID() (common.UserID, error) {
return "", nil
}
func (generator readerIDGenerator) NewInitialRaceName() (common.RaceName, error) {
func (generator readerIDGenerator) NewUserName() (common.UserName, error) {
return "", nil
}
@@ -633,26 +592,12 @@ func (clock fixedClock) Now() time.Time {
return clock.now
}
type stubRaceNamePolicy struct{}
func (stubRaceNamePolicy) CanonicalKey(raceName common.RaceName) (account.RaceNameCanonicalKey, error) {
return canonicalKey(raceName), nil
}
func canonicalKey(raceName common.RaceName) account.RaceNameCanonicalKey {
return account.RaceNameCanonicalKey(strings.NewReplacer(
"1", "i",
"0", "o",
"8", "b",
).Replace(strings.ToLower(raceName.String())))
}
func validUserAccount() account.UserAccount {
createdAt := time.Unix(1_775_240_000, 0).UTC()
return account.UserAccount{
UserID: common.UserID("user-123"),
Email: common.Email("pilot@example.com"),
RaceName: common.RaceName("Pilot Nova"),
UserName: common.UserName("player-abcdefgh"),
PreferredLanguage: common.LanguageTag("en"),
TimeZone: common.TimeZoneName("Europe/Kaliningrad"),
DeclaredCountry: common.CountryCode("DE"),
@@ -727,6 +672,5 @@ var (
_ ports.EntitlementLifecycleStore = (*fakeEntitlementLifecycleStore)(nil)
_ ports.SanctionStore = fakeSanctionStore{}
_ ports.LimitStore = fakeLimitStore{}
_ ports.RaceNamePolicy = stubRaceNamePolicy{}
_ ports.IDGenerator = readerIDGenerator{}
)