package handlers import ( "context" "net/http" "net/http/httptest" "testing" "time" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.uber.org/zap" ) // MockSessionServiceForHandler mocks SessionService for session handler type MockSessionServiceForHandler struct { mock.Mock } func (m *MockSessionServiceForHandler) RevokeSession(ctx context.Context, token string) error { args := m.Called(ctx, token) return args.Error(0) } func (m *MockSessionServiceForHandler) RevokeAllUserSessions(ctx context.Context, userID uuid.UUID) (int64, error) { args := m.Called(ctx, userID) return args.Get(0).(int64), args.Error(1) } func (m *MockSessionServiceForHandler) GetUserSessions(userID uuid.UUID) ([]*services.Session, error) { args := m.Called(userID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).([]*services.Session), args.Error(1) } func (m *MockSessionServiceForHandler) DeleteSession(tokenHash string) error { args := m.Called(tokenHash) return args.Error(0) } func (m *MockSessionServiceForHandler) RefreshSession(ctx context.Context, token string, newExpiresIn time.Duration) error { args := m.Called(ctx, token, newExpiresIn) return args.Error(0) } func (m *MockSessionServiceForHandler) GetSessionStats(ctx context.Context) (map[string]interface{}, error) { args := m.Called(ctx) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(map[string]interface{}), args.Error(1) } func (m *MockSessionServiceForHandler) HashTokenForMiddleware(token string) string { args := m.Called(token) return args.String(0) } // MockAuditServiceForSession mocks AuditService for session operations type MockAuditServiceForSession struct { mock.Mock } func (m *MockAuditServiceForSession) LogPasswordResetRequest(ctx context.Context, userID *uuid.UUID, email, ip, userAgent string) error { args := m.Called(ctx, userID, email, ip, userAgent) return args.Error(0) } func (m *MockAuditServiceForSession) LogPasswordReset(ctx context.Context, userID uuid.UUID, success bool, ip, userAgent string) error { args := m.Called(ctx, userID, success, ip, userAgent) return args.Error(0) } // LogSessionRevocation is not needed for AuditServiceInterfaceForSession func setupTestSessionRouter(mockSessionService *MockSessionServiceForHandler, mockAuditService *MockAuditServiceForSession) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() logger := zap.NewNop() handler := NewSessionHandlerWithInterface(mockSessionService, mockAuditService, logger) api := router.Group("/api/v1/sessions") api.Use(func(c *gin.Context) { userIDStr := c.GetHeader("X-User-ID") if userIDStr != "" { uid, err := uuid.Parse(userIDStr) if err == nil { c.Set("user_id", uid) } } c.Next() }) { api.POST("/logout", handler.Logout()) api.POST("/logout-all", handler.LogoutAll()) api.GET("/", handler.GetSessions()) api.DELETE("/:session_id", handler.RevokeSession()) api.GET("/stats", handler.GetSessionStats()) api.POST("/refresh", handler.RefreshSession()) } return router } func TestSessionHandler_Logout_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() token := "test-token" mockSessionService.On("RevokeSession", mock.Anything, token).Return(nil) // Execute req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil) req.Header.Set("X-User-ID", userID.String()) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_Logout_Unauthorized(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) // Execute - No X-User-ID header req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusUnauthorized, w.Code) mockSessionService.AssertNotCalled(t, "RevokeSession") } func TestSessionHandler_Logout_NoAuthHeader(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() // Execute - No Authorization header req, _ := http.NewRequest("POST", "/api/v1/sessions/logout", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusBadRequest, w.Code) mockSessionService.AssertNotCalled(t, "RevokeSession") } func TestSessionHandler_LogoutAll_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() revokedCount := int64(3) mockSessionService.On("RevokeAllUserSessions", mock.Anything, userID).Return(revokedCount, nil) // Execute req, _ := http.NewRequest("POST", "/api/v1/sessions/logout-all", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_GetSessions_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() sessionID := uuid.New() expectedSessions := []*services.Session{ { ID: sessionID, UserID: userID, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(24 * time.Hour), IPAddress: "127.0.0.1", UserAgent: "test-agent", }, } mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil) mockSessionService.On("HashTokenForMiddleware", mock.Anything).Return("hashed-token") // Execute req, _ := http.NewRequest("GET", "/api/v1/sessions/", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_RevokeSession_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() sessionID := uuid.New() expectedSessions := []*services.Session{ { ID: sessionID, UserID: userID, TokenHash: "hashed-token", }, } mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil) mockSessionService.On("DeleteSession", "hashed-token").Return(nil) // Execute req, _ := http.NewRequest("DELETE", "/api/v1/sessions/"+sessionID.String(), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_RevokeSession_NotFound(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() sessionID := uuid.New() expectedSessions := []*services.Session{ { ID: uuid.New(), // Different session ID UserID: userID, }, } mockSessionService.On("GetUserSessions", userID).Return(expectedSessions, nil) // Execute req, _ := http.NewRequest("DELETE", "/api/v1/sessions/"+sessionID.String(), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusNotFound, w.Code) mockSessionService.AssertNotCalled(t, "DeleteSession") } func TestSessionHandler_GetSessionStats_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() expectedStats := map[string]interface{}{ "total_sessions": 10, "active_sessions": 5, } mockSessionService.On("GetSessionStats", mock.Anything).Return(expectedStats, nil) // Execute req, _ := http.NewRequest("GET", "/api/v1/sessions/stats", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_RefreshSession_Success(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() token := "test-token" mockSessionService.On("RefreshSession", mock.Anything, token, 24*time.Hour).Return(nil) // Execute req, _ := http.NewRequest("POST", "/api/v1/sessions/refresh", nil) req.Header.Set("X-User-ID", userID.String()) req.Header.Set("Authorization", "Bearer "+token) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusOK, w.Code) mockSessionService.AssertExpectations(t) } func TestSessionHandler_RefreshSession_NoAuthHeader(t *testing.T) { // Setup mockSessionService := new(MockSessionServiceForHandler) mockAuditService := new(MockAuditServiceForSession) router := setupTestSessionRouter(mockSessionService, mockAuditService) userID := uuid.New() // Execute - No Authorization header req, _ := http.NewRequest("POST", "/api/v1/sessions/refresh", nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusBadRequest, w.Code) mockSessionService.AssertNotCalled(t, "RefreshSession") }