2025-12-03 19:29:37 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-12-16 16:23:49 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
|
|
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
|
|
2025-12-03 19:29:37 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"gorm.io/driver/sqlite"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
func setupTestTrackSearchService(t *testing.T) (*TrackSearchService, *gorm.DB, uuid.UUID, func()) {
|
2025-12-03 19:29:37 +00:00
|
|
|
// Setup in-memory SQLite database
|
|
|
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Auto-migrate
|
|
|
|
|
err = db.AutoMigrate(&models.Track{}, &models.User{})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Create test user
|
2025-12-06 16:21:59 +00:00
|
|
|
userID := uuid.New()
|
2025-12-03 19:29:37 +00:00
|
|
|
user := &models.User{
|
2025-12-06 16:21:59 +00:00
|
|
|
ID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Username: "testuser",
|
|
|
|
|
Email: "test@example.com",
|
|
|
|
|
IsActive: true,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(user).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Setup service
|
|
|
|
|
service := NewTrackSearchService(db)
|
|
|
|
|
|
|
|
|
|
// Cleanup function
|
|
|
|
|
cleanup := func() {
|
|
|
|
|
// Database will be closed automatically
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 16:21:59 +00:00
|
|
|
return service, db, userID, cleanup
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_FullTextSearch(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Test Track 1",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Another Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test full-text search
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Query: "Test",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Test Track 1", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_GenreFilter(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Rock Track",
|
|
|
|
|
Artist: "Rock Artist",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Pop Track",
|
|
|
|
|
Artist: "Pop Artist",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test genre filter
|
|
|
|
|
genre := "Rock"
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Genre: &genre,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Rock Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_DurationFilter(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Short Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 120, // 2 minutes
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Long Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 300, // 5 minutes
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test min duration filter
|
|
|
|
|
minDuration := 200
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
MinDuration: &minDuration,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Long Track", results[0].Title)
|
|
|
|
|
|
|
|
|
|
// Test max duration filter
|
|
|
|
|
maxDuration := 150
|
|
|
|
|
results, total, err = service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
MaxDuration: &maxDuration,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Short Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_FormatFilter(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "MP3 Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "FLAC Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.flac",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test format filter
|
|
|
|
|
format := "MP3"
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Format: &format,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "MP3 Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_DateRangeFilter(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks with different dates
|
|
|
|
|
now := time.Now()
|
|
|
|
|
oldDate := now.AddDate(0, -2, 0) // 2 months ago
|
|
|
|
|
recentDate := now.AddDate(0, 0, -5) // 5 days ago
|
|
|
|
|
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Old Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
CreatedAt: oldDate,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Recent Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
CreatedAt: recentDate,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test min date filter
|
|
|
|
|
minDate := now.AddDate(0, -1, 0).Format(time.RFC3339) // 1 month ago
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
MinDate: &minDate,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Recent Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_Pagination(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create multiple test tracks
|
|
|
|
|
for i := 0; i < 25; i++ {
|
|
|
|
|
track := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Track " + fmt.Sprintf("%d", i+1),
|
|
|
|
|
Artist: "Artist",
|
|
|
|
|
FilePath: fmt.Sprintf("/test/track%d.mp3", i+1),
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test pagination - first page
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(25), total)
|
|
|
|
|
assert.Len(t, results, 10)
|
|
|
|
|
|
|
|
|
|
// Test pagination - second page
|
|
|
|
|
results, total, err = service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Page: 2,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(25), total)
|
|
|
|
|
assert.Len(t, results, 10)
|
|
|
|
|
|
|
|
|
|
// Test pagination - third page
|
|
|
|
|
results, total, err = service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Page: 3,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(25), total)
|
|
|
|
|
assert.Len(t, results, 5) // Only 5 remaining
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_Sorting(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "A Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Z Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test sorting by title ascending
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: "title",
|
|
|
|
|
SortOrder: "asc",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
assert.Equal(t, "A Track", results[0].Title)
|
|
|
|
|
assert.Equal(t, "Z Track", results[1].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_OnlyPublic(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create public track
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Public Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Create private track
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Private Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: false,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
2025-12-16 16:23:49 +00:00
|
|
|
// Force IsPublic to false (GORM might use default value true)
|
|
|
|
|
err = db.Model(track2).Update("is_public", false).Error
|
|
|
|
|
require.NoError(t, err)
|
2025-12-03 19:29:37 +00:00
|
|
|
|
|
|
|
|
// Test that only public tracks are returned
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Public Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_CombinedFilters(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks with different attributes
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Rock MP3 Track",
|
|
|
|
|
Artist: "Rock Artist",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Pop FLAC Track",
|
|
|
|
|
Artist: "Pop Artist",
|
|
|
|
|
FilePath: "/test/track2.flac",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track3 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Rock FLAC Track",
|
|
|
|
|
Artist: "Rock Artist 2",
|
|
|
|
|
FilePath: "/test/track3.flac",
|
|
|
|
|
FileSize: 7 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 250,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track3).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test combined filters: genre + format
|
|
|
|
|
genre := "Rock"
|
|
|
|
|
format := "MP3"
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Genre: &genre,
|
|
|
|
|
Format: &format,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Rock MP3 Track", results[0].Title)
|
|
|
|
|
|
|
|
|
|
// Test combined filters: genre + duration range
|
|
|
|
|
minDuration := 200
|
|
|
|
|
maxDuration := 300
|
|
|
|
|
results, total, err = service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
Genre: &genre,
|
|
|
|
|
MinDuration: &minDuration,
|
|
|
|
|
MaxDuration: &maxDuration,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(1), total)
|
|
|
|
|
assert.Len(t, results, 1)
|
|
|
|
|
assert.Equal(t, "Rock FLAC Track", results[0].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_SortByPopularity(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks with different like counts
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Low Likes Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
LikeCount: 5,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "High Likes Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
LikeCount: 50,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test sorting by popularity (descending)
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: "popularity",
|
|
|
|
|
SortOrder: "desc",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
assert.Equal(t, "High Likes Track", results[0].Title) // Highest likes first
|
|
|
|
|
assert.Equal(t, "Low Likes Track", results[1].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_SortByPlayCount(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks with different play counts
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Low Plays Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
PlayCount: 10,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "High Plays Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
PlayCount: 100,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test sorting by play_count (descending)
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: "play_count",
|
|
|
|
|
SortOrder: "desc",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
assert.Equal(t, "High Plays Track", results[0].Title) // Highest plays first
|
|
|
|
|
assert.Equal(t, "Low Plays Track", results[1].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_SortByTitle(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks with different titles
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Zebra Track",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Alpha Track",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test sorting by title (ascending)
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: "title",
|
|
|
|
|
SortOrder: "asc",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
assert.Equal(t, "Alpha Track", results[0].Title) // Alphabetically first
|
|
|
|
|
assert.Equal(t, "Zebra Track", results[1].Title)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_SortByCommentCount(t *testing.T) {
|
2025-12-06 16:21:59 +00:00
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
2025-12-03 19:29:37 +00:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Track With Comments",
|
|
|
|
|
Artist: "Artist One",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Title: "Track Without Comments",
|
|
|
|
|
Artist: "Artist Two",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Create comments for track1
|
|
|
|
|
err = db.AutoMigrate(&models.TrackComment{})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
comment1 := &models.TrackComment{
|
|
|
|
|
TrackID: track1.ID,
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Content: "Great track!",
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(comment1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
comment2 := &models.TrackComment{
|
|
|
|
|
TrackID: track1.ID,
|
2025-12-06 16:21:59 +00:00
|
|
|
UserID: userID,
|
2025-12-03 19:29:37 +00:00
|
|
|
Content: "Love it!",
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(comment2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Test sorting by comment_count (descending)
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: "comment_count",
|
|
|
|
|
SortOrder: "desc",
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
assert.Equal(t, "Track With Comments", results[0].Title) // Most comments first
|
|
|
|
|
assert.Equal(t, "Track Without Comments", results[1].Title)
|
|
|
|
|
}
|
2026-02-27 08:43:25 +00:00
|
|
|
|
|
|
|
|
func TestTrackSearchService_SearchTracks_InvalidSortBy_FallbackToCreatedAt(t *testing.T) {
|
|
|
|
|
// v0.903: SQL injection prevention - invalid sortBy falls back to created_at DESC
|
|
|
|
|
service, db, userID, cleanup := setupTestTrackSearchService(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// Create test tracks
|
|
|
|
|
track1 := &models.Track{
|
|
|
|
|
UserID: userID,
|
|
|
|
|
Title: "First Track",
|
|
|
|
|
Artist: "Artist",
|
|
|
|
|
FilePath: "/test/track1.mp3",
|
|
|
|
|
FileSize: 5 * 1024 * 1024,
|
|
|
|
|
Format: "MP3",
|
|
|
|
|
Duration: 180,
|
|
|
|
|
Genre: "Rock",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err := db.Create(track1).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
track2 := &models.Track{
|
|
|
|
|
UserID: userID,
|
|
|
|
|
Title: "Second Track",
|
|
|
|
|
Artist: "Artist",
|
|
|
|
|
FilePath: "/test/track2.mp3",
|
|
|
|
|
FileSize: 6 * 1024 * 1024,
|
|
|
|
|
Format: "FLAC",
|
|
|
|
|
Duration: 200,
|
|
|
|
|
Genre: "Pop",
|
|
|
|
|
IsPublic: true,
|
|
|
|
|
Status: models.TrackStatusCompleted,
|
|
|
|
|
}
|
|
|
|
|
err = db.Create(track2).Error
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Malicious sortBy - should fallback to created_at DESC (no error, no SQL injection)
|
|
|
|
|
maliciousSortBy := "invalid'; DROP TABLE tracks;--"
|
|
|
|
|
results, total, err := service.SearchTracks(ctx, TrackSearchParams{
|
|
|
|
|
SortBy: maliciousSortBy,
|
|
|
|
|
Page: 1,
|
|
|
|
|
Limit: 10,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(2), total)
|
|
|
|
|
assert.Len(t, results, 2)
|
|
|
|
|
// Should return results (fallback to created_at DESC applied, no injection)
|
|
|
|
|
assert.Contains(t, []string{results[0].Title, results[1].Title}, "First Track")
|
|
|
|
|
assert.Contains(t, []string{results[0].Title, results[1].Title}, "Second Track")
|
|
|
|
|
}
|