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
@@ -21,6 +21,7 @@ func TestApplySanctionServiceExecutePublishesEvent(t *testing.T) {
limitStore := newFakeLimitStore()
publisher := &recordingPolicyPublisher{}
lifecyclePublisher := &fakeLifecyclePublisher{}
service, err := NewApplySanctionServiceWithObservability(
fakeAccountStore{existsByUserID: map[common.UserID]bool{userID: true}},
sanctionStore,
@@ -31,6 +32,7 @@ func TestApplySanctionServiceExecutePublishesEvent(t *testing.T) {
nil,
nil,
publisher,
lifecyclePublisher,
)
require.NoError(t, err)
@@ -47,8 +49,130 @@ func TestApplySanctionServiceExecutePublishesEvent(t *testing.T) {
require.Len(t, publisher.sanctionEvents, 1)
require.Equal(t, ports.SanctionChangedOperationApplied, publisher.sanctionEvents[0].Operation)
require.Equal(t, common.Source("admin_internal_api"), publisher.sanctionEvents[0].Source)
require.Empty(t, lifecyclePublisher.events,
"login_block must not emit a user.lifecycle.permanent_blocked event")
}
func TestApplySanctionServiceExecutePermanentBlockPublishesLifecycleEvent(t *testing.T) {
t.Parallel()
now := time.Unix(1_775_240_000, 0).UTC()
userID := common.UserID("user-123")
sanctionStore := newFakeSanctionStore()
limitStore := newFakeLimitStore()
publisher := &recordingPolicyPublisher{}
lifecyclePublisher := &fakeLifecyclePublisher{}
service, err := NewApplySanctionServiceWithObservability(
fakeAccountStore{existsByUserID: map[common.UserID]bool{userID: true}},
sanctionStore,
limitStore,
&fakePolicyLifecycleStore{sanctions: sanctionStore, limits: limitStore},
fixedClock{now: now},
fixedIDGenerator{sanctionRecordID: policy.SanctionRecordID("sanction-1")},
nil,
nil,
publisher,
lifecyclePublisher,
)
require.NoError(t, err)
appliedAt := now.Add(-time.Minute)
_, err = service.Execute(context.Background(), ApplySanctionInput{
UserID: userID.String(),
SanctionCode: string(policy.SanctionCodePermanentBlock),
Scope: "platform",
ReasonCode: "terminal_policy_violation",
Actor: ActorInput{Type: "admin", ID: "admin-1"},
AppliedAt: appliedAt.Format(time.RFC3339Nano),
})
require.NoError(t, err)
require.Len(t, publisher.sanctionEvents, 1)
require.Len(t, lifecyclePublisher.events, 1)
emitted := lifecyclePublisher.events[0]
require.Equal(t, ports.UserLifecyclePermanentBlockedEventType, emitted.EventType)
require.Equal(t, userID, emitted.UserID)
require.True(t, emitted.OccurredAt.Equal(appliedAt.UTC()))
require.Equal(t, common.Source("admin_internal_api"), emitted.Source)
require.Equal(t, common.ReasonCode("terminal_policy_violation"), emitted.ReasonCode)
require.Equal(t, common.ActorRef{Type: common.ActorType("admin"), ID: common.ActorID("admin-1")}, emitted.Actor)
}
func TestRemoveSanctionServicePermanentBlockDoesNotEmitLifecycleEvent(t *testing.T) {
t.Parallel()
now := time.Unix(1_775_240_000, 0).UTC()
userID := common.UserID("user-123")
sanctionStore := newFakeSanctionStore()
limitStore := newFakeLimitStore()
publisher := &recordingPolicyPublisher{}
lifecyclePublisher := &fakeLifecyclePublisher{}
// First, apply permanent_block so a subsequent remove has an active record
// to target.
applyService, err := NewApplySanctionServiceWithObservability(
fakeAccountStore{existsByUserID: map[common.UserID]bool{userID: true}},
sanctionStore,
limitStore,
&fakePolicyLifecycleStore{sanctions: sanctionStore, limits: limitStore},
fixedClock{now: now},
fixedIDGenerator{sanctionRecordID: policy.SanctionRecordID("sanction-1")},
nil,
nil,
publisher,
lifecyclePublisher,
)
require.NoError(t, err)
_, err = applyService.Execute(context.Background(), ApplySanctionInput{
UserID: userID.String(),
SanctionCode: string(policy.SanctionCodePermanentBlock),
Scope: "platform",
ReasonCode: "terminal_policy_violation",
Actor: ActorInput{Type: "admin", ID: "admin-1"},
AppliedAt: now.Add(-time.Hour).Format(time.RFC3339Nano),
})
require.NoError(t, err)
require.Len(t, lifecyclePublisher.events, 1)
removeService, err := NewRemoveSanctionServiceWithObservability(
fakeAccountStore{existsByUserID: map[common.UserID]bool{userID: true}},
sanctionStore,
limitStore,
&fakePolicyLifecycleStore{sanctions: sanctionStore, limits: limitStore},
fixedClock{now: now},
fixedIDGenerator{},
nil,
nil,
publisher,
)
require.NoError(t, err)
_, err = removeService.Execute(context.Background(), RemoveSanctionInput{
UserID: userID.String(),
SanctionCode: string(policy.SanctionCodePermanentBlock),
ReasonCode: "appeal_granted",
Actor: ActorInput{Type: "admin", ID: "admin-2"},
})
require.NoError(t, err)
require.Len(t, lifecyclePublisher.events, 1,
"remove-sanction must not emit an additional lifecycle event")
}
type fakeLifecyclePublisher struct {
events []ports.UserLifecycleEvent
}
func (publisher *fakeLifecyclePublisher) PublishUserLifecycleEvent(_ context.Context, event ports.UserLifecycleEvent) error {
if err := event.Validate(); err != nil {
return err
}
publisher.events = append(publisher.events, event)
return nil
}
var _ ports.UserLifecyclePublisher = (*fakeLifecyclePublisher)(nil)
func TestRemoveSanctionServiceExecuteMissingDoesNotPublishEvent(t *testing.T) {
t.Parallel()