2021-10-08 18:23:43 -08:00
package persistence
import (
"time"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/model"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe ( "SmartPlaylist" , func ( ) {
var pls SmartPlaylist
Describe ( "AddFilters" , func ( ) {
BeforeEach ( func ( ) {
sp := model . SmartPlaylist {
RuleGroup : model . RuleGroup {
Combinator : "and" , Rules : model . Rules {
model . Rule { Field : "title" , Operator : "contains" , Value : "love" } ,
model . Rule { Field : "year" , Operator : "is in the range" , Value : [ ] int { 1980 , 1989 } } ,
model . Rule { Field : "loved" , Operator : "is true" } ,
model . Rule { Field : "lastPlayed" , Operator : "in the last" , Value : "30" } ,
model . RuleGroup {
Combinator : "or" ,
Rules : model . Rules {
model . Rule { Field : "artist" , Operator : "is not" , Value : "zé" } ,
model . Rule { Field : "album" , Operator : "is" , Value : "4" } ,
} ,
} ,
} } ,
Order : "artist asc" ,
Limit : 100 ,
}
pls = SmartPlaylist ( sp )
} )
It ( "returns a proper SQL query" , func ( ) {
sel := pls . AddFilters ( squirrel . Select ( "media_file" ) . Columns ( "*" ) )
sql , args , err := sel . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( "SELECT media_file, * WHERE (media_file.title ILIKE ? AND (media_file.year >= ? AND media_file.year <= ?) AND annotation.starred = ? AND annotation.play_date > ? AND (media_file.artist <> ? OR media_file.album = ?)) ORDER BY artist asc LIMIT 100" ) )
lastMonth := time . Now ( ) . Add ( - 30 * 24 * time . Hour )
Expect ( args ) . To ( ConsistOf ( "%love%" , 1980 , 1989 , true , BeTemporally ( "~" , lastMonth , time . Second ) , "zé" , "4" ) )
} )
2021-10-14 11:23:13 -08:00
It ( "returns an error if field is invalid" , func ( ) {
r := pls . Rules [ 0 ] . ( model . Rule )
r . Field = "INVALID"
pls . Rules [ 0 ] = r
sel := pls . AddFilters ( squirrel . Select ( "media_file" ) . Columns ( "*" ) )
_ , _ , err := sel . ToSql ( )
Expect ( err ) . To ( MatchError ( "invalid smart playlist field 'INVALID'" ) )
} )
2021-10-08 18:23:43 -08:00
} )
Describe ( "fieldMap" , func ( ) {
It ( "includes all possible fields" , func ( ) {
for _ , field := range model . SmartPlaylistFields {
Expect ( fieldMap ) . To ( HaveKey ( field ) )
}
} )
It ( "does not have extra fields" , func ( ) {
for field := range fieldMap {
Expect ( model . SmartPlaylistFields ) . To ( ContainElement ( field ) )
}
} )
} )
Describe ( "stringRule" , func ( ) {
DescribeTable ( "stringRule" ,
func ( operator , expectedSql , expectedValue string ) {
r := stringRule { Field : "title" , Operator : operator , Value : "value" }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( expectedSql ) )
Expect ( args ) . To ( ConsistOf ( expectedValue ) )
} ,
Entry ( "is" , "is" , "title = ?" , "value" ) ,
Entry ( "is not" , "is not" , "title <> ?" , "value" ) ,
Entry ( "contains" , "contains" , "title ILIKE ?" , "%value%" ) ,
Entry ( "does not contains" , "does not contains" , "title NOT ILIKE ?" , "%value%" ) ,
Entry ( "begins with" , "begins with" , "title ILIKE ?" , "value%" ) ,
Entry ( "ends with" , "ends with" , "title ILIKE ?" , "%value" ) ,
)
} )
Describe ( "numberRule" , func ( ) {
DescribeTable ( "operators" ,
2021-10-14 11:23:13 -08:00
func ( operator , expectedSql string , expectedValue int ) {
2021-10-08 18:23:43 -08:00
r := numberRule { Field : "year" , Operator : operator , Value : 1985 }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( expectedSql ) )
2021-10-14 11:23:13 -08:00
Expect ( args ) . To ( ConsistOf ( expectedValue ) )
2021-10-08 18:23:43 -08:00
} ,
Entry ( "is" , "is" , "year = ?" , 1985 ) ,
Entry ( "is not" , "is not" , "year <> ?" , 1985 ) ,
Entry ( "is greater than" , "is greater than" , "year > ?" , 1985 ) ,
Entry ( "is less than" , "is less than" , "year < ?" , 1985 ) ,
)
It ( "implements the 'is in the range' operator" , func ( ) {
r := numberRule { Field : "year" , Operator : "is in the range" , Value : [ ] int { 1981 , 1990 } }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( "(year >= ? AND year <= ?)" ) )
Expect ( args ) . To ( ConsistOf ( 1981 , 1990 ) )
} )
} )
2021-10-14 11:23:13 -08:00
Describe ( "dateRule" , func ( ) {
dateStr := "2021-10-14"
date , _ := time . Parse ( "2006-01-02" , dateStr )
DescribeTable ( "simple operators" ,
func ( operator , expectedSql string , expectedValue time . Time ) {
r := dateRule { Field : "lastPlayed" , Operator : operator , Value : dateStr }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( expectedSql ) )
Expect ( args ) . To ( ConsistOf ( expectedValue ) )
} ,
Entry ( "is" , "is" , "lastPlayed = ?" , date ) ,
Entry ( "is not" , "is not" , "lastPlayed <> ?" , date ) ,
Entry ( "is before" , "is before" , "lastPlayed < ?" , date ) ,
Entry ( "is after" , "is after" , "lastPlayed > ?" , date ) ,
)
DescribeTable ( "period operators" ,
func ( operator , expectedSql string , expectedValue time . Time ) {
2021-10-14 17:24:03 -08:00
delta := 30 * time . Hour // Must be large to account for the hours of the day
2021-10-14 11:23:13 -08:00
r := dateRule { Field : "lastPlayed" , Operator : operator , Value : 90 }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( expectedSql ) )
2021-10-14 17:24:03 -08:00
Expect ( args ) . To ( ConsistOf ( BeTemporally ( "~" , expectedValue , delta ) ) )
2021-10-14 11:23:13 -08:00
} ,
Entry ( "in the last" , "in the last" , "lastPlayed > ?" , date . Add ( - 90 * 24 * time . Hour ) ) ,
Entry ( "not in the last" , "not in the last" , "lastPlayed < ?" , date . Add ( - 90 * 24 * time . Hour ) ) ,
)
It ( "accepts string as the 'in the last' operator value" , func ( ) {
r := dateRule { Field : "lastPlayed" , Operator : "in the last" , Value : "90" }
_ , args , _ := r . ToSql ( )
2021-10-14 17:24:03 -08:00
Expect ( args ) . To ( ConsistOf ( BeTemporally ( "~" , date . Add ( - 90 * 24 * time . Hour ) , 30 * time . Hour ) ) )
2021-10-14 11:23:13 -08:00
} )
It ( "implements the 'is in the range' operator" , func ( ) {
date2Str := "2021-09-14"
date2 , _ := time . Parse ( "2006-01-02" , date2Str )
r := dateRule { Field : "lastPlayed" , Operator : "is in the range" , Value : [ ] string { date2Str , dateStr } }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( "(lastPlayed >= ? AND lastPlayed <= ?)" ) )
2021-10-14 17:24:03 -08:00
Expect ( args ) . To ( ConsistOf ( BeTemporally ( "~" , date2 , 30 * time . Hour ) , BeTemporally ( "~" , date , 30 * time . Hour ) ) )
2021-10-14 11:23:13 -08:00
} )
It ( "returns error if date is invalid" , func ( ) {
r := dateRule { Field : "lastPlayed" , Operator : "is" , Value : "INVALID" }
_ , _ , err := r . ToSql ( )
Expect ( err ) . To ( MatchError ( "invalid date: INVALID" ) )
} )
} )
Describe ( "boolRule" , func ( ) {
DescribeTable ( "operators" ,
func ( operator , expectedSql string , expectedValue ... interface { } ) {
r := boolRule { Field : "loved" , Operator : operator }
sql , args , err := r . ToSql ( )
Expect ( err ) . ToNot ( HaveOccurred ( ) )
Expect ( sql ) . To ( Equal ( expectedSql ) )
Expect ( args ) . To ( ConsistOf ( expectedValue ... ) )
} ,
Entry ( "is true" , "is true" , "loved = ?" , true ) ,
Entry ( "is false" , "is false" , "loved = ?" , false ) ,
)
} )
2021-10-08 18:23:43 -08:00
} )