package services import ( "context" "os" "testing" "time" "github.com/google/uuid" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) // setupTestCacheService crée un CacheService de test avec Redis func setupTestCacheService(t *testing.T) (*CacheService, *redis.Client) { redisURL := os.Getenv("REDIS_TEST_URL") if redisURL == "" { redisURL = "redis://localhost:6379/15" // Utilise DB 15 pour les tests } opts, err := redis.ParseURL(redisURL) if err != nil { t.Skipf("Skipping test: failed to parse Redis URL: %v", err) return nil, nil } client := redis.NewClient(opts) // Test de connexion ctx := context.Background() _, err = client.Ping(ctx).Result() if err != nil { t.Skipf("Skipping test: Redis not available: %v", err) return nil, nil } // Nettoyer la base de données de test client.FlushDB(ctx) // Cleanup: Flush DB après les tests t.Cleanup(func() { client.FlushDB(ctx) client.Close() }) logger := zap.NewNop() service := NewCacheService(client, logger) return service, client } func TestCacheService_SetAndGet_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() key := "test:key" value := map[string]interface{}{ "name": "test", "value": 123, } ttl := 5 * time.Minute // Set err := service.Set(ctx, key, value, ttl) assert.NoError(t, err) // Get var retrieved map[string]interface{} err = service.Get(ctx, key, &retrieved) assert.NoError(t, err) assert.Equal(t, value["name"], retrieved["name"]) assert.Equal(t, float64(123), retrieved["value"]) // JSON unmarshals numbers as float64 } func TestCacheService_Get_CacheMiss(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() key := "test:nonexistent" var value map[string]interface{} err := service.Get(ctx, key, &value) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_Delete_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() key := "test:delete" value := "test value" ttl := 5 * time.Minute // Set err := service.Set(ctx, key, value, ttl) assert.NoError(t, err) // Verify exists var retrieved string err = service.Get(ctx, key, &retrieved) assert.NoError(t, err) // Delete err = service.Delete(ctx, key) assert.NoError(t, err) // Verify deleted err = service.Get(ctx, key, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_Exists_True(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() key := "test:exists" value := "test value" ttl := 5 * time.Minute // Set err := service.Set(ctx, key, value, ttl) assert.NoError(t, err) // Check exists exists, err := service.Exists(ctx, key) assert.NoError(t, err) assert.True(t, exists) } func TestCacheService_Exists_False(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() key := "test:nonexistent" exists, err := service.Exists(ctx, key) assert.NoError(t, err) assert.False(t, exists) } func TestCacheService_SetUser_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() userID := uuid.New() user := map[string]interface{}{ "id": userID.String(), "username": "testuser", "email": "test@example.com", } config := DefaultCacheConfig() err := service.SetUser(ctx, userID, user, config) assert.NoError(t, err) // Verify var retrieved map[string]interface{} err = service.GetUser(ctx, userID, &retrieved) assert.NoError(t, err) assert.Equal(t, user["username"], retrieved["username"]) } func TestCacheService_DeleteUser_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() userID := uuid.New() user := map[string]interface{}{ "id": userID.String(), "username": "testuser", } config := DefaultCacheConfig() // Set err := service.SetUser(ctx, userID, user, config) assert.NoError(t, err) // Delete err = service.DeleteUser(ctx, userID) assert.NoError(t, err) // Verify deleted var retrieved map[string]interface{} err = service.GetUser(ctx, userID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_SetTrack_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() trackID := uuid.New() track := map[string]interface{}{ "id": trackID.String(), "title": "Test Track", "duration": 180, } config := DefaultCacheConfig() err := service.SetTrack(ctx, trackID, track, config) assert.NoError(t, err) // Verify var retrieved map[string]interface{} err = service.GetTrack(ctx, trackID, &retrieved) assert.NoError(t, err) assert.Equal(t, track["title"], retrieved["title"]) } func TestCacheService_DeleteTrack_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() trackID := uuid.New() track := map[string]interface{}{ "id": trackID.String(), "title": "Test Track", } config := DefaultCacheConfig() // Set err := service.SetTrack(ctx, trackID, track, config) assert.NoError(t, err) // Delete err = service.DeleteTrack(ctx, trackID) assert.NoError(t, err) // Verify deleted var retrieved map[string]interface{} err = service.GetTrack(ctx, trackID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_SetPlaylist_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() playlistID := uuid.New() playlist := map[string]interface{}{ "id": playlistID.String(), "name": "Test Playlist", "tracks": []string{}, } config := DefaultCacheConfig() err := service.SetPlaylist(ctx, playlistID, playlist, config) assert.NoError(t, err) // Verify var retrieved map[string]interface{} err = service.GetPlaylist(ctx, playlistID, &retrieved) assert.NoError(t, err) assert.Equal(t, playlist["name"], retrieved["name"]) } func TestCacheService_DeletePlaylist_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() playlistID := uuid.New() playlist := map[string]interface{}{ "id": playlistID.String(), "name": "Test Playlist", } config := DefaultCacheConfig() // Set err := service.SetPlaylist(ctx, playlistID, playlist, config) assert.NoError(t, err) // Delete err = service.DeletePlaylist(ctx, playlistID) assert.NoError(t, err) // Verify deleted var retrieved map[string]interface{} err = service.GetPlaylist(ctx, playlistID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_DeletePattern_Success(t *testing.T) { service, client := setupTestCacheService(t) if service == nil { return } ctx := context.Background() pattern := "test:pattern:*" // Set multiple keys matching pattern keys := []string{"test:pattern:1", "test:pattern:2", "test:pattern:3"} for _, key := range keys { err := client.Set(ctx, key, "value", 5*time.Minute).Err() require.NoError(t, err) } // Set a key that doesn't match err := client.Set(ctx, "test:other:1", "value", 5*time.Minute).Err() require.NoError(t, err) // Delete pattern err = service.DeletePattern(ctx, pattern) assert.NoError(t, err) // Verify pattern keys deleted for _, key := range keys { exists, err := client.Exists(ctx, key).Result() assert.NoError(t, err) assert.Equal(t, int64(0), exists) } // Verify non-matching key still exists exists, err := client.Exists(ctx, "test:other:1").Result() assert.NoError(t, err) assert.Equal(t, int64(1), exists) } func TestCacheService_SetUserTracks_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() userID := uuid.New() page := 1 tracks := []map[string]interface{}{ {"id": uuid.New().String(), "title": "Track 1"}, {"id": uuid.New().String(), "title": "Track 2"}, } config := DefaultCacheConfig() err := service.SetUserTracks(ctx, userID, page, tracks, config) assert.NoError(t, err) // Verify var retrieved []map[string]interface{} err = service.GetUserTracks(ctx, userID, page, &retrieved) assert.NoError(t, err) assert.Len(t, retrieved, 2) } func TestCacheService_DeleteUserTracks_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() userID := uuid.New() page := 1 tracks := []map[string]interface{}{ {"id": uuid.New().String(), "title": "Track 1"}, } config := DefaultCacheConfig() // Set err := service.SetUserTracks(ctx, userID, page, tracks, config) assert.NoError(t, err) // Delete err = service.DeleteUserTracks(ctx, userID) assert.NoError(t, err) // Verify deleted var retrieved []map[string]interface{} err = service.GetUserTracks(ctx, userID, page, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_SetSearchResults_Success(t *testing.T) { service, _ := setupTestCacheService(t) if service == nil { return } ctx := context.Background() query := "test query" results := map[string]interface{}{ "tracks": []string{"track1", "track2"}, "users": []string{"user1"}, } config := DefaultCacheConfig() err := service.SetSearchResults(ctx, query, results, config) assert.NoError(t, err) // Verify var retrieved map[string]interface{} err = service.GetSearchResults(ctx, query, &retrieved) assert.NoError(t, err) assert.NotNil(t, retrieved["tracks"]) } func TestCacheService_InvalidateUserCache_Success(t *testing.T) { service, client := setupTestCacheService(t) if service == nil { return } ctx := context.Background() userID := uuid.New() config := DefaultCacheConfig() // Set various user-related cache entries err := service.SetUser(ctx, userID, map[string]interface{}{"id": userID.String()}, config) assert.NoError(t, err) err = service.SetUserTracks(ctx, userID, 1, []map[string]interface{}{}, config) assert.NoError(t, err) // Set a user session key manually err = client.Set(ctx, "user_sessions:"+userID.String()+":session1", "value", 5*time.Minute).Err() require.NoError(t, err) // Invalidate err = service.InvalidateUserCache(ctx, userID) assert.NoError(t, err) // Verify user cache deleted var retrieved map[string]interface{} err = service.GetUser(ctx, userID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_InvalidateTrackCache_Success(t *testing.T) { service, client := setupTestCacheService(t) if service == nil { return } ctx := context.Background() trackID := uuid.New() config := DefaultCacheConfig() // Set track cache err := service.SetTrack(ctx, trackID, map[string]interface{}{"id": trackID.String()}, config) assert.NoError(t, err) // Set search results (should be invalidated) err = client.Set(ctx, "search:test", "results", 5*time.Minute).Err() require.NoError(t, err) // Invalidate err = service.InvalidateTrackCache(ctx, trackID) assert.NoError(t, err) // Verify track cache deleted var retrieved map[string]interface{} err = service.GetTrack(ctx, trackID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_InvalidatePlaylistCache_Success(t *testing.T) { service, client := setupTestCacheService(t) if service == nil { return } ctx := context.Background() playlistID := uuid.New() config := DefaultCacheConfig() // Set playlist cache err := service.SetPlaylist(ctx, playlistID, map[string]interface{}{"id": playlistID.String()}, config) assert.NoError(t, err) // Set search results (should be invalidated) err = client.Set(ctx, "search:test", "results", 5*time.Minute).Err() require.NoError(t, err) // Invalidate err = service.InvalidatePlaylistCache(ctx, playlistID) assert.NoError(t, err) // Verify playlist cache deleted var retrieved map[string]interface{} err = service.GetPlaylist(ctx, playlistID, &retrieved) assert.Error(t, err) assert.Equal(t, ErrCacheMiss, err) } func TestCacheService_DefaultCacheConfig(t *testing.T) { config := DefaultCacheConfig() assert.NotNil(t, config) assert.Equal(t, 5*time.Minute, config.DefaultTTL) assert.Equal(t, 5*time.Minute, config.UserTTL) assert.Equal(t, 30*time.Minute, config.TrackTTL) assert.Equal(t, 15*time.Minute, config.PlaylistTTL) assert.Equal(t, 1*time.Minute, config.RoomTTL) }