From 77bda79e36f8c889e734f599bd284eedec43da46 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Fri, 21 Nov 2025 15:49:27 +0900 Subject: [PATCH] filter: add unit tests --- src/filter.rs | 391 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 384 insertions(+), 7 deletions(-) diff --git a/src/filter.rs b/src/filter.rs index d030c31..d75a152 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -14,16 +14,27 @@ pub enum CaseSensitivity { CommandDependent, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ComparisonOperator { + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Filter { Not(Box), And(Box, Box), - EqTag(Tag, String, CaseSensitivity), - Contains(Tag, String, CaseSensitivity), - StartsWith(Tag, String, CaseSensitivity), - PerlRegex(Tag, String), - NegPerlRegex(Tag, String), + // The bool indicates whether the comparison is negated (true for !=, false for ==) + EqTag(Tag, CaseSensitivity, bool), + Contains(Tag, CaseSensitivity, bool), + StartsWith(Tag, CaseSensitivity, bool), + PerlRegex(Tag), + NegPerlRegex(Tag), EqUri(String), Base(String), ModifiedSince(u64), @@ -37,9 +48,375 @@ pub enum Filter { bits: Option, channels: Option, }, - PrioCmp(Priority), + PrioCmp(ComparisonOperator, Priority), } pub fn parse_filter(parts: &mut SplitWhitespace<'_>) -> Result { - todo!() + // TODO: count parentheses and extract the full filter string + unimplemented!() +} + +fn parse_filter_str(string: &str) -> Result { + unimplemented!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_filter_eq_tag() { + let mut parts = "(artist == 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CommandDependent, + false, + ) + ); + + let mut parts = "(artist != 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CommandDependent, + true, + ) + ); + } + + #[test] + fn test_parse_filter_contains() { + let mut parts = "(album contains 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CommandDependent, + false, + ), + ); + + let mut parts = "(album !contains 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CommandDependent, + true, + ), + ); + } + + #[test] + fn test_parse_filter_starts_with() { + let mut parts = "(title starts_with 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CommandDependent, + false, + ), + ); + + let mut parts = "(title !starts_with 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CommandDependent, + true, + ), + ); + } + + #[test] + fn test_parse_filter_perl_regex_positive() { + let mut parts = "(composer =~ 'Beethoven.*')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::PerlRegex(Tag::Composer("Beethoven.*".to_string())), + ); + } + + #[test] + fn test_parse_filter_perl_regex_negative() { + let mut parts = "(genre !~ 'Pop.*')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::NegPerlRegex(Tag::Genre("Pop.*".to_string())), + ); + } + + #[test] + fn test_parse_filter_base() { + let mut parts = "(base 'Rock/Classics')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!(filter, Filter::Base("Rock/Classics".to_string()),); + } + + #[test] + fn test_parse_filter_modified_since() { + let mut parts = "(modified-since '1622505600')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!(filter, Filter::ModifiedSince(1622505600),); + } + + #[test] + fn test_parse_filter_audio_added_since() { + let mut parts = "(added-since '1622505600')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!(filter, Filter::ModifiedSince(1622505600),); + } + + #[test] + fn test_parse_filter_audio_format_eq() { + let mut parts = "(AudioFormat == '44100:16:2')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::AudioFormatEq { + sample_rate: 44100, + bits: 16, + channels: 2, + }, + ); + } + + #[test] + fn test_parse_filter_audio_format_eq_mask() { + let mut parts = "(AudioFormat =~ '44100:*:2')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::AudioFormatEqMask { + sample_rate: Some(44100), + bits: None, + channels: Some(2), + }, + ); + } + + #[test] + fn test_parse_filter_prio_cmp() { + let mut parts = "(prio >= 42)".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::PrioCmp(ComparisonOperator::GreaterThanOrEqual, 42), + ); + } + + #[test] + fn test_parse_filter_not() { + let mut parts = "(!(artist == 'The Beatles'))".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Not(Box::new(Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CommandDependent, + false, + ))), + ); + } + + #[test] + fn test_parse_filter_and() { + let mut parts = + "((artist == 'The Beatles') AND (album == 'Abbey Road'))".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::And( + Box::new(Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CommandDependent, + false, + )), + Box::new(Filter::EqTag( + Tag::Album("Abbey Road".to_string()), + CaseSensitivity::CommandDependent, + false, + )), + ), + ); + } + + #[test] + fn test_parse_filter_explicitly_case_sensitive() { + let mut parts = "(artist eq_cs 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CaseSensitive, + false, + ), + ); + + let mut parts = "(artist !eq_cs 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CaseSensitive, + true, + ), + ); + + let mut parts = "(album contains_cs 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CaseSensitive, + false, + ), + ); + + let mut parts = "(album !contains_cs 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CaseSensitive, + true, + ), + ); + + let mut parts = "(title starts_with_cs 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CaseSensitive, + false, + ), + ); + + let mut parts = "(title !starts_with_cs 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CaseSensitive, + true, + ), + ); + } + + #[test] + fn test_parse_filter_explicitly_case_insensitive() { + let mut parts = "(artist eq_ci 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CaseInsensitive, + false, + ), + ); + + let mut parts = "(artist !eq_ci 'The Beatles')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CaseInsensitive, + true, + ), + ); + + let mut parts = "(album contains_ci 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CaseInsensitive, + false, + ), + ); + + let mut parts = "(album !contains_ci 'Greatest Hits')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::Contains( + Tag::Album("Greatest Hits".to_string()), + CaseSensitivity::CaseInsensitive, + true, + ), + ); + + let mut parts = "(title starts_with_ci 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CaseInsensitive, + false, + ), + ); + + let mut parts = "(title !starts_with_ci 'Symphony No. ')".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::StartsWith( + Tag::Title("Symphony No. ".to_string()), + CaseSensitivity::CaseInsensitive, + true, + ), + ); + } + + #[test] + fn test_parse_filter_string_escapes() { + let mut parts = "(Artist == \"foo\\'bar\\\"\")".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("foo'bar\"".to_string()), + CaseSensitivity::CommandDependent, + false, + ), + ); + } + + #[test] + fn test_parse_filter_excessive_whitespace() { + let mut parts = "(\tartist\n == 'The Beatles' ) ".split_whitespace(); + let filter = parse_filter(&mut parts).unwrap(); + assert_eq!( + filter, + Filter::EqTag( + Tag::Artist("The Beatles".to_string()), + CaseSensitivity::CommandDependent, + false, + ) + ); + } }