veza/veza-backend-api/tests/pagination/pagination_test.go

685 lines
20 KiB
Go

//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")
}