diff --git a/VERSION b/VERSION index 9bff09870..c7a29c710 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.921 +0.922 diff --git a/veza-backend-api/internal/handlers/dashboard_test.go b/veza-backend-api/internal/handlers/dashboard_test.go new file mode 100644 index 000000000..82dceb64e --- /dev/null +++ b/veza-backend-api/internal/handlers/dashboard_test.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "veza-backend-api/internal/models" + "veza-backend-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +// mockDashboardAuditService returns empty stats and activity for tests +type mockDashboardAuditService struct { + statsErr error + activityErr error +} + +func (m *mockDashboardAuditService) GetStats(ctx context.Context, startDate, endDate time.Time) ([]*services.AuditStats, error) { + if m.statsErr != nil { + return nil, m.statsErr + } + return []*services.AuditStats{}, nil +} + +func (m *mockDashboardAuditService) GetUserActivity(ctx context.Context, userID uuid.UUID, limit int) ([]*services.AuditLog, error) { + if m.activityErr != nil { + return nil, m.activityErr + } + return []*services.AuditLog{}, nil +} + +// mockDashboardTrackService returns empty tracks for tests +type mockDashboardTrackService struct { + tracksErr error +} + +func (m *mockDashboardTrackService) ListTracks(ctx context.Context, params TrackListParamsForDashboard) ([]*models.Track, int64, error) { + if m.tracksErr != nil { + return nil, 0, m.tracksErr + } + return []*models.Track{}, 0, nil +} + +func TestDashboardHandler_GetDashboard_HappyPath(t *testing.T) { + gin.SetMode(gin.TestMode) + logger := zap.NewNop() + handler := NewDashboardHandlerWithInterface( + &mockDashboardAuditService{}, + &mockDashboardTrackService{}, + logger, + ) + + router := gin.New() + userID := uuid.New() + router.Use(func(c *gin.Context) { + c.Set("user_id", userID) + c.Next() + }) + router.GET("/dashboard", handler.GetDashboard()) + + req := httptest.NewRequest("GET", "/dashboard", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + var resp APIResponse + err := json.Unmarshal(w.Body.Bytes(), &resp) + require.NoError(t, err) + assert.True(t, resp.Success) + assert.NotNil(t, resp.Data) +} + +func TestDashboardHandler_GetDashboard_Unauthorized(t *testing.T) { + gin.SetMode(gin.TestMode) + logger := zap.NewNop() + handler := NewDashboardHandlerWithInterface( + &mockDashboardAuditService{}, + &mockDashboardTrackService{}, + logger, + ) + + router := gin.New() + // No auth middleware - no user_id in context + router.GET("/dashboard", handler.GetDashboard()) + + req := httptest.NewRequest("GET", "/dashboard", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestDashboardHandler_GetDashboard_WithQueryParams(t *testing.T) { + gin.SetMode(gin.TestMode) + logger := zap.NewNop() + handler := NewDashboardHandlerWithInterface( + &mockDashboardAuditService{}, + &mockDashboardTrackService{}, + logger, + ) + + router := gin.New() + userID := uuid.New() + router.Use(func(c *gin.Context) { + c.Set("user_id", userID) + c.Next() + }) + router.GET("/dashboard", handler.GetDashboard()) + + req := httptest.NewRequest("GET", "/dashboard?activity_limit=5&library_limit=3&stats_period=7d", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} diff --git a/veza-backend-api/internal/handlers/presence_handler_test.go b/veza-backend-api/internal/handlers/presence_handler_test.go new file mode 100644 index 000000000..ce491908f --- /dev/null +++ b/veza-backend-api/internal/handlers/presence_handler_test.go @@ -0,0 +1,85 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "veza-backend-api/internal/models" + "veza-backend-api/internal/services" + + "github.com/gin-gonic/gin" + "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" +) + +func setupPresenceHandler(t *testing.T) (*PresenceHandler, *gin.Engine) { + gin.SetMode(gin.TestMode) + logger := zap.NewNop() + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, db.AutoMigrate(&models.UserPresence{})) + + presenceSvc := services.NewPresenceService(db, logger) + handler := NewPresenceHandler(presenceSvc, logger) + + router := gin.New() + router.Use(func(c *gin.Context) { + if uidStr := c.GetHeader("X-User-ID"); uidStr != "" { + if uid, err := uuid.Parse(uidStr); err == nil { + c.Set("user_id", uid) + } + } + c.Next() + }) + router.GET("/users/:id/presence", handler.GetPresence) + router.PUT("/users/me/presence", handler.UpdatePresence) + + return handler, router +} + +func TestPresenceHandler_GetPresence_InvalidUserID(t *testing.T) { + _, router := setupPresenceHandler(t) + + req := httptest.NewRequest("GET", "/users/invalid-id/presence", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestPresenceHandler_GetPresence_NotFound(t *testing.T) { + _, router := setupPresenceHandler(t) + userID := uuid.New() + viewerID := uuid.New() + + // Pass X-User-ID so GetUserIDUUID (optional viewer) succeeds; request targets a different user with no presence + req := httptest.NewRequest("GET", "/users/"+userID.String()+"/presence", nil) + req.Header.Set("X-User-ID", viewerID.String()) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + // Should return 200 with offline status when user has no presence + assert.Equal(t, http.StatusOK, w.Code) + var resp APIResponse + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + assert.True(t, resp.Success) +} + +func TestPresenceHandler_UpdatePresence_Unauthorized(t *testing.T) { + _, router := setupPresenceHandler(t) + body := bytes.NewBufferString(`{"status":"online"}`) + req := httptest.NewRequest("PUT", "/users/me/presence", body) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + // No user_id in context - GetUserIDUUID fails, handler returns 401 via RespondWithAppError + assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusBadRequest) +}