348 lines
10 KiB
Go
348 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) 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")
|
|
}
|