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
@@ -95,6 +95,7 @@ func TestSnapshotReaderExecuteBuildsPaidSnapshotAndDerivedState(t *testing.T) {
{LimitCode: "max_owned_private_games", Value: 3},
{LimitCode: "max_pending_public_applications", Value: 10},
{LimitCode: "max_active_game_memberships", Value: 10},
{LimitCode: "max_registered_race_names", Value: 2},
}, result.EffectiveLimits)
}
@@ -128,6 +129,7 @@ func TestSnapshotReaderExecuteDeniesUnpaidAndLoginBlockedUsers(t *testing.T) {
wantLimits: []EffectiveLimitView{
{LimitCode: "max_pending_public_applications", Value: 3},
{LimitCode: "max_active_game_memberships", Value: 3},
{LimitCode: "max_registered_race_names", Value: 1},
},
},
{
@@ -149,6 +151,7 @@ func TestSnapshotReaderExecuteDeniesUnpaidAndLoginBlockedUsers(t *testing.T) {
{LimitCode: "max_owned_private_games", Value: 3},
{LimitCode: "max_pending_public_applications", Value: 10},
{LimitCode: "max_active_game_memberships", Value: 10},
{LimitCode: "max_registered_race_names", Value: 2},
},
},
}
@@ -181,6 +184,71 @@ func TestSnapshotReaderExecuteDeniesUnpaidAndLoginBlockedUsers(t *testing.T) {
}
}
func TestSnapshotReaderExecutePermanentBlockCollapsesMarkers(t *testing.T) {
t.Parallel()
now := time.Unix(1_775_240_500, 0).UTC()
userID := common.UserID("user-123")
tests := []struct {
name string
snapshot entitlement.CurrentSnapshot
sanctions []policy.SanctionRecord
}{
{
name: "permanent_block alone on paid user",
snapshot: paidEntitlementSnapshot(userID, now.Add(-24*time.Hour), now.Add(24*time.Hour)),
sanctions: []policy.SanctionRecord{
activeSanction(userID, policy.SanctionCodePermanentBlock, "platform", now.Add(-time.Hour)),
},
},
{
name: "permanent_block alone on free user",
snapshot: freeEntitlementSnapshot(userID, now.Add(-24*time.Hour)),
sanctions: []policy.SanctionRecord{
activeSanction(userID, policy.SanctionCodePermanentBlock, "platform", now.Add(-time.Hour)),
},
},
{
name: "permanent_block dominates login_block",
snapshot: paidEntitlementSnapshot(userID, now.Add(-24*time.Hour), now.Add(24*time.Hour)),
sanctions: []policy.SanctionRecord{
activeSanction(userID, policy.SanctionCodeLoginBlock, "auth", now.Add(-time.Hour)),
activeSanction(userID, policy.SanctionCodePermanentBlock, "platform", now.Add(-30*time.Minute)),
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
service, err := NewSnapshotReader(
fakeAccountStore{existsByUserID: map[common.UserID]bool{userID: true}},
fakeEntitlementReader{byUserID: map[common.UserID]entitlement.CurrentSnapshot{userID: tt.snapshot}},
fakeSanctionStore{byUserID: map[common.UserID][]policy.SanctionRecord{userID: tt.sanctions}},
fakeLimitStore{},
fixedClock{now: now},
)
require.NoError(t, err)
result, err := service.Execute(context.Background(), GetUserEligibilityInput{UserID: userID.String()})
require.NoError(t, err)
require.True(t, result.Exists)
require.Equal(t, EligibilityMarkersView{}, result.Markers,
"every can_* marker must be false under permanent_block")
gotSanctions := make([]string, 0, len(result.ActiveSanctions))
for _, sanction := range result.ActiveSanctions {
gotSanctions = append(gotSanctions, sanction.SanctionCode)
}
require.Contains(t, gotSanctions, string(policy.SanctionCodePermanentBlock),
"permanent_block must surface in the eligibility snapshot")
})
}
}
func TestSnapshotReaderExecuteRepairsExpiredPaidSnapshotWithStore(t *testing.T) {
t.Parallel()
@@ -191,12 +259,6 @@ func TestSnapshotReaderExecuteRepairsExpiredPaidSnapshotWithStore(t *testing.T)
require.NoError(t, store.Accounts().Create(context.Background(), ports.CreateAccountInput{
Account: accountRecord,
Reservation: account.RaceNameReservation{
CanonicalKey: account.RaceNameCanonicalKey("pilot nova"),
UserID: userID,
RaceName: accountRecord.RaceName,
ReservedAt: accountRecord.UpdatedAt,
},
}))
expiredEndsAt := now.Add(-time.Minute)
@@ -239,6 +301,7 @@ func TestSnapshotReaderExecuteRepairsExpiredPaidSnapshotWithStore(t *testing.T)
require.Equal(t, []EffectiveLimitView{
{LimitCode: "max_pending_public_applications", Value: 3},
{LimitCode: "max_active_game_memberships", Value: 3},
{LimitCode: "max_registered_race_names", Value: 1},
}, result.EffectiveLimits)
storedSnapshot, err := store.EntitlementSnapshots().GetByUserID(context.Background(), userID)
@@ -264,7 +327,7 @@ func (store fakeAccountStore) GetByEmail(context.Context, common.Email) (account
return account.UserAccount{}, ports.ErrNotFound
}
func (store fakeAccountStore) GetByRaceName(context.Context, common.RaceName) (account.UserAccount, error) {
func (store fakeAccountStore) GetByUserName(context.Context, common.UserName) (account.UserAccount, error) {
return account.UserAccount{}, ports.ErrNotFound
}
@@ -276,10 +339,6 @@ func (store fakeAccountStore) ExistsByUserID(_ context.Context, userID common.Us
return store.existsByUserID[userID], nil
}
func (store fakeAccountStore) RenameRaceName(context.Context, ports.RenameRaceNameInput) error {
return nil
}
func (store fakeAccountStore) Update(context.Context, account.UserAccount) error {
return nil
}
@@ -374,7 +433,7 @@ func (generator fixedIDGenerator) NewUserID() (common.UserID, error) {
return "", nil
}
func (generator fixedIDGenerator) NewInitialRaceName() (common.RaceName, error) {
func (generator fixedIDGenerator) NewUserName() (common.UserName, error) {
return "", nil
}
@@ -486,7 +545,7 @@ func validAccountRecord() account.UserAccount {
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"),
CreatedAt: createdAt,