package services import ( "context" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/database" "veza-backend-api/internal/models" ) func setupTestSessionService(t *testing.T) (*SessionService, *gorm.DB, *database.Database) { gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) err = gormDB.AutoMigrate(&models.User{}) require.NoError(t, err) // Create sessions table manually to match what service expects (or if models exist) // Since models.Session might not be in models package or used by AutoMigrate? // SessionService setup says: // query := INSERT INTO sessions (id, user_id, token_hash, created_at, expires_at, ip_address, user_agent) // The service uses raw SQL, so we need to ensure table exists. // But SessionService struct 'Session' has db tags. // Let's generic DB wrapper handling. wrapper uses sql.DB. // Let's create table manually to be safe or check if Session model is exported in internal/models // The service defines its OWN Session struct in internal/services/session_service.go // So we must manually create table in test. err = gormDB.Exec(` CREATE TABLE sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, token_hash TEXT NOT NULL, created_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL, revoked_at TIMESTAMP, ip_address TEXT, user_agent TEXT ) `).Error require.NoError(t, err) sqlDB, err := gormDB.DB() require.NoError(t, err) testDB := &database.Database{ DB: sqlDB, } logger := zap.NewNop() service := NewSessionService(testDB, logger) return service, gormDB, testDB } func TestSessionService_CreateAndValidate(t *testing.T) { service, _, _ := setupTestSessionService(t) ctx := context.Background() userID := uuid.New() token := "test-token" req := &SessionCreateRequest{ UserID: userID, Token: token, IPAddress: "127.0.0.1", UserAgent: "TestAgent", ExpiresIn: time.Hour, } session, err := service.CreateSession(ctx, req) assert.NoError(t, err) assert.NotNil(t, session) assert.Equal(t, userID, session.UserID) // Validate validSession, err := service.ValidateSession(ctx, token) assert.NoError(t, err) assert.NotNil(t, validSession) assert.Equal(t, session.ID, validSession.ID) } func TestSessionService_Revoke(t *testing.T) { service, _, _ := setupTestSessionService(t) ctx := context.Background() userID := uuid.New() token := "test-token-revoke" req := &SessionCreateRequest{ UserID: userID, Token: token, ExpiresIn: time.Hour, } _, err := service.CreateSession(ctx, req) require.NoError(t, err) err = service.RevokeSession(ctx, token) assert.NoError(t, err) // Validate should fail _, err = service.ValidateSession(ctx, token) assert.Error(t, err) } func TestSessionService_Cleanup(t *testing.T) { service, _, _ := setupTestSessionService(t) ctx := context.Background() userID := uuid.New() // Create expired session // Since CreateSession sets expiresAt based on Now(), we'll hack it by short duration and sleeping, // OR just manually insert an expired one? // Creating with very short duration is easier if possible, but 1ms might be flaky. // We can cheat by passing negative duration if logic allows? // CreateSession: expiresAt := time.Now().Add(expiresIn). req := &SessionCreateRequest{ UserID: userID, Token: "expired-token", ExpiresIn: -1 * time.Hour, } // CreateSession checks if expiresIn == 0 defaults to 24h. But negative is fine. _, err := service.CreateSession(ctx, req) require.NoError(t, err) err = service.CleanupExpiredSessions(ctx) assert.NoError(t, err) // Check count stats, err := service.GetSessionStats(ctx) assert.NoError(t, err) assert.Equal(t, int64(0), stats["total_active"]) }