package revokedevicesession import ( "context" "errors" "testing" "time" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/devicesession" "galaxy/authsession/internal/domain/gatewayprojection" "galaxy/authsession/internal/service/shared" "galaxy/authsession/internal/testkit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestExecuteRevokesActiveSessionAndPublishes(t *testing.T) { t.Parallel() store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{} record := activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC()) if err := store.Create(context.Background(), record); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ DeviceSessionID: "device-session-1", ReasonCode: "logout_all", ActorType: "system", }) require.NoError(t, err) assert.Equal(t, "revoked", result.Outcome) assert.EqualValues(t, 1, result.AffectedSessionCount) assert.Equal(t, "device-session-1", result.DeviceSessionID) stored, err := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, err) require.NotNil(t, stored.Revocation) assert.Equal(t, devicesession.StatusRevoked, stored.Status) assert.Equal(t, devicesession.RevokeReasonLogoutAll, stored.Revocation.ReasonCode) assert.Equal(t, common.RevokeActorType("system"), stored.Revocation.ActorType) assert.Empty(t, stored.Revocation.ActorID) assert.Equal(t, time.Unix(20, 0).UTC(), stored.Revocation.At) published := publisher.PublishedSnapshots() require.Len(t, published, 1) assert.Equal(t, gatewayprojection.StatusRevoked, published[0].Status) assert.Equal(t, common.DeviceSessionID("device-session-1"), published[0].DeviceSessionID) assert.Equal(t, devicesession.RevokeReasonLogoutAll, published[0].RevokeReasonCode) assert.Equal(t, common.RevokeActorType("system"), published[0].RevokeActorType) require.NotNil(t, published[0].RevokedAt) assert.Equal(t, time.Unix(20, 0).UTC(), published[0].RevokedAt.UTC()) } func TestExecuteAlreadyRevokedReturnsZeroAffectedAndRepublishes(t *testing.T) { t.Parallel() store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{} record := revokedSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC()) if err := store.Create(context.Background(), record); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) result, err := service.Execute(context.Background(), Input{ DeviceSessionID: "device-session-1", ReasonCode: "logout_all", ActorType: "system", }) require.NoError(t, err) assert.Equal(t, "already_revoked", result.Outcome) assert.EqualValues(t, 0, result.AffectedSessionCount) assert.Equal(t, "device-session-1", result.DeviceSessionID) stored, err := store.Get(context.Background(), common.DeviceSessionID("device-session-1")) require.NoError(t, err) require.NotNil(t, stored.Revocation) assert.Equal(t, *record.Revocation, *stored.Revocation) published := publisher.PublishedSnapshots() require.Len(t, published, 1) assert.Equal(t, gatewayprojection.StatusRevoked, published[0].Status) assert.Equal(t, devicesession.RevokeReasonLogoutAll, published[0].RevokeReasonCode) assert.Equal(t, common.RevokeActorType("system"), published[0].RevokeActorType) require.NotNil(t, published[0].RevokedAt) assert.Equal(t, record.Revocation.At, *published[0].RevokedAt) } func TestExecuteReturnsSessionNotFound(t *testing.T) { t.Parallel() service, err := New(&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{ DeviceSessionID: "missing", ReasonCode: "logout_all", ActorType: "system", }) assert.Equal(t, shared.ErrorCodeSessionNotFound, shared.CodeOf(err)) } func TestExecuteReturnsServiceUnavailableWhenPublishFails(t *testing.T) { t.Parallel() store := &testkit.InMemorySessionStore{} publisher := &testkit.RecordingProjectionPublisher{Err: errors.New("publish failed")} record := activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC()) if err := store.Create(context.Background(), record); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } service, err := New(store, publisher, testkit.FixedClock{Time: time.Unix(20, 0).UTC()}) require.NoError(t, err) _, err = service.Execute(context.Background(), Input{ DeviceSessionID: "device-session-1", ReasonCode: "logout_all", ActorType: "system", }) 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.StatusRevoked, stored.Status) assert.Equal(t, devicesession.RevokeReasonLogoutAll, stored.Revocation.ReasonCode) assert.Equal(t, common.RevokeActorType("system"), stored.Revocation.ActorType) } 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, } } func revokedSessionFixture(deviceSessionID string, userID string, createdAt time.Time) devicesession.Session { record := activeSessionFixture(deviceSessionID, userID, createdAt) record.Status = devicesession.StatusRevoked record.Revocation = &devicesession.Revocation{ At: createdAt.Add(time.Minute), ReasonCode: devicesession.RevokeReasonLogoutAll, ActorType: common.RevokeActorType("system"), } return record }