package services import ( "testing" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestNewFullTextSearchService(t *testing.T) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("Failed to open database: %v", err) } logger := zap.NewNop() service := NewFullTextSearchService(db, logger) if service == nil { t.Error("NewFullTextSearchService() returned nil") } if service.db == nil { t.Error("NewFullTextSearchService() returned service with nil db") } if service.logger == nil { t.Error("NewFullTextSearchService() returned service with nil logger") } } func TestFullTextSearchService_prepareSearchQuery(t *testing.T) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("Failed to open database: %v", err) } service := NewFullTextSearchService(db, zap.NewNop()) tests := []struct { name string query string expected string // Expected to contain certain patterns }{ { name: "simple query", query: "test", expected: "test", }, { name: "multiple words", query: "test query", expected: "test & query", }, { name: "empty query", query: "", expected: "", }, { name: "query with special characters", query: "test-query!", expected: "test-query!", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := service.prepareSearchQuery(tt.query) if tt.query == "" && result != "" { t.Errorf("prepareSearchQuery(%q) = %q, expected empty string", tt.query, result) } if tt.query != "" && result == "" { t.Errorf("prepareSearchQuery(%q) = %q, expected non-empty", tt.query, result) } }) } } func TestFullTextSearchService_SearchParams_Defaults(t *testing.T) { params := SearchParams{} // Test that defaults are applied in Search method // This is more of an integration test, but we can test the logic if params.Page < 1 { params.Page = 1 } if params.Limit < 1 { params.Limit = 20 } if params.Limit > 100 { params.Limit = 100 } if params.Page != 1 { t.Errorf("Expected default page 1, got %d", params.Page) } if params.Limit != 20 { t.Errorf("Expected default limit 20, got %d", params.Limit) } } func TestContainsString(t *testing.T) { tests := []struct { name string slice []string item string want bool }{ { name: "contains item", slice: []string{"track", "user", "playlist"}, item: "user", want: true, }, { name: "does not contain item", slice: []string{"track", "user", "playlist"}, item: "album", want: false, }, { name: "empty slice", slice: []string{}, item: "track", want: false, }, { name: "case sensitive", slice: []string{"track", "user"}, item: "Track", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := containsString(tt.slice, tt.item) if got != tt.want { t.Errorf("containsString(%v, %q) = %v, want %v", tt.slice, tt.item, got, tt.want) } }) } } // Note: Full integration tests would require: // 1. A real PostgreSQL database with the search indexes // 2. Test data (tracks, users, playlists) // 3. Verification of tsvector/tsquery functionality // // Example integration test structure: // func TestFullTextSearchService_Search_Integration(t *testing.T) { // // Setup test database with PostgreSQL // db := setupTestDB(t) // defer cleanupTestDB(t, db) // // // Create test data // createTestTracks(t, db) // createTestUsers(t, db) // createTestPlaylists(t, db) // // service := NewFullTextSearchService(db, zap.NewNop()) // // ctx := context.Background() // params := SearchParams{ // Query: "test", // Page: 1, // Limit: 10, // } // // result, err := service.Search(ctx, params) // if err != nil { // t.Fatalf("Search() error = %v", err) // } // // if len(result.Tracks) == 0 && len(result.Users) == 0 && len(result.Playlists) == 0 { // t.Error("Search() returned no results") // } // }