veza/veza-backend-api/internal/handlers/session_test.go

353 lines
10 KiB
Go

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) RevokeOtherUserSessions(ctx context.Context, userID uuid.UUID, exceptTokenHash string) (int64, error) {
args := m.Called(ctx, userID, exceptTokenHash)
return args.Get(0).(int64), args.Error(1)
}
func (m *MockSessionServiceForHandler) GetUserSessions(ctx context.Context, userID uuid.UUID) ([]*services.Session, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*services.Session), args.Error(1)
}
func (m *MockSessionServiceForHandler) DeleteSession(ctx context.Context, tokenHash string) error {
args := m.Called(ctx, 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", mock.Anything, userID).Return(expectedSessions, nil)
// HashTokenForMiddleware not called when no Authorization header
// 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", mock.Anything, userID).Return(expectedSessions, nil)
mockSessionService.On("DeleteSession", mock.Anything, "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", mock.Anything, 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")
}