veza/veza-backend-api/internal/services/track_search_service_test.go

793 lines
19 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package services
import (
"context"
"fmt"
"github.com/google/uuid"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"veza-backend-api/internal/models"
)
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
userID := uuid.New()
2025-12-03 19:29:37 +00:00
user := &models.User{
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
}
return service, db, userID, cleanup
2025-12-03 19:29:37 +00:00
}
func TestTrackSearchService_SearchTracks_FullTextSearch(t *testing.T) {
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{
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{
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) {
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{
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{
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) {
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{
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{
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) {
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{
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{
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) {
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{
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{
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) {
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{
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) {
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{
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{
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) {
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{
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{
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)
// 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) {
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{
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{
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{
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) {
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{
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{
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) {
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{
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{
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) {
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{
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{
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) {
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{
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{
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,
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,
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)
}