From 603e178e17effef1a8d83f261e44652cc0e9907c Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 21 Apr 2023 15:56:03 +0200 Subject: [PATCH 1/3] fix!: correctly type Optional fields --- cli/src/main.rs | 22 ++++- src/api_model.rs | 179 ++++++++++++++++++++++++++++++++++++++ src/models/album.rs | 31 +++---- src/models/artist.rs | 29 +++--- src/models/lyrics.rs | 12 +-- src/models/snippet.rs | 4 +- src/models/subtitle.rs | 20 ++--- src/models/track.rs | 55 ++++++------ src/models/translation.rs | 8 +- tests/album_test.rs | 15 ++-- tests/artist_test.rs | 14 ++- tests/lyrics_test.rs | 27 +++--- tests/subtitle_test.rs | 21 ++--- tests/track_test.rs | 64 ++++++++------ 14 files changed, 354 insertions(+), 147 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 618de62..0a913be 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -156,6 +156,8 @@ impl From for SubtitleFormat { } } +const NA_STR: &str = "n/a"; + #[tokio::main] async fn main() { let cli = Cli::parse(); @@ -235,8 +237,14 @@ async fn run(cli: Cli) -> Result<()> { }; eprintln!("Lyrics ID: {}", lyrics.lyrics_id); - eprintln!("Language: {}", lyrics.lyrics_language); - eprintln!("Copyright: {}", lyrics.lyrics_copyright); + eprintln!( + "Language: {}", + lyrics.lyrics_language.as_deref().unwrap_or(NA_STR) + ); + eprintln!( + "Copyright: {}", + lyrics.lyrics_copyright.as_deref().unwrap_or(NA_STR) + ); eprintln!(); println!("{}", lyrics.lyrics_body); @@ -298,9 +306,15 @@ async fn run(cli: Cli) -> Result<()> { }; eprintln!("Subtitle ID: {}", subtitles.subtitle_id); - eprintln!("Language: {}", subtitles.subtitle_language); + eprintln!( + "Language: {}", + subtitles.subtitle_language.as_deref().unwrap_or(NA_STR) + ); eprintln!("Length: {}", subtitles.subtitle_length); - eprintln!("Copyright: {}", subtitles.lyrics_copyright); + eprintln!( + "Copyright: {}", + subtitles.lyrics_copyright.as_deref().unwrap_or(NA_STR) + ); eprintln!(); println!("{}", subtitles.subtitle_body); diff --git a/src/api_model.rs b/src/api_model.rs index aa8038c..ca42287 100644 --- a/src/api_model.rs +++ b/src/api_model.rs @@ -227,6 +227,126 @@ where deserializer.deserialize_any(BoolFromIntVisitor) } +/** + * The Musixmatch API returns zero as a default value for numeric IDs. + * These values should be deserialized as [`None`]. + */ +pub fn null_if_zero<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct NullIfZeroVisitor; + + impl<'de> Visitor<'de> for NullIfZeroVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("unsigned int or None") + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + if v < 1 { + Ok(None) + } else { + Ok(Some(v)) + } + } + + fn visit_u32(self, v: u32) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + + fn visit_u8(self, v: u8) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + } + + deserializer.deserialize_any(NullIfZeroVisitor) +} + +pub fn null_if_empty<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct NullIfEmptyVisitor; + + impl<'de> Visitor<'de> for NullIfEmptyVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("string or None") + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + if v.is_empty() { + Ok(None) + } else { + Ok(Some(v.to_owned())) + } + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + if v.is_empty() { + Ok(None) + } else { + Ok(Some(v)) + } + } + } + + deserializer.deserialize_any(NullIfEmptyVisitor) +} + pub mod optional_date { use super::*; use serde::Serializer; @@ -257,6 +377,20 @@ pub mod optional_date { formatter.write_str("timestamp or empty string") } + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + fn visit_str(self, v: &str) -> Result where E: serde::de::Error, @@ -338,6 +472,51 @@ mod tests { ); } + #[test] + fn deserialize_null_if_zero() { + #[derive(Deserialize)] + struct S { + #[serde(default, deserialize_with = "null_if_zero")] + n: Option, + } + + let json = r#"{"n": 0}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, None); + + let json = r#"{"n": 1}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, Some(1)); + + let json = r#"{"n": null}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, None); + + let json = r#"{}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, None); + } + + #[test] + fn deserialize_null_if_empty() { + #[derive(Deserialize)] + struct S { + #[serde(default, deserialize_with = "null_if_empty")] + s: Option, + } + + let json = r#"{"s": ""}"#; + assert_eq!(serde_json::from_str::(json).unwrap().s, None); + + let json = r#"{"s": "a"}"#; + assert_eq!( + serde_json::from_str::(json).unwrap().s, + Some("a".to_owned()) + ); + + let json = r#"{"n": null}"#; + assert_eq!(serde_json::from_str::(json).unwrap().s, None); + + let json = r#"{}"#; + assert_eq!(serde_json::from_str::(json).unwrap().s, None); + } + #[test] fn deserialize_optional_date() { #[derive(Deserialize)] diff --git a/src/models/album.rs b/src/models/album.rs index 4996dcd..51a82a1 100644 --- a/src/models/album.rs +++ b/src/models/album.rs @@ -21,8 +21,8 @@ pub struct Album { /// Musicbrainz album ID /// /// **Note:** most albums dont have this entry set - #[serde(default)] - pub album_mbid: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_mbid: Option, /// Album name pub album_name: String, /// Popularity of the album from 0 to 100 @@ -31,13 +31,14 @@ pub struct Album { /// Number of tracks on the album pub album_track_count: u16, /// Album release date (e.g. "2009-07-07") + // TODO: use date format #[serde(default)] pub album_release_date: String, /// Album type (Single / EP / Album) + // TODO: use enum pub album_release_type: String, /// Musixmatch artist ID - #[serde(default)] pub artist_id: u64, /// Artist name pub artist_name: String, @@ -56,11 +57,11 @@ pub struct Album { pub secondary_genres: Genres, /// Album copyright text - #[serde(default)] - pub album_copyright: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_copyright: Option, /// Album label / recording company - #[serde(default)] - pub album_label: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_label: Option, /// Human-readable URL-safe Album ID /// /// Example: `LMFAO/Party-Rock-5` @@ -71,17 +72,17 @@ pub struct Album { pub updated_time: OffsetDateTime, /// Album cover URL (100x100px) - #[serde(default)] - pub album_coverart_100x100: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_100x100: Option, /// Album cover URL (350x350px) - #[serde(default)] - pub album_coverart_350x350: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_350x350: Option, /// Album cover URL (500x500px) - #[serde(default)] - pub album_coverart_500x500: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_500x500: Option, /// Album cover URL (800x800px) /// /// **Note:** not present on a lot of albums - #[serde(default)] - pub album_coverart_800x800: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_800x800: Option, } diff --git a/src/models/artist.rs b/src/models/artist.rs index 87f337e..02377bf 100644 --- a/src/models/artist.rs +++ b/src/models/artist.rs @@ -21,8 +21,8 @@ pub struct Artist { /// Musicbrainz Artist ID /// /// **Note:** most tracks dont have this entry set - #[serde(default)] - pub artist_mbid: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_mbid: Option, /// Artist name pub artist_name: String, @@ -30,8 +30,8 @@ pub struct Artist { #[serde(default)] pub artist_name_translation_list: Vec, /// Artist origin as a 2-letter country code (e.g. "US") - #[serde(default)] - pub artist_country: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_country: Option, /// Alternative names for the artist (e.g. in different languages) #[serde(default)] pub artist_alias_list: Vec, @@ -53,20 +53,20 @@ pub struct Artist { pub secondary_genres: Genres, /// URL of the artist's Twitter profile - #[serde(default)] - pub artist_twitter_url: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_twitter_url: Option, /// URL of the artist's website - #[serde(default)] - pub artist_website_url: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_website_url: Option, /// URL of the artist's Instagram profile - #[serde(default)] - pub artist_instagram_url: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_instagram_url: Option, /// URL of the artist's TikTok profile - #[serde(default)] - pub artist_tiktok_url: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_tiktok_url: Option, /// URL of the artist's Facebook profile - #[serde(default)] - pub artist_facebook_url: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_facebook_url: Option, /// URL-safe human-readable artist ID /// @@ -76,6 +76,7 @@ pub struct Artist { #[serde(with = "time::serde::rfc3339")] pub updated_time: OffsetDateTime, /// Year of the start of the artist's presence + // TODO: use proper date format for these values #[serde(default)] pub begin_date_year: String, /// Start date of the artist's presence in YYYY-MM-DD format diff --git a/src/models/lyrics.rs b/src/models/lyrics.rs index b270576..dd9053f 100644 --- a/src/models/lyrics.rs +++ b/src/models/lyrics.rs @@ -21,11 +21,11 @@ pub struct Lyrics { /// Lyrics text pub lyrics_body: String, /// Language code (e.g. "en") - #[serde(default)] - pub lyrics_language: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub lyrics_language: Option, /// Language name (e.g. "English") - #[serde(default)] - pub lyrics_language_description: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub lyrics_language_description: Option, /// Copyright text of the lyrics /// /// Ends with a newline. @@ -35,8 +35,8 @@ pub struct Lyrics { /// Writer(s): David Hodges /// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p. /// ``` - #[serde(default)] - pub lyrics_copyright: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub lyrics_copyright: Option, /// Date and time when the lyrics were last updated #[serde(with = "time::serde::rfc3339")] pub updated_time: OffsetDateTime, diff --git a/src/models/snippet.rs b/src/models/snippet.rs index e8a5eee..9dc03a9 100644 --- a/src/models/snippet.rs +++ b/src/models/snippet.rs @@ -18,8 +18,8 @@ pub struct Snippet { /// Unique Musixmatch Snippet ID pub snippet_id: u64, /// Snippet language code (e.g. "en") - #[serde(default)] - pub snippet_language: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub snippet_language: Option, /// True if the track is instrumental #[serde(default, deserialize_with = "crate::api_model::bool_from_int")] pub instrumental: bool, diff --git a/src/models/subtitle.rs b/src/models/subtitle.rs index 17e8ac7..6c84a8b 100644 --- a/src/models/subtitle.rs +++ b/src/models/subtitle.rs @@ -196,11 +196,11 @@ pub struct Subtitle { /// Subtitle / synchronized lyrics in the requested format pub subtitle_body: String, /// Language code (e.g. "en") - #[serde(default)] - pub subtitle_language: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub subtitle_language: Option, /// Language name (e.g. "English") - #[serde(default)] - pub subtitle_language_description: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub subtitle_language_description: Option, /// Copyright text of the lyrics /// /// Ends with a newline. @@ -210,8 +210,8 @@ pub struct Subtitle { /// Writer(s): David Hodges /// Copyright: Emi Blackwood Music Inc., 12.06 Publishing, Hipgnosis Sfh I Limited, Hifi Music Ip Issuer L.p. /// ``` - #[serde(default)] - pub lyrics_copyright: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub lyrics_copyright: Option, /// Duration of the subtitle in seconds pub subtitle_length: u32, /// Date and time when the subtitle was last updated @@ -239,7 +239,7 @@ pub struct SubtitleLines { /// List of subtitle lines pub lines: Vec, /// Language code (e.g. "en") - pub lang: String, + pub lang: Option, /// Duration of the subtitle in seconds pub length: u32, } @@ -268,10 +268,10 @@ impl SubtitleLines { pub fn to_ttml(&self) -> String { let mut ttml = format!( r#" - + -
"#, - self.lang, self.lang +
"#, + self.lang.as_deref().unwrap_or_default(), ); for i in 0..self.lines.len() { diff --git a/src/models/track.rs b/src/models/track.rs index 0a51323..e0c7ccc 100644 --- a/src/models/track.rs +++ b/src/models/track.rs @@ -22,26 +22,26 @@ pub struct Track { /// Track Musicbrainz ID /// /// **Note:** most tracks dont have this entry set - #[serde(default)] - pub track_mbid: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub track_mbid: Option, /// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) - #[serde(default)] - pub track_isrc: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub track_isrc: Option, /// [ISRCs](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) of equivalent tracks (e.g. on different albums) #[serde(default)] pub commontrack_isrcs: Vec>, /// Track ID on Spotify - #[serde(default)] - pub track_spotify_id: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub track_spotify_id: Option, /// Spotify IDs of equivalent tracks (e.g. on different albums) #[serde(default)] pub commontrack_spotify_ids: Vec, /// Track ID on Soundcloud - #[serde(default)] - pub track_soundcloud_id: u64, + #[serde(default, deserialize_with = "crate::api_model::null_if_zero")] + pub track_soundcloud_id: Option, /// Track ID on XBox Music - #[serde(default)] - pub track_xboxmusic_id: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub track_xboxmusic_id: Option, /// Title of the track pub track_name: String, @@ -78,44 +78,41 @@ pub struct Track { pub num_favourite: u32, /// Musixmatch lyrics ID - #[serde(default)] - pub lyrics_id: u64, + #[serde(default, deserialize_with = "crate::api_model::null_if_zero")] + pub lyrics_id: Option, /// Musixmatch subtitle ID - #[serde(default)] - pub subtitle_id: u64, + #[serde(default, deserialize_with = "crate::api_model::null_if_zero")] + pub subtitle_id: Option, /// Musixmatch album ID - #[serde(default)] pub album_id: u64, /// Album name - #[serde(default)] pub album_name: String, /// Musixmatch artist ID pub artist_id: u64, + /// Artist name + pub artist_name: String, /// Musicbrainz artist ID /// /// **Note:** most tracks dont have this entry set - #[serde(default)] - pub artist_mbid: String, - /// Artist name - #[serde(default)] - pub artist_name: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub artist_mbid: Option, /// Album cover URL (100x100px) - #[serde(default)] - pub album_coverart_100x100: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_100x100: Option, /// Album cover URL (350x350px) - #[serde(default)] - pub album_coverart_350x350: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_350x350: Option, /// Album cover URL (500x500px) - #[serde(default)] - pub album_coverart_500x500: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_500x500: Option, /// Album cover URL (800x800px) /// /// **Note:** not present on a lot of albums - #[serde(default)] - pub album_coverart_800x800: String, + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub album_coverart_800x800: Option, /// Human-readable Musixmatch ID /// diff --git a/src/models/translation.rs b/src/models/translation.rs index b203def..46a4e7a 100644 --- a/src/models/translation.rs +++ b/src/models/translation.rs @@ -19,9 +19,9 @@ pub(crate) struct TranslationBody { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[non_exhaustive] pub struct Translation { - /// 2-character source language code (e.g. "en") - #[serde(default)] - pub language_from: String, + /// source language code (e.g. "en") + #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] + pub language_from: Option, /// Translated line #[serde(default)] pub description: String, @@ -102,7 +102,7 @@ impl TranslationMap { time: line.time, }) .collect::>(), - lang: self.lang.to_owned(), + lang: Some(self.lang.to_owned()), length: subtitles.length, } } diff --git a/tests/album_test.rs b/tests/album_test.rs index 8f6d764..8a68d93 100644 --- a/tests/album_test.rs +++ b/tests/album_test.rs @@ -16,7 +16,10 @@ async fn by_id(#[case] album_id: AlbumId<'_>) { let album = new_mxm().album(album_id).await.unwrap(); assert_eq!(album.album_id, 14248253); - assert_eq!(album.album_mbid, "6c3cf9d8-88a8-43ed-850b-55813f01e451"); + assert_eq!( + album.album_mbid.unwrap(), + "6c3cf9d8-88a8-43ed-850b-55813f01e451" + ); assert_eq!(album.album_name, "Gangnam Style (강남스타일)"); assert!(album.album_rating > 25); assert_eq!(album.album_track_count, 1); @@ -43,25 +46,25 @@ async fn by_id(#[case] album_id: AlbumId<'_>) { ); assert_eq!( - album.album_copyright, + album.album_copyright.unwrap(), "© 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc." ); assert_eq!( - album.album_label, + album.album_label.unwrap(), "Silent Records/Universal Republic Records" ); assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single"); assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC)); assert_eq!( - album.album_coverart_100x100, + album.album_coverart_100x100.unwrap(), "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg" ); assert_eq!( - album.album_coverart_350x350, + album.album_coverart_350x350.unwrap(), "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg" ); assert_eq!( - album.album_coverart_500x500, + album.album_coverart_500x500.unwrap(), "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg" ); } diff --git a/tests/artist_test.rs b/tests/artist_test.rs index 9048190..ad8fc73 100644 --- a/tests/artist_test.rs +++ b/tests/artist_test.rs @@ -15,7 +15,10 @@ async fn by_id(#[case] artist_id: ArtistId<'_>) { // dbg!(&artist); assert_eq!(artist.artist_id, 410698); - assert_eq!(artist.artist_mbid, "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"); + assert_eq!( + artist.artist_mbid.unwrap(), + "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a" + ); assert_eq!(artist.artist_name, "PSY"); assert!( artist.artist_name_translation_list.iter().any(|tl| { @@ -25,7 +28,7 @@ async fn by_id(#[case] artist_id: ArtistId<'_>) { "missing Korean translation in: {:?}", artist.artist_name_translation_list ); - assert_eq!(artist.artist_country, "KR"); + assert_eq!(artist.artist_country.unwrap(), "KR"); assert!(artist.artist_rating > 50); let first_genre = &artist.primary_genres.music_genre_list[0].music_genre; assert_eq!(first_genre.music_genre_id, 14); @@ -33,9 +36,12 @@ async fn by_id(#[case] artist_id: ArtistId<'_>) { assert_eq!(first_genre.music_genre_name, "Pop"); assert_eq!(first_genre.music_genre_name_extended, "Pop"); assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); - assert_eq!(artist.artist_twitter_url, "https://twitter.com/psy_oppa"); assert_eq!( - artist.artist_facebook_url, + artist.artist_twitter_url.unwrap(), + "https://twitter.com/psy_oppa" + ); + assert_eq!( + artist.artist_facebook_url.unwrap(), "https://www.facebook.com/officialpsy" ); assert_eq!(artist.artist_vanity_id, "410698"); diff --git a/tests/lyrics_test.rs b/tests/lyrics_test.rs index e8d91af..b731bf0 100644 --- a/tests/lyrics_test.rs +++ b/tests/lyrics_test.rs @@ -20,13 +20,10 @@ async fn from_match() { assert!(lyrics .lyrics_body .starts_with("Eyes, in the sky, gazing far into the night\n")); - assert_eq!(lyrics.lyrics_language, "en"); - assert_eq!(lyrics.lyrics_language_description, "English"); - assert!( - lyrics.lyrics_copyright.contains("Kim Jeffeson"), - "copyright: {}", - lyrics.lyrics_copyright, - ); + assert_eq!(lyrics.lyrics_language.unwrap(), "en"); + assert_eq!(lyrics.lyrics_language_description.unwrap(), "English"); + let copyright = lyrics.lyrics_copyright.unwrap(); + assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC)); } @@ -43,12 +40,12 @@ async fn from_id(#[case] track_id: TrackId<'_>) { // dbg!(&lyrics); assert_eq!(lyrics.lyrics_id, 30126001); - assert_eq!(lyrics.lyrics_language, "ko"); - assert_eq!(lyrics.lyrics_language_description, "Korean"); + assert_eq!(lyrics.lyrics_language.unwrap(), "ko"); + assert_eq!(lyrics.lyrics_language_description.unwrap(), "Korean"); + let copyright = lyrics.lyrics_copyright.unwrap(); assert!( - lyrics.lyrics_copyright.contains("Kenneth Scott Chesak"), - "copyright: {}", - lyrics.lyrics_copyright, + copyright.contains("Kenneth Scott Chesak"), + "copyright: {copyright}", ); assert!(lyrics.updated_time > datetime!(2022-8-27 0:00 UTC)); } @@ -65,9 +62,9 @@ async fn instrumental() { assert!(lyrics.instrumental); assert!(!lyrics.explicit); assert_eq!(lyrics.lyrics_body, ""); - assert_eq!(lyrics.lyrics_language, ""); - assert_eq!(lyrics.lyrics_language_description, ""); - assert_eq!(lyrics.lyrics_copyright, ""); + assert_eq!(lyrics.lyrics_language, None); + assert_eq!(lyrics.lyrics_language_description, None); + assert_eq!(lyrics.lyrics_copyright, None); assert!(lyrics.updated_time > datetime!(2021-6-21 0:00 UTC)); } diff --git a/tests/subtitle_test.rs b/tests/subtitle_test.rs index 6f9e86c..35a54cf 100644 --- a/tests/subtitle_test.rs +++ b/tests/subtitle_test.rs @@ -26,13 +26,10 @@ async fn from_match() { // dbg!(&subtitle); assert_eq!(subtitle.subtitle_id, 36913312); - assert_eq!(subtitle.subtitle_language, "en"); - assert_eq!(subtitle.subtitle_language_description, "English"); - assert!( - subtitle.lyrics_copyright.contains("Kim Jeffeson"), - "copyright: {}", - subtitle.lyrics_copyright, - ); + assert_eq!(subtitle.subtitle_language.unwrap(), "en"); + assert_eq!(subtitle.subtitle_language_description.unwrap(), "English"); + let copyright = subtitle.lyrics_copyright.unwrap(); + assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); assert_eq!(subtitle.subtitle_length, 315); assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC)); } @@ -53,12 +50,12 @@ async fn from_id(#[case] track_id: TrackId<'_>) { // dbg!(&subtitle); assert_eq!(subtitle.subtitle_id, 36476905); - assert_eq!(subtitle.subtitle_language, "ko"); - assert_eq!(subtitle.subtitle_language_description, "Korean"); + assert_eq!(subtitle.subtitle_language.unwrap(), "ko"); + assert_eq!(subtitle.subtitle_language_description.unwrap(), "Korean"); + let copyright = subtitle.lyrics_copyright.unwrap(); assert!( - subtitle.lyrics_copyright.contains("Kenneth Scott Chesak"), - "copyright: {}", - subtitle.lyrics_copyright, + copyright.contains("Kenneth Scott Chesak"), + "copyright: {copyright}", ); assert_eq!(subtitle.subtitle_length, 175); assert!(subtitle.updated_time > datetime!(2022-8-27 0:00 UTC)); diff --git a/tests/track_test.rs b/tests/track_test.rs index 5ef932e..e9b0676 100644 --- a/tests/track_test.rs +++ b/tests/track_test.rs @@ -28,8 +28,11 @@ async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) { // dbg!(&track); assert_eq!(track.track_id, 15476784); - assert_eq!(track.track_mbid, "080975b0-39b1-493c-ae64-5cb3292409bb"); - assert_eq!(track.track_isrc, "USUM70824409"); + assert_eq!( + track.track_mbid.unwrap(), + "080975b0-39b1-493c-ae64-5cb3292409bb" + ); + assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); assert!( track.commontrack_isrcs[0] .iter() @@ -37,7 +40,7 @@ async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) { "commontrack_isrcs: {:?}", &track.commontrack_isrcs[0], ); - assert_eq!(track.track_spotify_id, "5R8dQOPq8haW94K7mgERlO"); + assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); assert!( track .commontrack_spotify_ids @@ -55,23 +58,26 @@ async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) { assert!(track.has_richsync); assert!(track.has_track_structure); assert!(track.num_favourite > 50); - assert_eq!(track.lyrics_id, 30678771); - assert_eq!(track.subtitle_id, 36450705); + assert_eq!(track.lyrics_id.unwrap(), 30678771); + assert_eq!(track.subtitle_id.unwrap(), 36450705); assert_eq!(track.album_id, 13810402); assert_eq!(track.album_name, "The Fame"); assert_eq!(track.artist_id, 378462); - assert_eq!(track.artist_mbid, "650e7db6-b795-4eb5-a702-5ea2fc46c848"); + assert_eq!( + track.artist_mbid.unwrap(), + "650e7db6-b795-4eb5-a702-5ea2fc46c848" + ); assert_eq!(track.artist_name, "Lady Gaga"); assert_eq!( - track.album_coverart_100x100, + track.album_coverart_100x100.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" ); assert_eq!( - track.album_coverart_350x350, + track.album_coverart_350x350.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" ); assert_eq!( - track.album_coverart_500x500, + track.album_coverart_500x500.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" ); assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); @@ -127,8 +133,8 @@ async fn from_id(#[case] track_id: TrackId<'_>) { // dbg!(&track); assert_eq!(track.track_id, 205688271); - assert_eq!(track.track_isrc, "KRA302000590"); - assert_eq!(track.track_spotify_id, "1t2qYCAjUAoGfeFeoBlK51"); + assert_eq!(track.track_isrc.unwrap(), "KRA302000590"); + assert_eq!(track.track_spotify_id.unwrap(), "1t2qYCAjUAoGfeFeoBlK51"); assert_eq!(track.track_name, "Black Mamba"); assert!(track.track_rating > 50); assert_eq!(track.track_length, 175); @@ -137,22 +143,22 @@ async fn from_id(#[case] track_id: TrackId<'_>) { assert!(track.has_subtitles); assert!(track.has_richsync); assert!(track.num_favourite > 200); - assert_eq!(track.lyrics_id, 30126001); - assert_eq!(track.subtitle_id, 36476905); + assert_eq!(track.lyrics_id.unwrap(), 30126001); + assert_eq!(track.subtitle_id.unwrap(), 36476905); assert_eq!(track.album_id, 41035954); assert_eq!(track.album_name, "Black Mamba - Single"); assert_eq!(track.artist_id, 46970441); assert_eq!(track.artist_name, "aespa"); assert_eq!( - track.album_coverart_100x100, + track.album_coverart_100x100.unwrap(), "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg" ); assert_eq!( - track.album_coverart_350x350, + track.album_coverart_350x350.unwrap(), "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg" ); assert_eq!( - track.album_coverart_500x500, + track.album_coverart_500x500.unwrap(), "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg" ); assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba"); @@ -180,8 +186,11 @@ async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: // dbg!(&track); assert_eq!(track.track_id, 15476784); - assert_eq!(track.track_mbid, "080975b0-39b1-493c-ae64-5cb3292409bb"); - assert_eq!(track.track_isrc, "USUM70824409"); + assert_eq!( + track.track_mbid.unwrap(), + "080975b0-39b1-493c-ae64-5cb3292409bb" + ); + assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); assert!( track.commontrack_isrcs[0] .iter() @@ -189,7 +198,7 @@ async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: "commontrack_isrcs: {:?}", &track.commontrack_isrcs[0], ); - assert_eq!(track.track_spotify_id, "5R8dQOPq8haW94K7mgERlO"); + assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); assert!( track .commontrack_spotify_ids @@ -207,23 +216,26 @@ async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: assert!(track.has_richsync); assert!(track.has_track_structure); assert!(track.num_favourite > 50); - assert_eq!(track.lyrics_id, 30678771); - assert_eq!(track.subtitle_id, 36450705); + assert_eq!(track.lyrics_id.unwrap(), 30678771); + assert_eq!(track.subtitle_id.unwrap(), 36450705); assert_eq!(track.album_id, 13810402); assert_eq!(track.album_name, "The Fame"); assert_eq!(track.artist_id, 378462); - assert_eq!(track.artist_mbid, "650e7db6-b795-4eb5-a702-5ea2fc46c848"); + assert_eq!( + track.artist_mbid.unwrap(), + "650e7db6-b795-4eb5-a702-5ea2fc46c848" + ); assert_eq!(track.artist_name, "Lady Gaga"); assert_eq!( - track.album_coverart_100x100, + track.album_coverart_100x100.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" ); assert_eq!( - track.album_coverart_350x350, + track.album_coverart_350x350.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" ); assert_eq!( - track.album_coverart_500x500, + track.album_coverart_500x500.unwrap(), "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" ); assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); @@ -402,7 +414,7 @@ async fn snippet() { .unwrap(); assert_eq!(snippet.snippet_id, 23036767); - assert_eq!(snippet.snippet_language, "en"); + assert_eq!(snippet.snippet_language.unwrap(), "en"); assert!(!snippet.instrumental); assert!(snippet.updated_time > datetime!(2022-8-29 0:00 UTC)); assert_eq!( From 199827e776e3b5e766845381c1c984cd94240920 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 21 Apr 2023 16:09:53 +0200 Subject: [PATCH 2/3] tests: merge tests into one file --- src/apis/track_api.rs | 2 +- tests/_fixtures.rs | 21 - tests/album_test.rs | 107 ----- tests/artist_test.rs | 122 ----- tests/lyrics_test.rs | 142 ------ tests/subtitle_test.rs | 132 ------ tests/tests.rs | 972 ++++++++++++++++++++++++++++++++++++++ tests/track_test.rs | 424 ----------------- tests/translation_test.rs | 31 -- 9 files changed, 973 insertions(+), 980 deletions(-) delete mode 100644 tests/_fixtures.rs delete mode 100644 tests/album_test.rs delete mode 100644 tests/artist_test.rs delete mode 100644 tests/lyrics_test.rs delete mode 100644 tests/subtitle_test.rs create mode 100644 tests/tests.rs delete mode 100644 tests/track_test.rs delete mode 100644 tests/translation_test.rs diff --git a/src/apis/track_api.rs b/src/apis/track_api.rs index b556cc6..045779f 100644 --- a/src/apis/track_api.rs +++ b/src/apis/track_api.rs @@ -191,7 +191,7 @@ impl Musixmatch { /// Create a new query builder for searching tracks in the Musixmatch database. /// - /// **Note:** The search results are unsorted the by default. You probably want + /// **Note:** The search results are unordered the by default. You probably want /// to sort by popularity (`.s_track_rating(SortOrder::Desc)`) to get relevant results. /// /// # Example diff --git a/tests/_fixtures.rs b/tests/_fixtures.rs deleted file mode 100644 index a754885..0000000 --- a/tests/_fixtures.rs +++ /dev/null @@ -1,21 +0,0 @@ -use musixmatch_inofficial::{storage::FileStorage, Musixmatch}; - -#[ctor::ctor] -fn init() { - let _ = dotenvy::dotenv(); - env_logger::init(); - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(new_mxm().login()) - .unwrap(); -} - -pub fn new_mxm() -> Musixmatch { - Musixmatch::new( - &std::env::var("MUSIXMATCH_EMAIL").unwrap(), - &std::env::var("MUSIXMATCH_PASSWORD").unwrap(), - Some(Box::new(FileStorage::default())), - ) -} diff --git a/tests/album_test.rs b/tests/album_test.rs deleted file mode 100644 index 8a68d93..0000000 --- a/tests/album_test.rs +++ /dev/null @@ -1,107 +0,0 @@ -mod _fixtures; -use _fixtures::*; - -use musixmatch_inofficial::{ - models::{AlbumId, ArtistId}, - Error, -}; -use rstest::rstest; -use time::macros::datetime; - -#[rstest] -#[case::id(AlbumId::AlbumId(14248253))] -#[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))] -#[tokio::test] -async fn by_id(#[case] album_id: AlbumId<'_>) { - let album = new_mxm().album(album_id).await.unwrap(); - - assert_eq!(album.album_id, 14248253); - assert_eq!( - album.album_mbid.unwrap(), - "6c3cf9d8-88a8-43ed-850b-55813f01e451" - ); - assert_eq!(album.album_name, "Gangnam Style (강남스타일)"); - assert!(album.album_rating > 25); - assert_eq!(album.album_track_count, 1); - assert_eq!(album.album_release_date, "2012-01-01"); - assert_eq!(album.album_release_type, "Single"); - assert_eq!(album.artist_id, 410698); - assert_eq!(album.artist_name, "PSY"); - - let first_pri_genre = &album.primary_genres.music_genre_list[0].music_genre; - assert_eq!(first_pri_genre.music_genre_id, 14); - assert_eq!(first_pri_genre.music_genre_parent_id, 34); - assert_eq!(first_pri_genre.music_genre_name, "Pop"); - assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); - assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); - - let first_sec_genre = &album.secondary_genres.music_genre_list[0].music_genre; - assert_eq!(first_sec_genre.music_genre_id, 17); - assert_eq!(first_sec_genre.music_genre_parent_id, 34); - assert_eq!(first_sec_genre.music_genre_name, "Dance"); - assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); - assert_eq!( - first_sec_genre.music_genre_vanity.as_ref().unwrap(), - "Dance" - ); - - assert_eq!( - album.album_copyright.unwrap(), - "© 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc." - ); - assert_eq!( - album.album_label.unwrap(), - "Silent Records/Universal Republic Records" - ); - assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single"); - assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC)); - assert_eq!( - album.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg" - ); - assert_eq!( - album.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg" - ); - assert_eq!( - album.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg" - ); -} - -#[tokio::test] -async fn by_id_missing() { - let err = new_mxm() - .album(AlbumId::AlbumId(999999999999)) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn artist_albums() { - let albums = new_mxm() - .artist_albums(ArtistId::ArtistId(1039), None, 10, 1) - .await - .unwrap(); - - assert_eq!(albums.len(), 10); -} - -#[tokio::test] -async fn artist_albums_missing() { - let err = new_mxm() - .artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn charts() { - let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap(); - - assert_eq!(albums.len(), 10); -} diff --git a/tests/artist_test.rs b/tests/artist_test.rs deleted file mode 100644 index ad8fc73..0000000 --- a/tests/artist_test.rs +++ /dev/null @@ -1,122 +0,0 @@ -mod _fixtures; -use _fixtures::*; - -use musixmatch_inofficial::{models::ArtistId, Error}; -use rstest::rstest; -use time::macros::datetime; - -#[rstest] -#[case::id(ArtistId::ArtistId(410698))] -#[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))] -#[tokio::test] -async fn by_id(#[case] artist_id: ArtistId<'_>) { - let artist = new_mxm().artist(artist_id).await.unwrap(); - - // dbg!(&artist); - - assert_eq!(artist.artist_id, 410698); - assert_eq!( - artist.artist_mbid.unwrap(), - "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a" - ); - assert_eq!(artist.artist_name, "PSY"); - assert!( - artist.artist_name_translation_list.iter().any(|tl| { - tl.artist_name_translation.language == "KO" - && tl.artist_name_translation.translation == "싸이" - }), - "missing Korean translation in: {:?}", - artist.artist_name_translation_list - ); - assert_eq!(artist.artist_country.unwrap(), "KR"); - assert!(artist.artist_rating > 50); - let first_genre = &artist.primary_genres.music_genre_list[0].music_genre; - assert_eq!(first_genre.music_genre_id, 14); - assert_eq!(first_genre.music_genre_parent_id, 34); - assert_eq!(first_genre.music_genre_name, "Pop"); - assert_eq!(first_genre.music_genre_name_extended, "Pop"); - assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); - assert_eq!( - artist.artist_twitter_url.unwrap(), - "https://twitter.com/psy_oppa" - ); - assert_eq!( - artist.artist_facebook_url.unwrap(), - "https://www.facebook.com/officialpsy" - ); - assert_eq!(artist.artist_vanity_id, "410698"); - assert!(artist.updated_time > datetime!(2016-6-30 0:00 UTC)); - assert_eq!(artist.begin_date_year, "1977"); - assert_eq!(artist.begin_date, "1977-12-31"); - assert_eq!(artist.end_date_year, ""); - assert_eq!(artist.end_date, "0000-00-00"); -} - -#[tokio::test] -async fn by_id_missing() { - let err = new_mxm() - .artist(ArtistId::ArtistId(999999999999)) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn related() { - let artists = new_mxm() - .artist_related(ArtistId::ArtistId(26485840), 10, 1) - .await - .unwrap(); - - assert_eq!(artists.len(), 10); -} - -#[tokio::test] -async fn related_missing() { - let err = new_mxm() - .artist_related(ArtistId::ArtistId(999999999999), 10, 1) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn search() { - let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap(); - - assert_eq!(artists.len(), 5); - - let artist = &artists[0]; - assert_eq!(artist.artist_id, 410698); - assert_eq!(artist.artist_name, "PSY"); -} - -#[tokio::test] -async fn search_empty() { - let artists = new_mxm() - .artist_search( - "Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz", - 5, - 1, - ) - .await - .unwrap(); - - assert_eq!(artists.len(), 0); -} - -#[tokio::test] -async fn charts() { - let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap(); - - assert_eq!(artists.len(), 10); -} - -#[tokio::test] -async fn charts_no_country() { - let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap(); - - assert_eq!(artists.len(), 10); -} diff --git a/tests/lyrics_test.rs b/tests/lyrics_test.rs deleted file mode 100644 index b731bf0..0000000 --- a/tests/lyrics_test.rs +++ /dev/null @@ -1,142 +0,0 @@ -mod _fixtures; -use _fixtures::*; - -use std::{fs::File, io::BufWriter, path::Path}; - -use futures::stream::{self, StreamExt}; -use musixmatch_inofficial::{models::TrackId, Error}; -use rstest::rstest; -use time::macros::datetime; - -#[tokio::test] -async fn from_match() { - let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap(); - - // dbg!(&lyrics); - - assert_eq!(lyrics.lyrics_id, 25947036); - assert!(!lyrics.instrumental); - assert!(!lyrics.explicit); - assert!(lyrics - .lyrics_body - .starts_with("Eyes, in the sky, gazing far into the night\n")); - assert_eq!(lyrics.lyrics_language.unwrap(), "en"); - assert_eq!(lyrics.lyrics_language_description.unwrap(), "English"); - let copyright = lyrics.lyrics_copyright.unwrap(); - assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); - assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC)); -} - -#[rstest] -#[case::trackid(TrackId::TrackId(205688271))] -#[case::commontrack(TrackId::Commontrack(118480583))] -#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] -#[case::isrc(TrackId::Isrc("KRA302000590"))] -#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] -#[tokio::test] -async fn from_id(#[case] track_id: TrackId<'_>) { - let lyrics = new_mxm().track_lyrics(track_id).await.unwrap(); - - // dbg!(&lyrics); - - assert_eq!(lyrics.lyrics_id, 30126001); - assert_eq!(lyrics.lyrics_language.unwrap(), "ko"); - assert_eq!(lyrics.lyrics_language_description.unwrap(), "Korean"); - let copyright = lyrics.lyrics_copyright.unwrap(); - assert!( - copyright.contains("Kenneth Scott Chesak"), - "copyright: {copyright}", - ); - assert!(lyrics.updated_time > datetime!(2022-8-27 0:00 UTC)); -} - -/// This track has no lyrics -#[tokio::test] -async fn instrumental() { - let lyrics = new_mxm() - .matcher_lyrics("drivers license", "Bobby G") - .await - .unwrap(); - - assert_eq!(lyrics.lyrics_id, 25891609); - assert!(lyrics.instrumental); - assert!(!lyrics.explicit); - assert_eq!(lyrics.lyrics_body, ""); - assert_eq!(lyrics.lyrics_language, None); - assert_eq!(lyrics.lyrics_language_description, None); - assert_eq!(lyrics.lyrics_copyright, None); - assert!(lyrics.updated_time > datetime!(2021-6-21 0:00 UTC)); -} - -/// This track does not exist -#[tokio::test] -async fn missing() { - let err = new_mxm() - .track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn")) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn download_testdata() { - let json_path = Path::new("testfiles/lyrics.json"); - if json_path.exists() { - return; - } - - let lyrics = new_mxm() - .track_lyrics(TrackId::Commontrack(18576954)) - .await - .unwrap(); - - let json_file = File::create(json_path).unwrap(); - serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap(); -} - -#[tokio::test] -async fn download_testdata_translation() { - let json_path = Path::new("testfiles/translation.json"); - if json_path.exists() { - return; - } - - let translations = new_mxm() - .track_lyrics_translation(TrackId::Commontrack(18576954), "de") - .await - .unwrap(); - - let json_file = File::create(json_path).unwrap(); - serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap(); -} - -#[tokio::test] -async fn concurrency() { - let mxm = new_mxm(); - - let album = mxm - .album_tracks( - musixmatch_inofficial::models::AlbumId::AlbumId(17118624), - true, - 20, - 1, - ) - .await - .unwrap(); - - let x = stream::iter(album) - .map(|track| { - mxm.track_lyrics(musixmatch_inofficial::models::TrackId::TrackId( - track.track_id, - )) - }) - .buffered(8) - .collect::>() - .await - .into_iter() - .map(Result::unwrap) - .collect::>(); - - dbg!(x); -} diff --git a/tests/subtitle_test.rs b/tests/subtitle_test.rs deleted file mode 100644 index 35a54cf..0000000 --- a/tests/subtitle_test.rs +++ /dev/null @@ -1,132 +0,0 @@ -mod _fixtures; -use _fixtures::*; - -use std::{fs::File, io::BufWriter, path::Path}; - -use musixmatch_inofficial::{ - models::{SubtitleFormat, TrackId}, - Error, -}; -use rstest::rstest; -use time::macros::datetime; - -#[tokio::test] -async fn from_match() { - let subtitle = new_mxm() - .matcher_subtitle( - "Shine", - "Spektrem", - SubtitleFormat::Json, - Some(315.0), - Some(1.0), - ) - .await - .unwrap(); - - // dbg!(&subtitle); - - assert_eq!(subtitle.subtitle_id, 36913312); - assert_eq!(subtitle.subtitle_language.unwrap(), "en"); - assert_eq!(subtitle.subtitle_language_description.unwrap(), "English"); - let copyright = subtitle.lyrics_copyright.unwrap(); - assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); - assert_eq!(subtitle.subtitle_length, 315); - assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC)); -} - -#[rstest] -#[case::trackid(TrackId::TrackId(205688271))] -#[case::commontrack(TrackId::Commontrack(118480583))] -#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] -#[case::isrc(TrackId::Isrc("KRA302000590"))] -#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] -#[tokio::test] -async fn from_id(#[case] track_id: TrackId<'_>) { - let subtitle = new_mxm() - .track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0)) - .await - .unwrap(); - - // dbg!(&subtitle); - - assert_eq!(subtitle.subtitle_id, 36476905); - assert_eq!(subtitle.subtitle_language.unwrap(), "ko"); - assert_eq!(subtitle.subtitle_language_description.unwrap(), "Korean"); - let copyright = subtitle.lyrics_copyright.unwrap(); - assert!( - copyright.contains("Kenneth Scott Chesak"), - "copyright: {copyright}", - ); - assert_eq!(subtitle.subtitle_length, 175); - assert!(subtitle.updated_time > datetime!(2022-8-27 0:00 UTC)); -} - -/// This track has no lyrics -#[tokio::test] -async fn instrumental() { - let err = new_mxm() - .matcher_subtitle( - "drivers license", - "Bobby G", - SubtitleFormat::Json, - Some(246.0), - Some(1.0), - ) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -/// This track has not been synced -#[tokio::test] -async fn unsynced() { - let err = new_mxm() - .track_subtitle( - TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1"), - SubtitleFormat::Json, - Some(213.0), - Some(1.0), - ) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -/// Try to get subtitles with wrong length parameter -#[tokio::test] -async fn wrong_length() { - let err = new_mxm() - .track_subtitle( - TrackId::Commontrack(118480583), - SubtitleFormat::Json, - Some(200.0), - Some(1.0), - ) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn download_testdata() { - let json_path = Path::new("testfiles/subtitles.json"); - if json_path.exists() { - return; - } - - let subtitle = new_mxm() - .track_subtitle( - TrackId::Commontrack(18576954), - SubtitleFormat::Json, - Some(259.0), - Some(1.0), - ) - .await - .unwrap(); - - let json_file = File::create(json_path).unwrap(); - serde_json::to_writer_pretty(BufWriter::new(json_file), &subtitle).unwrap(); -} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..69675e4 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,972 @@ +use rstest::rstest; +use time::macros::{date, datetime}; + +use musixmatch_inofficial::{ + models::{AlbumId, ArtistId, TrackId}, + storage::FileStorage, + Error, Musixmatch, +}; + +#[ctor::ctor] +fn init() { + let _ = dotenvy::dotenv(); + env_logger::init(); + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(new_mxm().login()) + .unwrap(); +} + +pub fn new_mxm() -> Musixmatch { + Musixmatch::new( + &std::env::var("MUSIXMATCH_EMAIL").unwrap(), + &std::env::var("MUSIXMATCH_PASSWORD").unwrap(), + Some(Box::new(FileStorage::default())), + ) +} + +mod album { + use super::*; + + #[rstest] + #[case::id(AlbumId::AlbumId(14248253))] + #[case::musicbrainz(AlbumId::Musicbrainz("6c3cf9d8-88a8-43ed-850b-55813f01e451"))] + #[tokio::test] + async fn by_id(#[case] album_id: AlbumId<'_>) { + let album = new_mxm().album(album_id).await.unwrap(); + + assert_eq!(album.album_id, 14248253); + assert_eq!( + album.album_mbid.unwrap(), + "6c3cf9d8-88a8-43ed-850b-55813f01e451" + ); + assert_eq!(album.album_name, "Gangnam Style (강남스타일)"); + assert!(album.album_rating > 25); + assert_eq!(album.album_track_count, 1); + assert_eq!(album.album_release_date, "2012-01-01"); + assert_eq!(album.album_release_type, "Single"); + assert_eq!(album.artist_id, 410698); + assert_eq!(album.artist_name, "PSY"); + + let first_pri_genre = &album.primary_genres.music_genre_list[0].music_genre; + assert_eq!(first_pri_genre.music_genre_id, 14); + assert_eq!(first_pri_genre.music_genre_parent_id, 34); + assert_eq!(first_pri_genre.music_genre_name, "Pop"); + assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); + assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); + + let first_sec_genre = &album.secondary_genres.music_genre_list[0].music_genre; + assert_eq!(first_sec_genre.music_genre_id, 17); + assert_eq!(first_sec_genre.music_genre_parent_id, 34); + assert_eq!(first_sec_genre.music_genre_name, "Dance"); + assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); + assert_eq!( + first_sec_genre.music_genre_vanity.as_ref().unwrap(), + "Dance" + ); + + assert_eq!( + album.album_copyright.unwrap(), + "© 2012 Schoolboy/Universal Republic Records, a division of UMG Recordings, Inc." + ); + assert_eq!( + album.album_label.unwrap(), + "Silent Records/Universal Republic Records" + ); + assert_eq!(album.album_vanity_id, "410698/Gangnam-Style-Single"); + assert!(album.updated_time > datetime!(2022-6-3 0:00 UTC)); + assert_eq!( + album.album_coverart_100x100.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045.jpg" + ); + assert_eq!( + album.album_coverart_350x350.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_350_350.jpg" + ); + assert_eq!( + album.album_coverart_500x500.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/5/4/0/4/4/5/26544045_500_500.jpg" + ); + } + + #[tokio::test] + async fn by_id_missing() { + let err = new_mxm() + .album(AlbumId::AlbumId(999999999999)) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn artist_albums() { + let albums = new_mxm() + .artist_albums(ArtistId::ArtistId(1039), None, 10, 1) + .await + .unwrap(); + + assert_eq!(albums.len(), 10); + } + + #[tokio::test] + async fn artist_albums_missing() { + let err = new_mxm() + .artist_albums(ArtistId::ArtistId(999999999999), None, 10, 1) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn charts() { + let albums = new_mxm().chart_albums("US", 10, 1).await.unwrap(); + + assert_eq!(albums.len(), 10); + } +} + +mod artist { + use super::*; + + #[rstest] + #[case::id(ArtistId::ArtistId(410698))] + #[case::musicbrainz(ArtistId::Musicbrainz("f99b7d67-4e63-4678-aa66-4c6ac0f7d24a"))] + #[tokio::test] + async fn by_id(#[case] artist_id: ArtistId<'_>) { + let artist = new_mxm().artist(artist_id).await.unwrap(); + + // dbg!(&artist); + + assert_eq!(artist.artist_id, 410698); + assert_eq!( + artist.artist_mbid.unwrap(), + "f99b7d67-4e63-4678-aa66-4c6ac0f7d24a" + ); + assert_eq!(artist.artist_name, "PSY"); + assert!( + artist.artist_name_translation_list.iter().any(|tl| { + tl.artist_name_translation.language == "KO" + && tl.artist_name_translation.translation == "싸이" + }), + "missing Korean translation in: {:?}", + artist.artist_name_translation_list + ); + assert_eq!(artist.artist_country.unwrap(), "KR"); + assert!(artist.artist_rating > 50); + let first_genre = &artist.primary_genres.music_genre_list[0].music_genre; + assert_eq!(first_genre.music_genre_id, 14); + assert_eq!(first_genre.music_genre_parent_id, 34); + assert_eq!(first_genre.music_genre_name, "Pop"); + assert_eq!(first_genre.music_genre_name_extended, "Pop"); + assert_eq!(first_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); + assert_eq!( + artist.artist_twitter_url.unwrap(), + "https://twitter.com/psy_oppa" + ); + assert_eq!( + artist.artist_facebook_url.unwrap(), + "https://www.facebook.com/officialpsy" + ); + assert_eq!(artist.artist_vanity_id, "410698"); + assert!(artist.updated_time > datetime!(2016-6-30 0:00 UTC)); + assert_eq!(artist.begin_date_year, "1977"); + assert_eq!(artist.begin_date, "1977-12-31"); + assert_eq!(artist.end_date_year, ""); + assert_eq!(artist.end_date, "0000-00-00"); + } + + #[tokio::test] + async fn by_id_missing() { + let err = new_mxm() + .artist(ArtistId::ArtistId(999999999999)) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn related() { + let artists = new_mxm() + .artist_related(ArtistId::ArtistId(26485840), 10, 1) + .await + .unwrap(); + + assert_eq!(artists.len(), 10); + } + + #[tokio::test] + async fn related_missing() { + let err = new_mxm() + .artist_related(ArtistId::ArtistId(999999999999), 10, 1) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn search() { + let artists = new_mxm().artist_search("psy", 5, 1).await.unwrap(); + + assert_eq!(artists.len(), 5); + + let artist = &artists[0]; + assert_eq!(artist.artist_id, 410698); + assert_eq!(artist.artist_name, "PSY"); + } + + #[tokio::test] + async fn search_empty() { + let artists = new_mxm() + .artist_search( + "Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz", + 5, + 1, + ) + .await + .unwrap(); + + assert_eq!(artists.len(), 0); + } + + #[tokio::test] + async fn charts() { + let artists = new_mxm().chart_artists("US", 10, 1).await.unwrap(); + + assert_eq!(artists.len(), 10); + } + + #[tokio::test] + async fn charts_no_country() { + let artists = new_mxm().chart_artists("XY", 10, 1).await.unwrap(); + + assert_eq!(artists.len(), 10); + } +} + +mod track { + use super::*; + use musixmatch_inofficial::models::{ChartName, SortOrder}; + + #[rstest] + #[case::no_translation(false, false)] + #[case::translation_2c(true, false)] + #[case::translation_3c(true, true)] + #[tokio::test] + async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) { + let track = new_mxm() + .matcher_track( + "Poker Face", + "Lady Gaga", + "The Fame", + translation_status, + lang_3c, + ) + .await + .unwrap(); + + // dbg!(&track); + + assert_eq!(track.track_id, 15476784); + assert_eq!( + track.track_mbid.unwrap(), + "080975b0-39b1-493c-ae64-5cb3292409bb" + ); + assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); + assert!( + track.commontrack_isrcs[0] + .iter() + .any(|isrc| isrc == "USUM70824409"), + "commontrack_isrcs: {:?}", + &track.commontrack_isrcs[0], + ); + assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); + assert!( + track + .commontrack_spotify_ids + .iter() + .any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"), + "commontrack_spotify_ids: {:?}", + track.commontrack_spotify_ids, + ); + assert_eq!(track.track_name, "Poker Face"); + assert!(track.track_rating > 50); + assert_eq!(track.commontrack_id, 47672612); + assert!(!track.instrumental); + assert!(track.explicit); + assert!(track.has_subtitles); + assert!(track.has_richsync); + assert!(track.has_track_structure); + assert!(track.num_favourite > 50); + assert_eq!(track.lyrics_id.unwrap(), 30678771); + assert_eq!(track.subtitle_id.unwrap(), 36450705); + assert_eq!(track.album_id, 13810402); + assert_eq!(track.album_name, "The Fame"); + assert_eq!(track.artist_id, 378462); + assert_eq!( + track.artist_mbid.unwrap(), + "650e7db6-b795-4eb5-a702-5ea2fc46c848" + ); + assert_eq!(track.artist_name, "Lady Gaga"); + assert_eq!( + track.album_coverart_100x100.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" + ); + assert_eq!( + track.album_coverart_350x350.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" + ); + assert_eq!( + track.album_coverart_500x500.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" + ); + assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); + let first_release = track.first_release_date.unwrap(); + assert_eq!(first_release.date(), date!(2008 - 1 - 1)); + assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC)); + + let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre; + assert_eq!(first_pri_genre.music_genre_id, 14); + assert_eq!(first_pri_genre.music_genre_parent_id, 34); + assert_eq!(first_pri_genre.music_genre_name, "Pop"); + assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); + assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); + + let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre; + assert_eq!(first_sec_genre.music_genre_id, 17); + assert_eq!(first_sec_genre.music_genre_parent_id, 34); + assert_eq!(first_sec_genre.music_genre_name, "Dance"); + assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); + assert_eq!( + first_sec_genre.music_genre_vanity.as_ref().unwrap(), + "Dance" + ); + + if translation_status { + assert!( + track.track_lyrics_translation_status.iter().all(|tl| { + (if lang_3c { + tl.from == "eng" + } else { + tl.from == "en" + }) && tl.perc >= 0.0 + && tl.perc <= 1.0 + }), + "translation: {:?}", + track.track_lyrics_translation_status + ); + } else { + assert!(track.track_lyrics_translation_status.is_empty()) + } + } + + #[rstest] + #[case::trackid(TrackId::TrackId(205688271))] + #[case::commontrack(TrackId::Commontrack(118480583))] + #[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] + #[case::isrc(TrackId::Isrc("KRA302000590"))] + #[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] + #[tokio::test] + async fn from_id(#[case] track_id: TrackId<'_>) { + let track = new_mxm().track(track_id, true, false).await.unwrap(); + + // dbg!(&track); + + assert_eq!(track.track_id, 205688271); + assert_eq!(track.track_isrc.unwrap(), "KRA302000590"); + assert_eq!(track.track_spotify_id.unwrap(), "1t2qYCAjUAoGfeFeoBlK51"); + assert_eq!(track.track_name, "Black Mamba"); + assert!(track.track_rating > 50); + assert_eq!(track.track_length, 175); + assert!(!track.explicit); + assert!(track.has_lyrics); + assert!(track.has_subtitles); + assert!(track.has_richsync); + assert!(track.num_favourite > 200); + assert_eq!(track.lyrics_id.unwrap(), 30126001); + assert_eq!(track.subtitle_id.unwrap(), 36476905); + assert_eq!(track.album_id, 41035954); + assert_eq!(track.album_name, "Black Mamba - Single"); + assert_eq!(track.artist_id, 46970441); + assert_eq!(track.artist_name, "aespa"); + assert_eq!( + track.album_coverart_100x100.unwrap(), + "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg" + ); + assert_eq!( + track.album_coverart_350x350.unwrap(), + "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg" + ); + assert_eq!( + track.album_coverart_500x500.unwrap(), + "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg" + ); + assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba"); + + let release_date = track.first_release_date.unwrap(); + assert_eq!(release_date.date(), date!(2020 - 11 - 17)); + assert!(track.updated_time > datetime!(2022-8-27 0:00 UTC)); + + let first_tstatus = &track.track_lyrics_translation_status[0]; + assert_eq!(first_tstatus.from, "ko"); + assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0); + } + + #[rstest] + #[case::no_translation(false, false)] + #[case::translation_2c(true, false)] + #[case::translation_3c(true, true)] + #[tokio::test] + async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) { + let track = new_mxm() + .track(TrackId::TrackId(15476784), translation_status, lang_3c) + .await + .unwrap(); + + // dbg!(&track); + + assert_eq!(track.track_id, 15476784); + assert_eq!( + track.track_mbid.unwrap(), + "080975b0-39b1-493c-ae64-5cb3292409bb" + ); + assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); + assert!( + track.commontrack_isrcs[0] + .iter() + .any(|isrc| isrc == "USUM70824409"), + "commontrack_isrcs: {:?}", + &track.commontrack_isrcs[0], + ); + assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); + assert!( + track + .commontrack_spotify_ids + .iter() + .any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"), + "commontrack_spotify_ids: {:?}", + track.commontrack_spotify_ids, + ); + assert_eq!(track.track_name, "Poker Face"); + assert!(track.track_rating > 50); + assert_eq!(track.commontrack_id, 47672612); + assert!(!track.instrumental); + assert!(track.explicit); + assert!(track.has_subtitles); + assert!(track.has_richsync); + assert!(track.has_track_structure); + assert!(track.num_favourite > 50); + assert_eq!(track.lyrics_id.unwrap(), 30678771); + assert_eq!(track.subtitle_id.unwrap(), 36450705); + assert_eq!(track.album_id, 13810402); + assert_eq!(track.album_name, "The Fame"); + assert_eq!(track.artist_id, 378462); + assert_eq!( + track.artist_mbid.unwrap(), + "650e7db6-b795-4eb5-a702-5ea2fc46c848" + ); + assert_eq!(track.artist_name, "Lady Gaga"); + assert_eq!( + track.album_coverart_100x100.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" + ); + assert_eq!( + track.album_coverart_350x350.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" + ); + assert_eq!( + track.album_coverart_500x500.unwrap(), + "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" + ); + assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); + let first_release = track.first_release_date.unwrap(); + assert_eq!(first_release.date(), date!(2008 - 1 - 1)); + assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC)); + + let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre; + assert_eq!(first_pri_genre.music_genre_id, 14); + assert_eq!(first_pri_genre.music_genre_parent_id, 34); + assert_eq!(first_pri_genre.music_genre_name, "Pop"); + assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); + assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); + + let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre; + assert_eq!(first_sec_genre.music_genre_id, 17); + assert_eq!(first_sec_genre.music_genre_parent_id, 34); + assert_eq!(first_sec_genre.music_genre_name, "Dance"); + assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); + assert_eq!( + first_sec_genre.music_genre_vanity.as_ref().unwrap(), + "Dance" + ); + + if translation_status { + assert!( + track.track_lyrics_translation_status.iter().all(|tl| { + (if lang_3c { + tl.from == "eng" + } else { + tl.from == "en" + }) && tl.perc >= 0.0 + && tl.perc <= 1.0 + }), + "translation: {:?}", + track.track_lyrics_translation_status + ); + } else { + assert!(track.track_lyrics_translation_status.is_empty()) + } + } + + #[tokio::test] + async fn from_id_missing() { + let err = new_mxm() + .track(TrackId::TrackId(999999999999), false, false) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn album_tracks() { + let tracks = new_mxm() + .album_tracks(AlbumId::AlbumId(17118624), true, 20, 1) + .await + .unwrap(); + + // dbg!(&tracks); + + let track_names = tracks + .iter() + .map(|t| t.track_name.to_owned()) + .collect::>(); + + assert_eq!( + track_names, + vec![ + "Gäa", + "Vergiss mein nicht", + "Orome", + "Falke flieg", + "Minne", + "Das Lied der Ahnen", + "Hörst du den Wind", + "Nan Úye", + "Faolan", + "Hymne der Nacht", + "Avalon", + "Tolo Nan", + "Oonagh", + ] + ); + + tracks.iter().for_each(|t| { + assert!(t.has_lyrics); + assert!(t.has_subtitles); + }); + } + + #[tokio::test] + async fn album_missing() { + let err = new_mxm() + .album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[rstest] + #[case::top(ChartName::Top)] + #[case::hot(ChartName::Hot)] + #[tokio::test] + async fn charts(#[case] chart_name: ChartName) { + let tracks = new_mxm() + .chart_tracks("US", chart_name, true, 20, 1) + .await + .unwrap(); + + assert_eq!(tracks.len(), 20); + } + + #[tokio::test] + async fn search() { + let tracks = new_mxm() + .track_search() + .q_artist("Lena") + .q_track("Satellite") + .s_track_rating(SortOrder::Desc) + .send(1, 0) + .await + .unwrap(); + + // dbg!(&tracks); + + assert_eq!(tracks.len(), 1); + + let track = &tracks[0]; + assert_eq!(track.commontrack_id, 12426476); + assert_eq!(track.track_name, "Satellite"); + assert_eq!(track.artist_name, "Lena"); + } + + #[tokio::test] + async fn search_lyrics() { + let tracks = new_mxm() + .track_search() + .q_lyrics("the whole world stops and stares for a while") + .s_track_rating(SortOrder::Desc) + .send(10, 1) + .await + .unwrap(); + + assert_eq!(tracks.len(), 10); + + let track = &tracks[0]; + assert_eq!(track.track_name, "Just the Way You Are"); + assert_eq!(track.artist_name, "Bruno Mars"); + } + + #[tokio::test] + async fn search_empty() { + let artists = new_mxm() + .track_search() + .q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz") + .send(10, 1) + .await + .unwrap(); + + assert_eq!(artists.len(), 0); + } + + #[tokio::test] + async fn genres() { + let genres = new_mxm().genres().await.unwrap(); + assert!(genres.len() > 360); + } + + #[tokio::test] + async fn snippet() { + let snippet = new_mxm() + .track_snippet(TrackId::Commontrack(8874280)) + .await + .unwrap(); + + assert_eq!(snippet.snippet_id, 23036767); + assert_eq!(snippet.snippet_language.unwrap(), "en"); + assert!(!snippet.instrumental); + assert!(snippet.updated_time > datetime!(2022-8-29 0:00 UTC)); + assert_eq!( + snippet.snippet_body, + "There's not a thing that I would change" + ); + } +} + +mod lyrics { + use futures::stream::{self, StreamExt}; + use std::{fs::File, io::BufWriter, path::Path}; + + use super::*; + + #[tokio::test] + async fn from_match() { + let lyrics = new_mxm().matcher_lyrics("Shine", "Spektrem").await.unwrap(); + + // dbg!(&lyrics); + + assert_eq!(lyrics.lyrics_id, 25947036); + assert!(!lyrics.instrumental); + assert!(!lyrics.explicit); + assert!(lyrics + .lyrics_body + .starts_with("Eyes, in the sky, gazing far into the night\n")); + assert_eq!(lyrics.lyrics_language.unwrap(), "en"); + assert_eq!(lyrics.lyrics_language_description.unwrap(), "English"); + let copyright = lyrics.lyrics_copyright.unwrap(); + assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); + assert!(lyrics.updated_time > datetime!(2021-6-3 0:00 UTC)); + } + + #[rstest] + #[case::trackid(TrackId::TrackId(205688271))] + #[case::commontrack(TrackId::Commontrack(118480583))] + #[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] + #[case::isrc(TrackId::Isrc("KRA302000590"))] + #[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] + #[tokio::test] + async fn from_id(#[case] track_id: TrackId<'_>) { + let lyrics = new_mxm().track_lyrics(track_id).await.unwrap(); + + // dbg!(&lyrics); + + assert_eq!(lyrics.lyrics_id, 30126001); + assert_eq!(lyrics.lyrics_language.unwrap(), "ko"); + assert_eq!(lyrics.lyrics_language_description.unwrap(), "Korean"); + let copyright = lyrics.lyrics_copyright.unwrap(); + assert!( + copyright.contains("Kenneth Scott Chesak"), + "copyright: {copyright}", + ); + assert!(lyrics.updated_time > datetime!(2022-8-27 0:00 UTC)); + } + + /// This track has no lyrics + #[tokio::test] + async fn instrumental() { + let lyrics = new_mxm() + .matcher_lyrics("drivers license", "Bobby G") + .await + .unwrap(); + + assert_eq!(lyrics.lyrics_id, 25891609); + assert!(lyrics.instrumental); + assert!(!lyrics.explicit); + assert_eq!(lyrics.lyrics_body, ""); + assert_eq!(lyrics.lyrics_language, None); + assert_eq!(lyrics.lyrics_language_description, None); + assert_eq!(lyrics.lyrics_copyright, None); + assert!(lyrics.updated_time > datetime!(2021-6-21 0:00 UTC)); + } + + /// This track does not exist + #[tokio::test] + async fn missing() { + let err = new_mxm() + .track_lyrics(TrackId::Spotify("674JwwTP7xCje81T0DRrLn")) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn download_testdata() { + let json_path = Path::new("testfiles/lyrics.json"); + if json_path.exists() { + return; + } + + let lyrics = new_mxm() + .track_lyrics(TrackId::Commontrack(18576954)) + .await + .unwrap(); + + let json_file = File::create(json_path).unwrap(); + serde_json::to_writer_pretty(BufWriter::new(json_file), &lyrics).unwrap(); + } + + #[tokio::test] + async fn download_testdata_translation() { + let json_path = Path::new("testfiles/translation.json"); + if json_path.exists() { + return; + } + + let translations = new_mxm() + .track_lyrics_translation(TrackId::Commontrack(18576954), "de") + .await + .unwrap(); + + let json_file = File::create(json_path).unwrap(); + serde_json::to_writer_pretty(BufWriter::new(json_file), &translations).unwrap(); + } + + #[tokio::test] + async fn concurrency() { + let mxm = new_mxm(); + + let album = mxm + .album_tracks( + musixmatch_inofficial::models::AlbumId::AlbumId(17118624), + true, + 20, + 1, + ) + .await + .unwrap(); + + let x = stream::iter(album) + .map(|track| { + mxm.track_lyrics(musixmatch_inofficial::models::TrackId::TrackId( + track.track_id, + )) + }) + .buffered(8) + .collect::>() + .await + .into_iter() + .map(Result::unwrap) + .collect::>(); + + dbg!(x); + } +} + +mod subtitles { + use std::{fs::File, io::BufWriter, path::Path}; + + use super::*; + use musixmatch_inofficial::models::SubtitleFormat; + + #[tokio::test] + async fn from_match() { + let subtitle = new_mxm() + .matcher_subtitle( + "Shine", + "Spektrem", + SubtitleFormat::Json, + Some(315.0), + Some(1.0), + ) + .await + .unwrap(); + + // dbg!(&subtitle); + + assert_eq!(subtitle.subtitle_id, 36913312); + assert_eq!(subtitle.subtitle_language.unwrap(), "en"); + assert_eq!(subtitle.subtitle_language_description.unwrap(), "English"); + let copyright = subtitle.lyrics_copyright.unwrap(); + assert!(copyright.contains("Kim Jeffeson"), "copyright: {copyright}",); + assert_eq!(subtitle.subtitle_length, 315); + assert!(subtitle.updated_time > datetime!(2021-6-30 0:00 UTC)); + } + + #[rstest] + #[case::trackid(TrackId::TrackId(205688271))] + #[case::commontrack(TrackId::Commontrack(118480583))] + #[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] + #[case::isrc(TrackId::Isrc("KRA302000590"))] + #[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] + #[tokio::test] + async fn from_id(#[case] track_id: TrackId<'_>) { + let subtitle = new_mxm() + .track_subtitle(track_id, SubtitleFormat::Json, Some(175.0), Some(1.0)) + .await + .unwrap(); + + // dbg!(&subtitle); + + assert_eq!(subtitle.subtitle_id, 36476905); + assert_eq!(subtitle.subtitle_language.unwrap(), "ko"); + assert_eq!(subtitle.subtitle_language_description.unwrap(), "Korean"); + let copyright = subtitle.lyrics_copyright.unwrap(); + assert!( + copyright.contains("Kenneth Scott Chesak"), + "copyright: {copyright}", + ); + assert_eq!(subtitle.subtitle_length, 175); + assert!(subtitle.updated_time > datetime!(2022-8-27 0:00 UTC)); + } + + /// This track has no lyrics + #[tokio::test] + async fn instrumental() { + let err = new_mxm() + .matcher_subtitle( + "drivers license", + "Bobby G", + SubtitleFormat::Json, + Some(246.0), + Some(1.0), + ) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + /// This track has not been synced + #[tokio::test] + async fn unsynced() { + let err = new_mxm() + .track_subtitle( + TrackId::Spotify("6oaWIABGL7eeiMILEDyGX1"), + SubtitleFormat::Json, + Some(213.0), + Some(1.0), + ) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + /// Try to get subtitles with wrong length parameter + #[tokio::test] + async fn wrong_length() { + let err = new_mxm() + .track_subtitle( + TrackId::Commontrack(118480583), + SubtitleFormat::Json, + Some(200.0), + Some(1.0), + ) + .await + .unwrap_err(); + + assert!(matches!(err, Error::NotFound)); + } + + #[tokio::test] + async fn download_testdata() { + let json_path = Path::new("testfiles/subtitles.json"); + if json_path.exists() { + return; + } + + let subtitle = new_mxm() + .track_subtitle( + TrackId::Commontrack(18576954), + SubtitleFormat::Json, + Some(259.0), + Some(1.0), + ) + .await + .unwrap(); + + let json_file = File::create(json_path).unwrap(); + serde_json::to_writer_pretty(BufWriter::new(json_file), &subtitle).unwrap(); + } +} + +mod translation { + use std::{fs::File, io::BufReader, path::Path}; + + use musixmatch_inofficial::models::{Lyrics, Subtitle, TranslationList, TranslationMap}; + + #[test] + fn translation_test() { + let lyrics_path = Path::new("testfiles/lyrics.json"); + let subtitles_path = Path::new("testfiles/subtitles.json"); + let translation_path = Path::new("testfiles/translation.json"); + + let lyrics: Lyrics = + serde_json::from_reader(BufReader::new(File::open(lyrics_path).unwrap())).unwrap(); + let subtitle: Subtitle = + serde_json::from_reader(BufReader::new(File::open(subtitles_path).unwrap())).unwrap(); + let translations: TranslationList = + serde_json::from_reader(BufReader::new(File::open(translation_path).unwrap())).unwrap(); + + let t_map = TranslationMap::from(translations); + + let lyrics_trans = t_map.translate_lyrics(&lyrics.lyrics_body); + let expected_lyrics = std::fs::read_to_string("testfiles/translated_lyrics.txt").unwrap(); + assert_eq!(lyrics_trans.trim(), expected_lyrics.trim()); + + let subtitles_trans = t_map.translate_subtitles(&subtitle.to_lines().unwrap()); + + let expected_lrc = std::fs::read_to_string("testfiles/translated_subtitles.lrc").unwrap(); + let expected_ttml = std::fs::read_to_string("testfiles/translated_subtitles.xml").unwrap(); + + assert_eq!(subtitles_trans.to_lrc().trim(), expected_lrc.trim()); + assert_eq!(subtitles_trans.to_ttml().trim(), expected_ttml.trim()); + } +} diff --git a/tests/track_test.rs b/tests/track_test.rs deleted file mode 100644 index e9b0676..0000000 --- a/tests/track_test.rs +++ /dev/null @@ -1,424 +0,0 @@ -mod _fixtures; -use _fixtures::*; - -use musixmatch_inofficial::{ - models::{AlbumId, ChartName, SortOrder, TrackId}, - Error, -}; -use rstest::rstest; -use time::macros::{date, datetime}; - -#[rstest] -#[case::no_translation(false, false)] -#[case::translation_2c(true, false)] -#[case::translation_3c(true, true)] -#[tokio::test] -async fn from_match(#[case] translation_status: bool, #[case] lang_3c: bool) { - let track = new_mxm() - .matcher_track( - "Poker Face", - "Lady Gaga", - "The Fame", - translation_status, - lang_3c, - ) - .await - .unwrap(); - - // dbg!(&track); - - assert_eq!(track.track_id, 15476784); - assert_eq!( - track.track_mbid.unwrap(), - "080975b0-39b1-493c-ae64-5cb3292409bb" - ); - assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); - assert!( - track.commontrack_isrcs[0] - .iter() - .any(|isrc| isrc == "USUM70824409"), - "commontrack_isrcs: {:?}", - &track.commontrack_isrcs[0], - ); - assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); - assert!( - track - .commontrack_spotify_ids - .iter() - .any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"), - "commontrack_spotify_ids: {:?}", - track.commontrack_spotify_ids, - ); - assert_eq!(track.track_name, "Poker Face"); - assert!(track.track_rating > 50); - assert_eq!(track.commontrack_id, 47672612); - assert!(!track.instrumental); - assert!(track.explicit); - assert!(track.has_subtitles); - assert!(track.has_richsync); - assert!(track.has_track_structure); - assert!(track.num_favourite > 50); - assert_eq!(track.lyrics_id.unwrap(), 30678771); - assert_eq!(track.subtitle_id.unwrap(), 36450705); - assert_eq!(track.album_id, 13810402); - assert_eq!(track.album_name, "The Fame"); - assert_eq!(track.artist_id, 378462); - assert_eq!( - track.artist_mbid.unwrap(), - "650e7db6-b795-4eb5-a702-5ea2fc46c848" - ); - assert_eq!(track.artist_name, "Lady Gaga"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" - ); - assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); - let first_release = track.first_release_date.unwrap(); - assert_eq!(first_release.date(), date!(2008 - 1 - 1)); - assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC)); - - let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre; - assert_eq!(first_pri_genre.music_genre_id, 14); - assert_eq!(first_pri_genre.music_genre_parent_id, 34); - assert_eq!(first_pri_genre.music_genre_name, "Pop"); - assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); - assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); - - let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre; - assert_eq!(first_sec_genre.music_genre_id, 17); - assert_eq!(first_sec_genre.music_genre_parent_id, 34); - assert_eq!(first_sec_genre.music_genre_name, "Dance"); - assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); - assert_eq!( - first_sec_genre.music_genre_vanity.as_ref().unwrap(), - "Dance" - ); - - if translation_status { - assert!( - track.track_lyrics_translation_status.iter().all(|tl| { - (if lang_3c { - tl.from == "eng" - } else { - tl.from == "en" - }) && tl.perc >= 0.0 - && tl.perc <= 1.0 - }), - "translation: {:?}", - track.track_lyrics_translation_status - ); - } else { - assert!(track.track_lyrics_translation_status.is_empty()) - } -} - -#[rstest] -#[case::trackid(TrackId::TrackId(205688271))] -#[case::commontrack(TrackId::Commontrack(118480583))] -#[case::vanity(TrackId::CommontrackVanity("aespa/Black-Mamba"))] -#[case::isrc(TrackId::Isrc("KRA302000590"))] -#[case::spotify(TrackId::Spotify("1t2qYCAjUAoGfeFeoBlK51"))] -#[tokio::test] -async fn from_id(#[case] track_id: TrackId<'_>) { - let track = new_mxm().track(track_id, true, false).await.unwrap(); - - // dbg!(&track); - - assert_eq!(track.track_id, 205688271); - assert_eq!(track.track_isrc.unwrap(), "KRA302000590"); - assert_eq!(track.track_spotify_id.unwrap(), "1t2qYCAjUAoGfeFeoBlK51"); - assert_eq!(track.track_name, "Black Mamba"); - assert!(track.track_rating > 50); - assert_eq!(track.track_length, 175); - assert!(!track.explicit); - assert!(track.has_lyrics); - assert!(track.has_subtitles); - assert!(track.has_richsync); - assert!(track.num_favourite > 200); - assert_eq!(track.lyrics_id.unwrap(), 30126001); - assert_eq!(track.subtitle_id.unwrap(), 36476905); - assert_eq!(track.album_id, 41035954); - assert_eq!(track.album_name, "Black Mamba - Single"); - assert_eq!(track.artist_id, 46970441); - assert_eq!(track.artist_name, "aespa"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums5/2/7/7/6/5/1/52156772_500_500.jpg" - ); - assert_eq!(track.commontrack_vanity_id, "aespa/Black-Mamba"); - - let release_date = track.first_release_date.unwrap(); - assert_eq!(release_date.date(), date!(2020 - 11 - 17)); - assert!(track.updated_time > datetime!(2022-8-27 0:00 UTC)); - - let first_tstatus = &track.track_lyrics_translation_status[0]; - assert_eq!(first_tstatus.from, "ko"); - assert!(first_tstatus.perc >= 0.0 && first_tstatus.perc <= 1.0); -} - -#[rstest] -#[case::no_translation(false, false)] -#[case::translation_2c(true, false)] -#[case::translation_3c(true, true)] -#[tokio::test] -async fn from_id_translations(#[case] translation_status: bool, #[case] lang_3c: bool) { - let track = new_mxm() - .track(TrackId::TrackId(15476784), translation_status, lang_3c) - .await - .unwrap(); - - // dbg!(&track); - - assert_eq!(track.track_id, 15476784); - assert_eq!( - track.track_mbid.unwrap(), - "080975b0-39b1-493c-ae64-5cb3292409bb" - ); - assert_eq!(track.track_isrc.unwrap(), "USUM70824409"); - assert!( - track.commontrack_isrcs[0] - .iter() - .any(|isrc| isrc == "USUM70824409"), - "commontrack_isrcs: {:?}", - &track.commontrack_isrcs[0], - ); - assert_eq!(track.track_spotify_id.unwrap(), "5R8dQOPq8haW94K7mgERlO"); - assert!( - track - .commontrack_spotify_ids - .iter() - .any(|spid| spid == "1QV6tiMFM6fSOKOGLMHYYg"), - "commontrack_spotify_ids: {:?}", - track.commontrack_spotify_ids, - ); - assert_eq!(track.track_name, "Poker Face"); - assert!(track.track_rating > 50); - assert_eq!(track.commontrack_id, 47672612); - assert!(!track.instrumental); - assert!(track.explicit); - assert!(track.has_subtitles); - assert!(track.has_richsync); - assert!(track.has_track_structure); - assert!(track.num_favourite > 50); - assert_eq!(track.lyrics_id.unwrap(), 30678771); - assert_eq!(track.subtitle_id.unwrap(), 36450705); - assert_eq!(track.album_id, 13810402); - assert_eq!(track.album_name, "The Fame"); - assert_eq!(track.artist_id, 378462); - assert_eq!( - track.artist_mbid.unwrap(), - "650e7db6-b795-4eb5-a702-5ea2fc46c848" - ); - assert_eq!(track.artist_name, "Lady Gaga"); - assert_eq!( - track.album_coverart_100x100.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636.jpg" - ); - assert_eq!( - track.album_coverart_350x350.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_350_350.jpg" - ); - assert_eq!( - track.album_coverart_500x500.unwrap(), - "https://s.mxmcdn.net/images-storage/albums/6/3/6/9/1/3/26319636_500_500.jpg" - ); - assert_eq!(track.commontrack_vanity_id, "Lady-Gaga/poker-face-1"); - let first_release = track.first_release_date.unwrap(); - assert_eq!(first_release.date(), date!(2008 - 1 - 1)); - assert!(track.updated_time > datetime!(2023-1-17 0:00 UTC)); - - let first_pri_genre = &track.primary_genres.music_genre_list[0].music_genre; - assert_eq!(first_pri_genre.music_genre_id, 14); - assert_eq!(first_pri_genre.music_genre_parent_id, 34); - assert_eq!(first_pri_genre.music_genre_name, "Pop"); - assert_eq!(first_pri_genre.music_genre_name_extended, "Pop"); - assert_eq!(first_pri_genre.music_genre_vanity.as_ref().unwrap(), "Pop"); - - let first_sec_genre = &track.secondary_genres.music_genre_list[0].music_genre; - assert_eq!(first_sec_genre.music_genre_id, 17); - assert_eq!(first_sec_genre.music_genre_parent_id, 34); - assert_eq!(first_sec_genre.music_genre_name, "Dance"); - assert_eq!(first_sec_genre.music_genre_name_extended, "Dance"); - assert_eq!( - first_sec_genre.music_genre_vanity.as_ref().unwrap(), - "Dance" - ); - - if translation_status { - assert!( - track.track_lyrics_translation_status.iter().all(|tl| { - (if lang_3c { - tl.from == "eng" - } else { - tl.from == "en" - }) && tl.perc >= 0.0 - && tl.perc <= 1.0 - }), - "translation: {:?}", - track.track_lyrics_translation_status - ); - } else { - assert!(track.track_lyrics_translation_status.is_empty()) - } -} - -#[tokio::test] -async fn from_id_missing() { - let err = new_mxm() - .track(TrackId::TrackId(999999999999), false, false) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[tokio::test] -async fn album_tracks() { - let tracks = new_mxm() - .album_tracks(AlbumId::AlbumId(17118624), true, 20, 1) - .await - .unwrap(); - - // dbg!(&tracks); - - let track_names = tracks - .iter() - .map(|t| t.track_name.to_owned()) - .collect::>(); - - assert_eq!( - track_names, - vec![ - "Gäa", - "Vergiss mein nicht", - "Orome", - "Falke flieg", - "Minne", - "Das Lied der Ahnen", - "Hörst du den Wind", - "Nan Úye", - "Faolan", - "Hymne der Nacht", - "Avalon", - "Tolo Nan", - "Oonagh", - ] - ); - - tracks.iter().for_each(|t| { - assert!(t.has_lyrics); - assert!(t.has_subtitles); - }); -} - -#[tokio::test] -async fn album_missing() { - let err = new_mxm() - .album_tracks(AlbumId::AlbumId(999999999999), false, 20, 1) - .await - .unwrap_err(); - - assert!(matches!(err, Error::NotFound)); -} - -#[rstest] -#[case::top(ChartName::Top)] -#[case::hot(ChartName::Hot)] -#[tokio::test] -async fn charts(#[case] chart_name: ChartName) { - let tracks = new_mxm() - .chart_tracks("US", chart_name, true, 20, 1) - .await - .unwrap(); - - assert_eq!(tracks.len(), 20); -} - -#[tokio::test] -async fn search() { - let tracks = new_mxm() - .track_search() - .q_artist("Lena") - .q_track("Satellite") - .s_track_rating(SortOrder::Desc) - .send(1, 0) - .await - .unwrap(); - - // dbg!(&tracks); - - assert_eq!(tracks.len(), 1); - - let track = &tracks[0]; - assert_eq!(track.commontrack_id, 12426476); - assert_eq!(track.track_name, "Satellite"); - assert_eq!(track.artist_name, "Lena"); -} - -#[tokio::test] -async fn search_lyrics() { - let tracks = new_mxm() - .track_search() - .q_lyrics("the whole world stops and stares for a while") - .s_track_rating(SortOrder::Desc) - .send(10, 1) - .await - .unwrap(); - - assert_eq!(tracks.len(), 10); - - let track = &tracks[0]; - assert_eq!(track.track_name, "Just the Way You Are"); - assert_eq!(track.artist_name, "Bruno Mars"); -} - -#[tokio::test] -async fn search_empty() { - let artists = new_mxm() - .track_search() - .q("Rindfleischettikettierungsüberwachungsaufgabenübertragungsgesetz") - .send(10, 1) - .await - .unwrap(); - - assert_eq!(artists.len(), 0); -} - -#[tokio::test] -async fn genres() { - let genres = new_mxm().genres().await.unwrap(); - assert!(genres.len() > 360); -} - -#[tokio::test] -async fn snippet() { - let snippet = new_mxm() - .track_snippet(TrackId::Commontrack(8874280)) - .await - .unwrap(); - - assert_eq!(snippet.snippet_id, 23036767); - assert_eq!(snippet.snippet_language.unwrap(), "en"); - assert!(!snippet.instrumental); - assert!(snippet.updated_time > datetime!(2022-8-29 0:00 UTC)); - assert_eq!( - snippet.snippet_body, - "There's not a thing that I would change" - ); -} diff --git a/tests/translation_test.rs b/tests/translation_test.rs deleted file mode 100644 index 2649dfd..0000000 --- a/tests/translation_test.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::{fs::File, io::BufReader, path::Path}; - -use musixmatch_inofficial::models::{Lyrics, Subtitle, TranslationList, TranslationMap}; - -#[test] -fn translation_test() { - let lyrics_path = Path::new("testfiles/lyrics.json"); - let subtitles_path = Path::new("testfiles/subtitles.json"); - let translation_path = Path::new("testfiles/translation.json"); - - let lyrics: Lyrics = - serde_json::from_reader(BufReader::new(File::open(lyrics_path).unwrap())).unwrap(); - let subtitle: Subtitle = - serde_json::from_reader(BufReader::new(File::open(subtitles_path).unwrap())).unwrap(); - let translations: TranslationList = - serde_json::from_reader(BufReader::new(File::open(translation_path).unwrap())).unwrap(); - - let t_map = TranslationMap::from(translations); - - let lyrics_trans = t_map.translate_lyrics(&lyrics.lyrics_body); - let expected_lyrics = std::fs::read_to_string("testfiles/translated_lyrics.txt").unwrap(); - assert_eq!(lyrics_trans.trim(), expected_lyrics.trim()); - - let subtitles_trans = t_map.translate_subtitles(&subtitle.to_lines().unwrap()); - - let expected_lrc = std::fs::read_to_string("testfiles/translated_subtitles.lrc").unwrap(); - let expected_ttml = std::fs::read_to_string("testfiles/translated_subtitles.xml").unwrap(); - - assert_eq!(subtitles_trans.to_lrc().trim(), expected_lrc.trim()); - assert_eq!(subtitles_trans.to_ttml().trim(), expected_ttml.trim()); -} From df69f4160aabb4256ae4d94a1387c31718ee50f8 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Fri, 21 Apr 2023 17:58:37 +0200 Subject: [PATCH 3/3] fix!: use numeric and date types for API model --- src/api_model.rs | 217 +++++++++++++++++++++++++++++++++++++++++-- src/models/album.rs | 33 ++++--- src/models/artist.rs | 33 +++---- src/models/mod.rs | 1 + src/models/track.rs | 6 +- tests/tests.rs | 22 +++-- 6 files changed, 257 insertions(+), 55 deletions(-) diff --git a/src/api_model.rs b/src/api_model.rs index ca42287..378daad 100644 --- a/src/api_model.rs +++ b/src/api_model.rs @@ -1,3 +1,5 @@ +use std::{marker::PhantomData, str::FromStr}; + use serde::{de::Visitor, Deserialize, Deserializer, Serialize}; use time::OffsetDateTime; @@ -231,14 +233,20 @@ where * The Musixmatch API returns zero as a default value for numeric IDs. * These values should be deserialized as [`None`]. */ -pub fn null_if_zero<'de, D>(deserializer: D) -> Result, D::Error> +pub fn null_if_zero<'de, D, N>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, + N: TryFrom, { - struct NullIfZeroVisitor; + struct NullIfZeroVisitor { + n: PhantomData, + } - impl<'de> Visitor<'de> for NullIfZeroVisitor { - type Value = Option; + impl<'de, N> Visitor<'de> for NullIfZeroVisitor + where + N: TryFrom, + { + type Value = Option; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("unsigned int or None") @@ -265,7 +273,7 @@ where if v < 1 { Ok(None) } else { - Ok(Some(v)) + Ok(v.try_into().ok()) } } @@ -291,9 +299,11 @@ where } } - deserializer.deserialize_any(NullIfZeroVisitor) + deserializer.deserialize_any(NullIfZeroVisitor { n: PhantomData }) } +/// The Musixmatch API returns an empty string as a default value for string fields. +/// These values should be deserialized as [`None`]. pub fn null_if_empty<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -331,23 +341,157 @@ where Ok(Some(v.to_owned())) } } + } - fn visit_string(self, v: String) -> Result + deserializer.deserialize_any(NullIfEmptyVisitor) +} + +/// Deserialize a numeric string into an integer. +/// Return None if the string is empty/null or if there was a parse error. +pub fn parse_int<'de, D, N>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + N: FromStr + TryFrom, +{ + struct ParseIntVisitor { + n: PhantomData, + } + + impl<'de, N> Visitor<'de> for ParseIntVisitor + where + N: FromStr + TryFrom, + { + type Value = Option; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("numeric string or None") + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(v.try_into().ok()) + } + + fn visit_u32(self, v: u32) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + + fn visit_u8(self, v: u8) -> Result + where + E: serde::de::Error, + { + self.visit_u64(v.into()) + } + + fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { if v.is_empty() { Ok(None) } else { - Ok(Some(v)) + Ok(v.parse().ok()) } } } - deserializer.deserialize_any(NullIfEmptyVisitor) + deserializer.deserialize_any(ParseIntVisitor { n: PhantomData }) } pub mod optional_date { + use super::*; + use serde::ser::Error as _; + use serde::Serializer; + use time::{macros::format_description, Date}; + + const DATE_FORMAT: &[time::format_description::FormatItem] = + format_description!("[year]-[month]-[day]"); + + pub fn serialize( + value: &Option, + serializer: S, + ) -> Result { + match value { + Some(date) => date + .format(&DATE_FORMAT) + .map_err(S::Error::custom)? + .serialize(serializer), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + struct OptionalDateVisitor; + + impl<'de> Visitor<'de> for OptionalDateVisitor { + type Value = Option; + + fn expecting( + &self, + formatter: &mut serde::__private::fmt::Formatter, + ) -> serde::__private::fmt::Result { + formatter.write_str("date or empty string") + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + if v.is_empty() || v == "0000-00-00" { + Ok(None) + } else { + Ok(Date::parse(v, &DATE_FORMAT).ok()) + } + } + } + + deserializer.deserialize_any(OptionalDateVisitor) + } +} + +pub mod optional_datetime { use super::*; use serde::Serializer; use time::format_description::well_known::Rfc3339; @@ -405,12 +549,14 @@ pub mod optional_date { } } - deserializer.deserialize_str(OptionalDateVisitor) + deserializer.deserialize_any(OptionalDateVisitor) } } #[cfg(test)] mod tests { + use time::Date; + use super::*; use crate::models::subtitle::SubtitleBody; @@ -517,17 +663,68 @@ mod tests { assert_eq!(serde_json::from_str::(json).unwrap().s, None); } + #[test] + fn deserialize_parse_int() { + #[derive(Deserialize)] + struct S { + #[serde(default, deserialize_with = "parse_int")] + n: Option, + } + + let json = r#"{"n": 0}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, Some(0)); + + let json = r#"{"n": "0"}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, Some(0)); + + let json = r#"{"n": null}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, None); + + let json = r#"{}"#; + assert_eq!(serde_json::from_str::(json).unwrap().n, None); + } + #[test] fn deserialize_optional_date() { #[derive(Deserialize)] struct S { #[serde(with = "optional_date")] + date: Option, + } + + let json_null_string = r#"{"date": null}"#; + let json_empty_string = r#"{"date": ""}"#; + let json_zero_date = r#"{"date": "0000-00-00"}"#; + let json_date = r#"{"date": "2022-08-27"}"#; + + let res = serde_json::from_str::(json_null_string).unwrap(); + assert!(res.date.is_none()); + + let res = serde_json::from_str::(json_empty_string).unwrap(); + assert!(res.date.is_none()); + + let res = serde_json::from_str::(json_zero_date).unwrap(); + assert!(res.date.is_none()); + + let res = serde_json::from_str::(json_date).unwrap(); + assert!(res.date.is_some()); + } + + #[test] + fn deserialize_optional_datetime() { + #[derive(Deserialize)] + struct S { + #[serde(with = "optional_datetime")] date: Option, } + let json_null_string = r#"{"date": null}"#; let json_empty_string = r#"{"date": ""}"#; let json_date = r#"{"date": "2022-08-27T23:47:20Z"}"#; + let res = serde_json::from_str::(json_null_string).unwrap(); + assert!(res.date.is_none()); + let res = serde_json::from_str::(json_empty_string).unwrap(); assert!(res.date.is_none()); diff --git a/src/models/album.rs b/src/models/album.rs index 51a82a1..24abbf7 100644 --- a/src/models/album.rs +++ b/src/models/album.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; +use time::{Date, OffsetDateTime}; use super::Genres; @@ -19,8 +19,6 @@ pub struct Album { /// Unique Musixmatch Album ID pub album_id: u64, /// Musicbrainz album ID - /// - /// **Note:** most albums dont have this entry set #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] pub album_mbid: Option, /// Album name @@ -30,13 +28,12 @@ pub struct Album { pub album_rating: u8, /// Number of tracks on the album pub album_track_count: u16, - /// Album release date (e.g. "2009-07-07") - // TODO: use date format + /// Album release date + #[serde(default, with = "crate::api_model::optional_date")] + pub album_release_date: Option, + /// Album type #[serde(default)] - pub album_release_date: String, - /// Album type (Single / EP / Album) - // TODO: use enum - pub album_release_type: String, + pub album_release_type: AlbumType, /// Musixmatch artist ID pub artist_id: u64, @@ -49,10 +46,6 @@ pub struct Album { /// Secondary genres / Subgenres /// /// Example: primary_genres: Pop, secondary_genres: K-Pop / Mandopop - /// - /// Note that this schema is not applied to all tracks on Musixmatch. - /// There are for example many K-Pop tracks with both Pop and K-Pop - /// tagged as primary genre and this field empty. #[serde(default)] pub secondary_genres: Genres, @@ -86,3 +79,17 @@ pub struct Album { #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] pub album_coverart_800x800: Option, } + +/// Album type +/// +/// Source: +#[allow(missing_docs)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum AlbumType { + #[default] + Album, + Single, + Compilation, + Remix, + Live, +} diff --git a/src/models/artist.rs b/src/models/artist.rs index 02377bf..e6d1a76 100644 --- a/src/models/artist.rs +++ b/src/models/artist.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; +use time::{Date, OffsetDateTime}; use super::Genres; @@ -19,8 +19,6 @@ pub struct Artist { /// Musixmatch Artist ID pub artist_id: u64, /// Musicbrainz Artist ID - /// - /// **Note:** most tracks dont have this entry set #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] pub artist_mbid: Option, @@ -75,23 +73,18 @@ pub struct Artist { /// Date and time when the artist was last updated #[serde(with = "time::serde::rfc3339")] pub updated_time: OffsetDateTime, - /// Year of the start of the artist's presence - // TODO: use proper date format for these values - #[serde(default)] - pub begin_date_year: String, - /// Start date of the artist's presence in YYYY-MM-DD format - /// - /// **Info:** the default value is `"0000-00-00"` - #[serde(default)] - pub begin_date: String, - /// Year of the end of the artist's presence - #[serde(default)] - pub end_date_year: String, - /// End date of the artist's presence in YYYY-MM-DD format - /// - /// **Info:** the default value is `"0000-00-00"` - #[serde(default)] - pub end_date: String, + /// Start year of the artist's presence + #[serde(default, deserialize_with = "crate::api_model::parse_int")] + pub begin_date_year: Option, + /// Start date of the artist's presence + #[serde(default, with = "crate::api_model::optional_date")] + pub begin_date: Option, + /// End year of the artist's presence + #[serde(default, deserialize_with = "crate::api_model::parse_int")] + pub end_date_year: Option, + /// End date of the artist's presence + #[serde(default, with = "crate::api_model::optional_date")] + pub end_date: Option, } /// Alternative artist name (e.g. different languages) diff --git a/src/models/mod.rs b/src/models/mod.rs index 29a1c9d..720d4e6 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -39,6 +39,7 @@ pub use artist::ArtistNameTranslationInner; pub(crate) mod album; pub use album::Album; +pub use album::AlbumType; pub(crate) mod snippet; pub use snippet::Snippet; diff --git a/src/models/track.rs b/src/models/track.rs index e0c7ccc..35f5f03 100644 --- a/src/models/track.rs +++ b/src/models/track.rs @@ -20,8 +20,6 @@ pub struct Track { /// Unique Musixmatch Track ID pub track_id: u64, /// Track Musicbrainz ID - /// - /// **Note:** most tracks dont have this entry set #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] pub track_mbid: Option, /// [International Standard Recording Code](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) @@ -94,8 +92,6 @@ pub struct Track { /// Artist name pub artist_name: String, /// Musicbrainz artist ID - /// - /// **Note:** most tracks dont have this entry set #[serde(default, deserialize_with = "crate::api_model::null_if_empty")] pub artist_mbid: Option, @@ -122,7 +118,7 @@ pub struct Track { pub commontrack_vanity_id: String, /// Track release date - #[serde(default, with = "crate::api_model::optional_date")] + #[serde(default, with = "crate::api_model::optional_datetime")] pub first_release_date: Option, /// Date and time when the track was last updated #[serde(with = "time::serde::rfc3339")] diff --git a/tests/tests.rs b/tests/tests.rs index 69675e4..00a3663 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -23,12 +23,13 @@ pub fn new_mxm() -> Musixmatch { Musixmatch::new( &std::env::var("MUSIXMATCH_EMAIL").unwrap(), &std::env::var("MUSIXMATCH_PASSWORD").unwrap(), - Some(Box::new(FileStorage::default())), + Some(Box::::default()), ) } mod album { use super::*; + use musixmatch_inofficial::models::AlbumType; #[rstest] #[case::id(AlbumId::AlbumId(14248253))] @@ -45,8 +46,8 @@ mod album { assert_eq!(album.album_name, "Gangnam Style (강남스타일)"); assert!(album.album_rating > 25); assert_eq!(album.album_track_count, 1); - assert_eq!(album.album_release_date, "2012-01-01"); - assert_eq!(album.album_release_type, "Single"); + assert_eq!(album.album_release_date.unwrap(), date!(2012 - 01 - 01)); + assert_eq!(album.album_release_type, AlbumType::Single); assert_eq!(album.artist_id, 410698); assert_eq!(album.artist_name, "PSY"); @@ -91,6 +92,13 @@ mod album { ); } + #[tokio::test] + async fn album_tst() { + let album = new_mxm().album(AlbumId::AlbumId(23976123)).await.unwrap(); + println!("type: {:?}", album.album_release_type); + println!("date: {:?}", album.album_release_date); + } + #[tokio::test] async fn by_id_missing() { let err = new_mxm() @@ -173,10 +181,10 @@ mod artist { ); assert_eq!(artist.artist_vanity_id, "410698"); assert!(artist.updated_time > datetime!(2016-6-30 0:00 UTC)); - assert_eq!(artist.begin_date_year, "1977"); - assert_eq!(artist.begin_date, "1977-12-31"); - assert_eq!(artist.end_date_year, ""); - assert_eq!(artist.end_date, "0000-00-00"); + assert_eq!(artist.begin_date_year.unwrap(), 1977); + assert_eq!(artist.begin_date.unwrap(), date!(1977 - 12 - 31)); + assert_eq!(artist.end_date_year, None); + assert_eq!(artist.end_date, None); } #[tokio::test]