package blockuser import ( "context" "errors" "testing" "time" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/devicesession" "galaxy/authsession/internal/domain/gatewayprojection" "galaxy/authsession/internal/domain/userresolution" "galaxy/authsession/internal/service/shared" "galaxy/authsession/internal/testkit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestExecuteBlocksByUserIDAndRevokesSessions(t *testing.T) { t.Parallel() userDirectory := &testkit.InMemoryUserDirectory{} store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{} if err := userDirectory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1")); err != nil { require.Failf(t, "test failed", "SeedExisting() returned error: %v", err) } if err := store.Create(context.Background(), activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC())); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(userDirectory, store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ UserID: "user-1", ReasonCode: "policy_block", ActorType: "admin", }) require.NoError(t, err) assert.Equal(t, "blocked", result.Outcome) assert.EqualValues(t, 1, result.AffectedSessionCount) assert.Equal(t, SubjectKindUserID, result.SubjectKind) assert.Equal(t, "user-1", result.SubjectValue) assert.Equal(t, []string{"device-session-1"}, result.AffectedDeviceSessionIDs) stored, getErr := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, getErr) require.NotNil(t, stored.Revocation) assert.Equal(t, devicesession.StatusRevoked, stored.Status) assert.Equal(t, devicesession.RevokeReasonUserBlocked, stored.Revocation.ReasonCode) assert.Equal(t, common.RevokeActorType("admin"), stored.Revocation.ActorType) resolution, resolveErr := userDirectory.ResolveByEmail(context.Background(), common.Email("pilot@example.com")) require.NoError(t, resolveErr) assert.Equal(t, userresolution.KindBlocked, resolution.Kind) assert.Equal(t, userresolution.BlockReasonCode("policy_block"), resolution.BlockReasonCode) published := publisher.PublishedSnapshots() require.Len(t, published, 1) assert.Equal(t, gatewayprojection.StatusRevoked, published[0].Status) assert.Equal(t, devicesession.RevokeReasonUserBlocked, published[0].RevokeReasonCode) assert.Equal(t, common.RevokeActorType("admin"), published[0].RevokeActorType) } func TestExecuteBlocksByEmailWithoutExistingUser(t *testing.T) { t.Parallel() userDirectory := &testkit.InMemoryUserDirectory{} publisher := &testkit.RecordingProjectionPublisher{} service, err := New(userDirectory, &testkit.InMemorySessionStore{}, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ Email: "pilot@example.com", ReasonCode: "policy_block", ActorType: "admin", }) require.NoError(t, err) assert.Equal(t, "blocked", result.Outcome) assert.EqualValues(t, 0, result.AffectedSessionCount) assert.Equal(t, SubjectKindEmail, result.SubjectKind) assert.Equal(t, "pilot@example.com", result.SubjectValue) require.NotNil(t, result.AffectedDeviceSessionIDs) assert.Empty(t, result.AffectedDeviceSessionIDs) resolution, resolveErr := userDirectory.ResolveByEmail(context.Background(), common.Email("pilot@example.com")) require.NoError(t, resolveErr) assert.Equal(t, userresolution.KindBlocked, resolution.Kind) assert.Equal(t, userresolution.BlockReasonCode("policy_block"), resolution.BlockReasonCode) assert.Empty(t, publisher.PublishedSnapshots()) } func TestExecuteBlocksByEmailWithExistingUserAndRevokesSessions(t *testing.T) { t.Parallel() userDirectory := &testkit.InMemoryUserDirectory{} store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{} if err := userDirectory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1")); err != nil { require.Failf(t, "test failed", "SeedExisting() returned error: %v", err) } if err := store.Create(context.Background(), activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC())); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(userDirectory, store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ Email: "pilot@example.com", ReasonCode: "policy_block", ActorType: "admin", }) require.NoError(t, err) assert.Equal(t, "blocked", result.Outcome) assert.EqualValues(t, 1, result.AffectedSessionCount) assert.Equal(t, []string{"device-session-1"}, result.AffectedDeviceSessionIDs) stored, getErr := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, getErr) require.NotNil(t, stored.Revocation) assert.Equal(t, devicesession.RevokeReasonUserBlocked, stored.Revocation.ReasonCode) assert.Equal(t, common.RevokeActorType("admin"), stored.Revocation.ActorType) resolution, resolveErr := userDirectory.ResolveByEmail(context.Background(), common.Email("pilot@example.com")) require.NoError(t, resolveErr) assert.Equal(t, userresolution.KindBlocked, resolution.Kind) assert.Equal(t, userresolution.BlockReasonCode("policy_block"), resolution.BlockReasonCode) published := publisher.PublishedSnapshots() require.Len(t, published, 1) assert.Equal(t, devicesession.RevokeReasonUserBlocked, published[0].RevokeReasonCode) } func TestExecuteReturnsSubjectNotFoundForUnknownUserID(t *testing.T) { t.Parallel() service, err := New(&testkit.InMemoryUserDirectory{}, &testkit.InMemorySessionStore{}, &testkit.RecordingProjectionPublisher{}, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) if err != nil { require.Failf(t, "test failed", "New() returned error: %v", err) } _, err = service.Execute(context.Background(), Input{ UserID: "missing", ReasonCode: "policy_block", ActorType: "admin", }) assert.Equal(t, shared.ErrorCodeSubjectNotFound, shared.CodeOf(err)) } func TestExecuteAlreadyBlockedStillRevokesLingeringSessions(t *testing.T) { t.Parallel() userDirectory := &testkit.InMemoryUserDirectory{} store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{} if err := userDirectory.SeedBlockedUser(common.Email("pilot@example.com"), common.UserID("user-1"), userresolution.BlockReasonCode("policy_block")); err != nil { require.Failf(t, "test failed", "SeedBlockedUser() returned error: %v", err) } if err := store.Create(context.Background(), activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC())); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(userDirectory, store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ Email: "pilot@example.com", ReasonCode: "policy_block", ActorType: "admin", }) require.NoError(t, err) assert.Equal(t, "already_blocked", result.Outcome) assert.EqualValues(t, 1, result.AffectedSessionCount) assert.Equal(t, []string{"device-session-1"}, result.AffectedDeviceSessionIDs) stored, getErr := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, getErr) require.NotNil(t, stored.Revocation) assert.Equal(t, devicesession.RevokeReasonUserBlocked, stored.Revocation.ReasonCode) assert.Equal(t, common.RevokeActorType("admin"), stored.Revocation.ActorType) published := publisher.PublishedSnapshots() require.Len(t, published, 1) assert.Equal(t, devicesession.RevokeReasonUserBlocked, published[0].RevokeReasonCode) } func TestExecuteReturnsServiceUnavailableWhenPublishFails(t *testing.T) { t.Parallel() userDirectory := &testkit.InMemoryUserDirectory{} store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{Err: errors.New("publish failed")} if err := userDirectory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1")); err != nil { require.Failf(t, "test failed", "SeedExisting() returned error: %v", err) } if err := store.Create(context.Background(), activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC())); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(userDirectory, store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) _, err = service.Execute(context.Background(), Input{ UserID: "user-1", ReasonCode: "policy_block", ActorType: "admin", }) assert.Equal(t, shared.ErrorCodeServiceUnavailable, shared.CodeOf(err)) stored, getErr := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, getErr) require.NotNil(t, stored.Revocation) assert.Equal(t, devicesession.RevokeReasonUserBlocked, stored.Revocation.ReasonCode) resolution, resolveErr := userDirectory.ResolveByEmail(context.Background(), common.Email("pilot@example.com")) require.NoError(t, resolveErr) assert.Equal(t, userresolution.KindBlocked, resolution.Kind) assert.Equal(t, userresolution.BlockReasonCode("policy_block"), resolution.BlockReasonCode) } func activeSessionFixture(deviceSessionID string, userID string, createdAt time.Time) devicesession.Session { key, err := common.NewClientPublicKey(make([]byte, 32)) if err != nil { panic(err) } return devicesession.Session{ ID: common.DeviceSessionID(deviceSessionID), UserID: common.UserID(userID), ClientPublicKey: key, Status: devicesession.StatusActive, CreatedAt: createdAt, } }