- docs: SCOPE_CONTROL, CONTRIBUTING, README, .github templates - frontend: DeveloperDashboardView, Player components, MSW handlers, auth, reactQuerySync - backend: playback_analytics, playlist_service, testutils, integration README Excluded (artifacts): .auth, playwright-report, test-results, storybook_audit_detailed.json
539 lines
13 KiB
Go
539 lines
13 KiB
Go
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 == "" {
|
|
if testing.Short() {
|
|
t.Skip("Skipping CacheService test in short mode (requires Redis, set REDIS_TEST_URL)")
|
|
return nil, nil
|
|
}
|
|
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)
|
|
}
|