package testkit import ( "context" "errors" "github.com/stretchr/testify/require" "testing" "time" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/devicesession" "galaxy/authsession/internal/ports" ) func TestInMemorySessionStoreCreateAndGet(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} 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) } got, err := store.Get(context.Background(), record.ID) if err != nil { require.Failf(t, "test failed", "Get() returned error: %v", err) } if got.ID != record.ID { require.Failf(t, "test failed", "Get().ID = %q, want %q", got.ID, record.ID) } } func TestInMemorySessionStoreListByUserIDNewestFirst(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} older := activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC()) newer := activeSessionFixture("device-session-2", "user-1", time.Unix(20, 0).UTC()) otherUser := activeSessionFixture("device-session-3", "user-2", time.Unix(30, 0).UTC()) for _, record := range []devicesession.Session{older, newer, otherUser} { if err := store.Create(context.Background(), record); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } } got, err := store.ListByUserID(context.Background(), common.UserID("user-1")) if err != nil { require.Failf(t, "test failed", "ListByUserID() returned error: %v", err) } if len(got) != 2 { require.Failf(t, "test failed", "ListByUserID() length = %d, want 2", len(got)) } if got[0].ID != newer.ID || got[1].ID != older.ID { require.Failf(t, "test failed", "ListByUserID() order = [%q %q], want [%q %q]", got[0].ID, got[1].ID, newer.ID, older.ID) } } func TestInMemorySessionStoreCountActiveByUserID(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} active := activeSessionFixture("device-session-1", "user-1", time.Unix(10, 0).UTC()) revoked := revokedSessionFixture("device-session-2", "user-1", time.Unix(20, 0).UTC()) for _, record := range []devicesession.Session{active, revoked} { if err := store.Create(context.Background(), record); err != nil { require.Failf(t, "test failed", "Create() returned error: %v", err) } } got, err := store.CountActiveByUserID(context.Background(), common.UserID("user-1")) if err != nil { require.Failf(t, "test failed", "CountActiveByUserID() returned error: %v", err) } if got != 1 { require.Failf(t, "test failed", "CountActiveByUserID() = %d, want 1", got) } } func TestInMemorySessionStoreRevokeIsIdempotent(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} 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) } input := ports.RevokeSessionInput{ DeviceSessionID: record.ID, Revocation: devicesession.Revocation{ At: time.Unix(30, 0).UTC(), ReasonCode: devicesession.RevokeReasonLogoutAll, ActorType: common.RevokeActorType("system"), }, } first, err := store.Revoke(context.Background(), input) if err != nil { require.Failf(t, "test failed", "first Revoke() returned error: %v", err) } if first.Outcome != ports.RevokeSessionOutcomeRevoked { require.Failf(t, "test failed", "first Revoke() outcome = %q, want %q", first.Outcome, ports.RevokeSessionOutcomeRevoked) } second, err := store.Revoke(context.Background(), input) if err != nil { require.Failf(t, "test failed", "second Revoke() returned error: %v", err) } if second.Outcome != ports.RevokeSessionOutcomeAlreadyRevoked { require.Failf(t, "test failed", "second Revoke() outcome = %q, want %q", second.Outcome, ports.RevokeSessionOutcomeAlreadyRevoked) } } func TestInMemorySessionStoreRevokeAllNoActiveSessions(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} 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) } input := ports.RevokeUserSessionsInput{ UserID: common.UserID("user-1"), Revocation: devicesession.Revocation{ At: time.Unix(40, 0).UTC(), ReasonCode: devicesession.RevokeReasonAdminRevoke, ActorType: common.RevokeActorType("admin"), }, } result, err := store.RevokeAllByUserID(context.Background(), input) if err != nil { require.Failf(t, "test failed", "RevokeAllByUserID() returned error: %v", err) } if result.Outcome != ports.RevokeUserSessionsOutcomeNoActiveSessions { require.Failf(t, "test failed", "RevokeAllByUserID() outcome = %q, want %q", result.Outcome, ports.RevokeUserSessionsOutcomeNoActiveSessions) } if len(result.Sessions) != 0 { require.Failf(t, "test failed", "RevokeAllByUserID() session count = %d, want 0", len(result.Sessions)) } } func TestInMemorySessionStoreGetNotFound(t *testing.T) { t.Parallel() store := &InMemorySessionStore{} _, err := store.Get(context.Background(), common.DeviceSessionID("missing")) if !errors.Is(err, ports.ErrNotFound) { require.Failf(t, "test failed", "Get() error = %v, want ErrNotFound", err) } } 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.RevokeReasonDeviceLogout, ActorType: common.RevokeActorType("user"), } return record }