diff --git a/src/lib.rs b/src/lib.rs index 61f73d2..1819ea3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,7 +139,9 @@ impl MusicHoard { let mut album_ids = HashMap::>::new(); for item in items.into_iter() { - let artist_id = ArtistId { name: item.album_artist }; + let artist_id = ArtistId { + name: item.album_artist, + }; let album_id = AlbumId { year: item.album_year, @@ -216,13 +218,125 @@ mod tests { pub static COLLECTION: Lazy> = Lazy::new(|| collection!()); + pub fn artist_to_items(artist: &Artist) -> Vec { + let mut items = vec![]; + + for album in artist.albums.iter() { + for track in album.tracks.iter() { + items.push(Item { + album_artist: artist.id.name.clone(), + album_year: album.id.year, + album_title: album.id.title.clone(), + track_number: track.number, + track_title: track.title.clone(), + track_artist: track.artist.clone(), + track_format: track.quality.format, + track_bitrate: track.quality.bitrate, + }); + } + } + + items + } + + pub fn artists_to_items(artists: &[Artist]) -> Vec { + let mut items = vec![]; + for artist in artists.iter() { + items.append(&mut artist_to_items(artist)); + } + items + } + + #[test] + fn rescan_library_ordered() { + let mut library = MockILibrary::new(); + let database = MockIDatabase::new(); + + let library_input = Query::new(); + let library_result = Ok(artists_to_items(&COLLECTION)); + + library + .expect_list() + .with(predicate::eq(library_input)) + .times(1) + .return_once(|_| library_result); + + let mut music_hoard = MusicHoard::new(library, database); + + music_hoard.rescan_library().unwrap(); + assert_eq!(music_hoard.get_collection(), &*COLLECTION); + } + + #[test] + fn rescan_library_unordered() { + let mut library = MockILibrary::new(); + let database = MockIDatabase::new(); + + let library_input = Query::new(); + let mut library_result = Ok(artists_to_items(&COLLECTION)); + + // Swap the last item with the first. + let last = library_result.as_ref().unwrap().len() - 1; + library_result.as_mut().unwrap().swap(0, last); + + library + .expect_list() + .with(predicate::eq(library_input)) + .times(1) + .return_once(|_| library_result); + + let mut music_hoard = MusicHoard::new(library, database); + + let mut expected = COLLECTION.to_owned(); + + // Putting the last track first will make the entire artist come first in the output. + expected.rotate_right(1); + + // Same applies to that artists' albums. + expected[0].albums.rotate_right(1); + + // Same applies to that album's tracks. + expected[0].albums[0].tracks.rotate_right(1); + + // And the original first album's (now the first album of the second artist) tracks first + // track comes last as it will only get picked up at the end. + expected[1].albums[0].tracks.rotate_left(1); + + music_hoard.rescan_library().unwrap(); + assert_eq!(music_hoard.get_collection(), &expected); + } + + #[test] + fn rescan_library_album_title_year_clash() { + let mut library = MockILibrary::new(); + let database = MockIDatabase::new(); + + let mut expected = COLLECTION.to_owned(); + expected[0].albums[0].id.year = expected[1].albums[0].id.year; + expected[0].albums[0].id.title = expected[1].albums[0].id.title.clone(); + + let library_input = Query::new(); + let library_result = Ok(artists_to_items(&expected)); + + library + .expect_list() + .with(predicate::eq(library_input)) + .times(1) + .return_once(|_| library_result); + + let mut music_hoard = MusicHoard::new(library, database); + + music_hoard.rescan_library().unwrap(); + assert_eq!(music_hoard.get_collection(), &expected); + } + #[test] fn read_get_write() { let mut library = MockILibrary::new(); let mut database = MockIDatabase::new(); let library_input = Query::new(); - let library_result = Ok(COLLECTION.to_owned()); + let library_result = Ok(artists_to_items(&COLLECTION)); let database_input = COLLECTION.to_owned(); let database_result = Ok(()); diff --git a/src/library/beets/mod.rs b/src/library/beets/mod.rs index cedbfd0..9253016 100644 --- a/src/library/beets/mod.rs +++ b/src/library/beets/mod.rs @@ -164,45 +164,34 @@ impl ILibraryPrivate for BeetsLibrary { mod tests { use mockall::predicate; - use crate::tests::COLLECTION; + use crate::tests::{artists_to_items, COLLECTION}; use super::*; - fn artist_to_beets_string(artist: &Artist) -> Vec { - let mut strings = vec![]; - - let album_artist = &artist.id.name; - - for album in artist.albums.iter() { - let album_year = &album.id.year; - let album_title = &album.id.title; - - for track in album.tracks.iter() { - let track_number = &track.number; - let track_title = &track.title; - let track_artist = &track.artist.join("; "); - let track_format = match track.quality.format { - Format::Flac => TRACK_FORMAT_FLAC, - Format::Mp3 => TRACK_FORMAT_MP3, - }; - let track_bitrate = track.quality.bitrate; - - strings.push(format!( - "{album_artist}{0}{album_year}{0}{album_title}{0}\ - {track_number}{0}{track_title}{0}\ - {track_artist}{0}{track_format}{0}{track_bitrate}kbps", - LIST_FORMAT_SEPARATOR, - )); - } - } - - strings + fn item_to_beets_string(item: &Item) -> String { + format!( + "{album_artist}{sep}{album_year}{sep}{album_title}{sep}\ + {track_number}{sep}{track_title}{sep}\ + {track_artist}{sep}{track_format}{sep}{track_bitrate}kbps", + album_artist = item.album_artist, + album_year = item.album_year, + album_title = item.album_title, + track_number = item.track_number, + track_title = item.track_title, + track_artist = item.track_artist.join("; "), + track_format = match item.track_format { + Format::Flac => TRACK_FORMAT_FLAC, + Format::Mp3 => TRACK_FORMAT_MP3, + }, + track_bitrate = item.track_bitrate, + sep = LIST_FORMAT_SEPARATOR, + ) } - fn artists_to_beets_string(artists: &[Artist]) -> Vec { + fn items_to_beets_strings(items: &[Item]) -> Vec { let mut strings = vec![]; - for artist in artists.iter() { - strings.append(&mut artist_to_beets_string(artist)); + for item in items.iter() { + strings.push(item_to_beets_string(item)); } strings } @@ -267,72 +256,15 @@ mod tests { let mut beets = BeetsLibrary::new(executor); let output = beets.list(&Query::new()).unwrap(); - let expected: Vec = vec![]; + let expected: Vec = vec![]; assert_eq!(output, expected); } #[test] - fn test_list_ordered() { + fn test_list() { let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; - let expected = COLLECTION.to_owned(); - let result = Ok(artists_to_beets_string(&expected)); - - let mut executor = MockIBeetsLibraryExecutor::new(); - executor - .expect_exec() - .with(predicate::eq(arguments)) - .times(1) - .return_once(|_| result); - - let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::new()).unwrap(); - - assert_eq!(output, expected); - } - - #[test] - fn test_list_unordered() { - let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; - let mut expected = COLLECTION.to_owned(); - let mut output = artists_to_beets_string(&expected); - let last = output.len() - 1; - output.swap(0, last); - let result = Ok(output); - - // Putting the last track first will make the entire artist come first in the output. - expected.rotate_right(1); - - // Same applies to that artists' albums. - expected[0].albums.rotate_right(1); - - // Same applies to that album's tracks. - expected[0].albums[0].tracks.rotate_right(1); - - // And the original first album's (now the first album of the second artist) tracks first - // track comes last. - expected[1].albums[0].tracks.rotate_left(1); - - let mut executor = MockIBeetsLibraryExecutor::new(); - executor - .expect_exec() - .with(predicate::eq(arguments)) - .times(1) - .return_once(|_| result); - - let mut beets = BeetsLibrary::new(executor); - let output = beets.list(&Query::new()).unwrap(); - - assert_eq!(output, expected); - } - - #[test] - fn test_list_album_title_year_clash() { - let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; - let mut expected = COLLECTION.to_owned(); - expected[0].albums[0].id.year = expected[1].albums[0].id.year; - expected[0].albums[0].id.title = expected[1].albums[0].id.title.clone(); - let output = artists_to_beets_string(&expected); - let result = Ok(output); + let expected = artists_to_items(&COLLECTION); + let result = Ok(items_to_beets_strings(&expected)); let mut executor = MockIBeetsLibraryExecutor::new(); executor @@ -378,15 +310,15 @@ mod tests { let mut beets = BeetsLibrary::new(executor); let output = beets.list(&query).unwrap(); - let expected: Vec = vec![]; + let expected: Vec = vec![]; assert_eq!(output, expected); } #[test] fn invalid_data_split() { let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; - let expected = COLLECTION.to_owned(); - let mut output = artists_to_beets_string(&expected); + let expected = artists_to_items(&COLLECTION); + let mut output = items_to_beets_strings(&expected); let invalid_string = output[2] .split(LIST_FORMAT_SEPARATOR) .map(|s| s.to_owned()) @@ -411,8 +343,8 @@ mod tests { #[test] fn invalid_data_format() { let arguments = vec!["ls".to_string(), LIST_FORMAT_ARG.to_string()]; - let expected = COLLECTION.to_owned(); - let mut output = artists_to_beets_string(&expected); + let expected = artists_to_items(&COLLECTION); + let mut output = items_to_beets_strings(&expected); let mut invalid_string = output[2] .split(LIST_FORMAT_SEPARATOR) .map(|s| s.to_owned()) diff --git a/src/library/mod.rs b/src/library/mod.rs index eba3b2d..98905c7 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -18,6 +18,7 @@ pub trait ILibrary { } /// An item from the library. An item corresponds to an individual file (usually a single track). +#[derive(Debug, PartialEq, Eq)] pub struct Item { pub album_artist: String, pub album_year: u32, diff --git a/tests/library/beets.rs b/tests/library/beets.rs index eb8f5f6..57cad1d 100644 --- a/tests/library/beets.rs +++ b/tests/library/beets.rs @@ -9,7 +9,7 @@ use once_cell::sync::Lazy; use musichoard::{ library::{ beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary}, - Field, ILibrary, Query, + Field, ILibrary, Item, Query, }, Artist, }; @@ -32,6 +32,35 @@ static BEETS_TEST_CONFIG: Lazy Vec { + let mut items = vec![]; + + for album in artist.albums.iter() { + for track in album.tracks.iter() { + items.push(Item { + album_artist: artist.id.name.clone(), + album_year: album.id.year, + album_title: album.id.title.clone(), + track_number: track.number, + track_title: track.title.clone(), + track_artist: track.artist.clone(), + track_format: track.quality.format, + track_bitrate: track.quality.bitrate, + }); + } + } + + items +} + +fn artists_to_items(artists: &[Artist]) -> Vec { + let mut items = vec![]; + for artist in artists.iter() { + items.append(&mut artist_to_items(artist)); + } + items +} + #[test] fn test_no_config_list() { let beets_arc = BEETS_EMPTY_CONFIG.clone(); @@ -39,7 +68,7 @@ fn test_no_config_list() { let output = beets.list(&Query::new()).unwrap(); - let expected: Vec = vec![]; + let expected: Vec = vec![]; assert_eq!(output, expected); } @@ -61,7 +90,7 @@ fn test_full_list() { let output = beets.list(&Query::new()).unwrap(); - let expected: Vec = COLLECTION.to_owned(); + let expected: Vec = artists_to_items(&COLLECTION); assert_eq!(output, expected); } @@ -74,7 +103,7 @@ fn test_album_artist_query() { .list(Query::new().include(Field::AlbumArtist(String::from("Аркона")))) .unwrap(); - let expected: Vec = COLLECTION[0..1].to_owned(); + let expected: Vec = artists_to_items(&COLLECTION[0..1]); assert_eq!(output, expected); } @@ -87,7 +116,7 @@ fn test_album_title_query() { .list(&Query::new().include(Field::AlbumTitle(String::from("Slovo")))) .unwrap(); - let expected: Vec = COLLECTION[0..1].to_owned(); + let expected: Vec = artists_to_items(&COLLECTION[0..1]); assert_eq!(output, expected); } @@ -100,6 +129,6 @@ fn test_exclude_query() { .list(&Query::new().exclude(Field::AlbumArtist(String::from("Аркона")))) .unwrap(); - let expected: Vec = COLLECTION[1..].to_owned(); + let expected: Vec = artists_to_items(&COLLECTION[1..]); assert_eq!(output, expected); }