//go:build integration || pagination // +build integration pagination package pagination import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/core/track" "veza-backend-api/internal/handlers" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "veza-backend-api/internal/services" ) // setupPaginationTestRouter crée un router de test avec des données pour tester la pagination func setupPaginationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, uuid.UUID, func()) { gin.SetMode(gin.TestMode) logger := zaptest.NewLogger(t) // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate models err = db.AutoMigrate( &models.User{}, &models.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, ) require.NoError(t, err) // Create test user userID := uuid.New() user := &models.User{ ID: userID, Email: "test@example.com", Username: "testuser", PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890", IsVerified: true, } err = db.Create(user).Error require.NoError(t, err) // Create 50 tracks for pagination testing uploadDir := t.TempDir() for i := 0; i < 50; i++ { track := &models.Track{ ID: uuid.New(), UserID: userID, Title: fmt.Sprintf("Test Track %d", i+1), IsPublic: true, } err = db.Create(track).Error require.NoError(t, err) } // Create 50 playlists for pagination testing for i := 0; i < 50; i++ { playlist := &models.Playlist{ ID: uuid.New(), UserID: userID, Title: fmt.Sprintf("Test Playlist %d", i+1), IsPublic: true, } err = db.Create(playlist).Error require.NoError(t, err) } // Create 10 additional users for pagination testing for i := 0; i < 10; i++ { user := &models.User{ ID: uuid.New(), Email: fmt.Sprintf("user%d@example.com", i+1), Username: fmt.Sprintf("user%d", i+1), PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890", IsVerified: true, } err = db.Create(user).Error require.NoError(t, err) } // Setup services (minimal setup for pagination tests) trackService := track.NewTrackService(db, logger, uploadDir) trackUploadService := services.NewTrackUploadService(db, logger) chunkService := services.NewTrackChunkService(t.TempDir(), nil, logger) likeService := services.NewTrackLikeService(db, logger) streamService := services.NewStreamService("http://localhost:8082", logger) trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService) playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) playlistHandler := handlers.NewPlaylistHandler(playlistService, db, logger) userService := services.NewUserServiceWithDB(userRepo, db) profileHandler := handlers.NewProfileHandler(userService, logger) // Create router router := gin.New() // Public routes tracks := router.Group("/api/v1/tracks") { tracks.GET("", trackHandler.ListTracks) } users := router.Group("/api/v1/users") { users.GET("", profileHandler.ListUsers) } // Protected routes (with mock auth) protected := router.Group("/api/v1") protected.Use(func(c *gin.Context) { c.Set("user_id", userID) c.Next() }) playlists := protected.Group("/playlists") { playlists.GET("", playlistHandler.GetPlaylists) } cleanup := func() { // Cleanup handled by t.TempDir() } return router, db, userID, cleanup } // TestPagination_Tracks_Default teste la pagination par défaut pour les tracks func TestPagination_Tracks_Default(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["success"].(bool)) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) tracks, ok := data["tracks"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(tracks), 20, "Default limit should be 20") pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, float64(1), pagination["page"], "Default page should be 1") assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20") } // TestPagination_Tracks_CustomParams teste la pagination avec des paramètres personnalisés func TestPagination_Tracks_CustomParams(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=2&limit=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) tracks, ok := data["tracks"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(tracks), 10, "Should return at most 10 tracks") pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, float64(2), pagination["page"], "Page should be 2") assert.Equal(t, float64(10), pagination["limit"], "Limit should be 10") } // TestPagination_Tracks_InvalidParams teste la validation des paramètres invalides func TestPagination_Tracks_InvalidParams(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() tests := []struct { name string query string expectedPage float64 expectedLimit float64 }{ { name: "Negative page", query: "page=-1&limit=10", expectedPage: 1, expectedLimit: 10, }, { name: "Zero page", query: "page=0&limit=10", expectedPage: 1, expectedLimit: 10, }, { name: "Negative limit", query: "page=1&limit=-5", expectedPage: 1, expectedLimit: 20, }, { name: "Zero limit", query: "page=1&limit=0", expectedPage: 1, expectedLimit: 20, }, { name: "Limit too high", query: "page=1&limit=200", expectedPage: 1, expectedLimit: 20, // Should default to 20 or max 100 }, { name: "Invalid page format", query: "page=abc&limit=10", expectedPage: 1, expectedLimit: 10, }, { name: "Invalid limit format", query: "page=1&limit=xyz", expectedPage: 1, expectedLimit: 20, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?%s", tt.query), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, tt.expectedPage, pagination["page"], "Page should be corrected") assert.Equal(t, tt.expectedLimit, pagination["limit"], "Limit should be corrected") }) } } // TestPagination_Tracks_Metadata teste les métadonnées de pagination func TestPagination_Tracks_Metadata(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) // Verify pagination metadata assert.Contains(t, pagination, "page") assert.Contains(t, pagination, "limit") assert.Contains(t, pagination, "total") assert.Contains(t, pagination, "total_pages") total := pagination["total"].(float64) assert.Greater(t, total, float64(0), "Total should be greater than 0") totalPages := pagination["total_pages"].(float64) assert.Greater(t, totalPages, float64(0), "Total pages should be greater than 0") } // TestPagination_Tracks_Navigation teste la navigation entre les pages func TestPagination_Tracks_Navigation(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() // Get first page req1 := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil) w1 := httptest.NewRecorder() router.ServeHTTP(w1, req1) assert.Equal(t, http.StatusOK, w1.Code) var resp1 map[string]interface{} err := json.Unmarshal(w1.Body.Bytes(), &resp1) require.NoError(t, err) data1, _ := resp1["data"].(map[string]interface{}) tracks1, _ := data1["tracks"].([]interface{}) pagination1, _ := data1["pagination"].(map[string]interface{}) // Get second page req2 := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=2&limit=10", nil) w2 := httptest.NewRecorder() router.ServeHTTP(w2, req2) assert.Equal(t, http.StatusOK, w2.Code) var resp2 map[string]interface{} err = json.Unmarshal(w2.Body.Bytes(), &resp2) require.NoError(t, err) data2, _ := resp2["data"].(map[string]interface{}) tracks2, _ := data2["tracks"].([]interface{}) pagination2, _ := data2["pagination"].(map[string]interface{}) // Verify pages are different assert.Equal(t, float64(1), pagination1["page"]) assert.Equal(t, float64(2), pagination2["page"]) // Verify tracks are different (if we have enough tracks) if len(tracks1) > 0 && len(tracks2) > 0 { track1ID := tracks1[0].(map[string]interface{})["id"].(string) track2ID := tracks2[0].(map[string]interface{})["id"].(string) assert.NotEqual(t, track1ID, track2ID, "Tracks should be different between pages") } } // TestPagination_Users_Default teste la pagination par défaut pour les users func TestPagination_Users_Default(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/users", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["success"].(bool)) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) users, ok := data["users"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(users), 20, "Default limit should be 20") pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, float64(1), pagination["page"], "Default page should be 1") assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20") } // TestPagination_Users_CustomParams teste la pagination avec des paramètres personnalisés pour users func TestPagination_Users_CustomParams(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/users?page=2&limit=5", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) users, ok := data["users"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(users), 5, "Should return at most 5 users") pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) assert.Equal(t, float64(2), pagination["page"], "Page should be 2") assert.Equal(t, float64(5), pagination["limit"], "Limit should be 5") } // TestPagination_Users_Metadata teste les métadonnées de pagination pour users func TestPagination_Users_Metadata(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/users?page=1&limit=5", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) // Verify pagination metadata assert.Contains(t, pagination, "page") assert.Contains(t, pagination, "limit") assert.Contains(t, pagination, "total") assert.Contains(t, pagination, "total_pages") assert.Contains(t, pagination, "has_next") assert.Contains(t, pagination, "has_prev") total := pagination["total"].(float64) assert.Greater(t, total, float64(0), "Total should be greater than 0") hasNext := pagination["has_next"].(bool) hasPrev := pagination["has_prev"].(bool) // On first page, should not have previous assert.False(t, hasPrev, "First page should not have previous") // If total > limit, should have next if total > pagination["limit"].(float64) { assert.True(t, hasNext, "Should have next page if total > limit") } } // TestPagination_Playlists_Default teste la pagination par défaut pour les playlists func TestPagination_Playlists_Default(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/playlists", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["success"].(bool)) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) playlists, ok := data["playlists"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(playlists), 20, "Default limit should be 20") // Playlists endpoint might return different structure // Check if pagination is present if pagination, ok := data["pagination"].(map[string]interface{}); ok { assert.Equal(t, float64(1), pagination["page"], "Default page should be 1") assert.Equal(t, float64(20), pagination["limit"], "Default limit should be 20") } else { // Some endpoints might return page/limit directly assert.Contains(t, data, "page") assert.Contains(t, data, "limit") } } // TestPagination_Playlists_CustomParams teste la pagination avec des paramètres personnalisés pour playlists func TestPagination_Playlists_CustomParams(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() req := httptest.NewRequest(http.MethodGet, "/api/v1/playlists?page=2&limit=15", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) playlists, ok := data["playlists"].([]interface{}) require.True(t, ok) assert.LessOrEqual(t, len(playlists), 15, "Should return at most 15 playlists") } // TestPagination_Consistency teste que tous les endpoints utilisent le même format de pagination func TestPagination_Consistency(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() endpoints := []string{ "/api/v1/tracks?page=1&limit=10", "/api/v1/users?page=1&limit=10", "/api/v1/playlists?page=1&limit=10", } for _, endpoint := range endpoints { t.Run(fmt.Sprintf("Consistency: %s", endpoint), func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, endpoint, nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) // All endpoints should return success: true assert.True(t, resp["success"].(bool)) // All endpoints should have data data, ok := resp["data"].(map[string]interface{}) require.True(t, ok, "Response should have data field") // All endpoints should have pagination info (either in pagination object or directly) hasPagination := false if _, ok := data["pagination"]; ok { hasPagination = true } else if _, ok := data["page"]; ok { hasPagination = true } assert.True(t, hasPagination, "Response should have pagination information") }) } } // TestPagination_EdgeCases teste les cas limites de pagination func TestPagination_EdgeCases(t *testing.T) { router, _, _, cleanup := setupPaginationTestRouter(t) defer cleanup() tests := []struct { name string query string expectedStatus int }{ { name: "Empty query", query: "", expectedStatus: http.StatusOK, }, { name: "Very large page number", query: "page=999999&limit=10", expectedStatus: http.StatusOK, }, { name: "Page 1 with limit 1", query: "page=1&limit=1", expectedStatus: http.StatusOK, }, { name: "Maximum limit", query: "page=1&limit=100", expectedStatus: http.StatusOK, }, { name: "Limit exceeds maximum", query: "page=1&limit=1000", expectedStatus: http.StatusOK, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?%s", tt.query), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, tt.expectedStatus, w.Code, "Should handle edge case gracefully") if w.Code == http.StatusOK { var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["success"].(bool), "Response should be successful") } }) } } // TestPagination_TotalCount teste que le total count est correct func TestPagination_TotalCount(t *testing.T) { router, db, _, cleanup := setupPaginationTestRouter(t) defer cleanup() // Count total tracks in database var totalTracks int64 db.Model(&models.Track{}).Count(&totalTracks) // Request first page req := httptest.NewRequest(http.MethodGet, "/api/v1/tracks?page=1&limit=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) // Verify total matches database count responseTotal := pagination["total"].(float64) assert.Equal(t, float64(totalTracks), responseTotal, "Total should match database count") } // TestPagination_TotalPages teste le calcul de total_pages func TestPagination_TotalPages(t *testing.T) { router, db, _, cleanup := setupPaginationTestRouter(t) defer cleanup() // Count total tracks var totalTracks int64 db.Model(&models.Track{}).Count(&totalTracks) limit := 10 expectedTotalPages := int((totalTracks + int64(limit) - 1) / int64(limit)) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/tracks?page=1&limit=%d", limit), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) data, ok := resp["data"].(map[string]interface{}) require.True(t, ok) pagination, ok := data["pagination"].(map[string]interface{}) require.True(t, ok) totalPages := pagination["total_pages"].(float64) assert.Equal(t, float64(expectedTotalPages), totalPages, "Total pages should be calculated correctly") }