package services import ( "context" "github.com/google/uuid" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func setupTestBatchDeleteDB(t *testing.T) (*TrackService, *gorm.DB, string, func()) { // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Auto-migrate err = db.AutoMigrate(&models.User{}, &models.Track{}) require.NoError(t, err) // Create test user user := &models.User{ ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Create another user user2 := &models.User{ ID: 2, Username: "otheruser", Email: "other@example.com", IsActive: true, } err = db.Create(user2).Error require.NoError(t, err) // Create temporary directory for test files testDir := filepath.Join(os.TempDir(), "test_batch_delete") err = os.MkdirAll(testDir, 0755) require.NoError(t, err) // Create logger logger := zap.NewNop() // Create service service := NewTrackService(db, logger, testDir) // Cleanup function cleanup := func() { os.RemoveAll(testDir) } return service, db, testDir, cleanup } func TestTrackService_BatchDeleteTracks_Success(t *testing.T) { service, db, testDir, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() // Create test files file1 := filepath.Join(testDir, "track1.mp3") file2 := filepath.Join(testDir, "track2.mp3") os.WriteFile(file1, []byte("test content 1"), 0644) os.WriteFile(file2, []byte("test content 2"), 0644) // Create tracks track1 := &models.Track{ UserID: 1, Title: "Track 1", FilePath: file1, FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } track2 := &models.Track{ UserID: 1, Title: "Track 2", FilePath: file2, FileSize: 2048, Format: "MP3", Duration: 240, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error require.NoError(t, err) err = db.Create(track2).Error require.NoError(t, err) // Batch delete result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID, track2.ID}, 1) require.NoError(t, err) require.NotNil(t, result) // Verify results assert.Equal(t, 2, len(result.Deleted)) assert.Contains(t, result.Deleted, track1.ID) assert.Contains(t, result.Deleted, track2.ID) assert.Equal(t, 0, len(result.Failed)) // Verify tracks are deleted from database var count int64 db.Model(&models.Track{}).Where("id IN ?", []int64{track1.ID, track2.ID}).Count(&count) assert.Equal(t, int64(0), count) // Verify files are deleted assert.NoFileExists(t, file1) assert.NoFileExists(t, file2) } func TestTrackService_BatchDeleteTracks_EmptyList(t *testing.T) { service, _, _, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() result, err := service.BatchDeleteTracks(ctx, []int64{}, 1) require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, 0, len(result.Deleted)) assert.Equal(t, 0, len(result.Failed)) } func TestTrackService_BatchDeleteTracks_ExceedsMaxBatchSize(t *testing.T) { service, _, _, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() // Create a list with more than 100 tracks trackIDs := make([]int64, 101) for i := range trackIDs { trackIDs[i] = int64(i + 1) } result, err := service.BatchDeleteTracks(ctx, trackIDs, 1) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "batch size exceeds maximum") } func TestTrackService_BatchDeleteTracks_NotFound(t *testing.T) { service, _, _, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() result, err := service.BatchDeleteTracks(ctx, []int64{999, 1000}, 1) require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, 0, len(result.Deleted)) assert.Equal(t, 2, len(result.Failed)) assert.Equal(t, int64(999), result.Failed[0].TrackID) assert.Equal(t, "track not found", result.Failed[0].Error) } func TestTrackService_BatchDeleteTracks_Forbidden(t *testing.T) { service, db, testDir, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() // Create file file1 := filepath.Join(testDir, "track1.mp3") os.WriteFile(file1, []byte("test content"), 0644) // Create track owned by user 1 track1 := &models.Track{ UserID: 1, Title: "Track 1", FilePath: file1, FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error require.NoError(t, err) // Try to delete as user 2 result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID}, 2) require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, 0, len(result.Deleted)) assert.Equal(t, 1, len(result.Failed)) assert.Equal(t, track1.ID, result.Failed[0].TrackID) assert.Contains(t, result.Failed[0].Error, "forbidden") // Verify track still exists var count int64 db.Model(&models.Track{}).Where("id = ?", track1.ID).Count(&count) assert.Equal(t, int64(1), count) } func TestTrackService_BatchDeleteTracks_PartialSuccess(t *testing.T) { service, db, testDir, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() // Create files file1 := filepath.Join(testDir, "track1.mp3") file2 := filepath.Join(testDir, "track2.mp3") os.WriteFile(file1, []byte("test content 1"), 0644) os.WriteFile(file2, []byte("test content 2"), 0644) // Create tracks track1 := &models.Track{ UserID: 1, Title: "Track 1", FilePath: file1, FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } track2 := &models.Track{ UserID: 2, // Owned by different user Title: "Track 2", FilePath: file2, FileSize: 2048, Format: "MP3", Duration: 240, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error require.NoError(t, err) err = db.Create(track2).Error require.NoError(t, err) // Try to delete both as user 1 result, err := service.BatchDeleteTracks(ctx, []int64{track1.ID, track2.ID}, 1) require.NoError(t, err) require.NotNil(t, result) // Track1 should be deleted, track2 should fail assert.Equal(t, 1, len(result.Deleted)) assert.Contains(t, result.Deleted, track1.ID) assert.Equal(t, 1, len(result.Failed)) assert.Equal(t, track2.ID, result.Failed[0].TrackID) assert.Contains(t, result.Failed[0].Error, "forbidden") // Verify track1 is deleted, track2 still exists var count1, count2 int64 db.Model(&models.Track{}).Where("id = ?", track1.ID).Count(&count1) db.Model(&models.Track{}).Where("id = ?", track2.ID).Count(&count2) assert.Equal(t, int64(0), count1) assert.Equal(t, int64(1), count2) } func TestTrackService_deleteTrackFiles(t *testing.T) { service, db, testDir, cleanup := setupTestBatchDeleteDB(t) defer cleanup() ctx := context.Background() // Create test files file1 := filepath.Join(testDir, "track1.mp3") waveform1 := filepath.Join(testDir, "waveform1.png") cover1 := filepath.Join(testDir, "cover1.jpg") os.WriteFile(file1, []byte("test content"), 0644) os.WriteFile(waveform1, []byte("waveform"), 0644) os.WriteFile(cover1, []byte("cover"), 0644) // Create track with all file paths track := &models.Track{ UserID: 1, Title: "Track with all files", FilePath: file1, WaveformPath: waveform1, CoverArtPath: cover1, FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error require.NoError(t, err) // Delete files err = service.deleteTrackFiles(ctx, track) require.NoError(t, err) // Verify all files are deleted assert.NoFileExists(t, file1) assert.NoFileExists(t, waveform1) assert.NoFileExists(t, cover1) }