diff --git a/src/external/musicbrainz/api/mod.rs b/src/external/musicbrainz/api/mod.rs index f4a2d5e..16173cd 100644 --- a/src/external/musicbrainz/api/mod.rs +++ b/src/external/musicbrainz/api/mod.rs @@ -70,7 +70,7 @@ impl ApiDisplay { }, None => format!("{year}"), }, - None => format!("*"), + None => String::from("*"), } } } diff --git a/src/external/musicbrainz/api/search/artist.rs b/src/external/musicbrainz/api/search/artist.rs index bede1dd..e7a1594 100644 --- a/src/external/musicbrainz/api/search/artist.rs +++ b/src/external/musicbrainz/api/search/artist.rs @@ -2,7 +2,10 @@ use std::fmt; use serde::Deserialize; -use crate::{collection::{artist::ArtistId, musicbrainz::Mbid}, external::musicbrainz::api::SerdeMbid}; +use crate::{ + collection::{artist::ArtistId, musicbrainz::Mbid}, + external::musicbrainz::api::SerdeMbid, +}; use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin}; @@ -74,3 +77,69 @@ impl From for SearchArtistResponseArtist } } } + +#[cfg(test)] +mod tests { + use mockall::predicate; + + use crate::external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp}; + + use super::*; + + fn de_response() -> DeserializeSearchArtistResponse { + let de_artist = DeserializeSearchArtistResponseArtist { + score: 67, + id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()), + name: String::from("an artist"), + sort_name: String::from("artist, an"), + disambiguation: None, + }; + DeserializeSearchArtistResponse { + artists: vec![de_artist.clone()], + } + } + + fn response(de_response: DeserializeSearchArtistResponse) -> SearchArtistResponse { + SearchArtistResponse { + artists: de_response + .artists + .into_iter() + .map(|a| SearchArtistResponseArtist { + score: 67, + id: a.id.0, + name: a.name.clone().into(), + sort: Some(a.sort_name).filter(|sn| sn != &a.name).map(Into::into), + disambiguation: a.disambiguation, + }) + .collect(), + } + } + + #[test] + fn search_no_field() { + let mut http = MockIMusicBrainzHttp::new(); + let url = format!( + "https://musicbrainz.org/ws/2\ + /artist\ + ?query=%22{no_field}%22", + no_field = "an+artist", + ); + + let de_response = de_response(); + let response = response(de_response.clone()); + + http.expect_get() + .times(1) + .with(predicate::eq(url)) + .return_once(|_| Ok(de_response)); + + let mut client = MusicBrainzClient::new(http); + + let name = "an artist"; + + let query = SearchArtistRequest::new().no_field(name); + + let matches = client.search_artist(query).unwrap(); + assert_eq!(matches, response); + } +} diff --git a/src/external/musicbrainz/api/search/query.rs b/src/external/musicbrainz/api/search/query.rs index 9faff82..1a1e243 100644 --- a/src/external/musicbrainz/api/search/query.rs +++ b/src/external/musicbrainz/api/search/query.rs @@ -141,6 +141,7 @@ impl fmt::Display for Query { } impl Query { + #[allow(clippy::new_ret_no_self)] pub fn new() -> EmptyQuery { EmptyQuery::default() } @@ -219,30 +220,54 @@ pub(crate) use impl_term; #[cfg(test)] mod tests { - use crate::external::musicbrainz::api::search::SearchReleaseGroupRequest; + use std::fmt; + + use super::*; + + pub enum TestEntity<'a> { + NoField(&'a str), + } + + impl<'a> fmt::Display for TestEntity<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NoField(s) => write!(f, "\"{s}\""), + } + } + } + + type TestEntityRequest<'a> = Query>; + + impl_term!(no_field, TestEntity<'a>, NoField, &'a str); #[test] fn lucene_logical() { - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() .no_field("jakarta apache") .or() .no_field("jakarta"); assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\""); - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() .no_field("jakarta apache") .and() .no_field("jakarta"); assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\""); - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() .require() .no_field("jakarta") .or() .no_field("lucene"); assert_eq!(format!("{query}"), "+\"jakarta\" OR \"lucene\""); - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() + .no_field("lucene") + .require() + .no_field("jakarta"); + assert_eq!(format!("{query}"), "\"lucene\" +\"jakarta\""); + + let query = TestEntityRequest::new() .no_field("jakarta apache") .not() .no_field("Apache Lucene"); @@ -251,7 +276,17 @@ mod tests { "\"jakarta apache\" NOT \"Apache Lucene\"" ); - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() + .prohibit() + .no_field("Apache Lucene") + .or() + .no_field("jakarta apache"); + assert_eq!( + format!("{query}"), + "-\"Apache Lucene\" OR \"jakarta apache\"" + ); + + let query = TestEntityRequest::new() .no_field("jakarta apache") .prohibit() .no_field("Apache Lucene"); @@ -260,9 +295,9 @@ mod tests { #[test] fn lucene_grouping() { - let query = SearchReleaseGroupRequest::new() + let query = TestEntityRequest::new() .expression( - SearchReleaseGroupRequest::new() + TestEntityRequest::new() .no_field("jakarta") .or() .no_field("apache"), diff --git a/src/external/musicbrainz/api/search/release_group.rs b/src/external/musicbrainz/api/search/release_group.rs index 4cfac19..b340e4d 100644 --- a/src/external/musicbrainz/api/search/release_group.rs +++ b/src/external/musicbrainz/api/search/release_group.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use crate::{ collection::{ - album::{AlbumDate, AlbumPrimaryType, AlbumSecondaryType}, + album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType}, musicbrainz::Mbid, }, external::musicbrainz::api::{ @@ -74,7 +74,7 @@ impl From for SearchReleaseGroupResponse pub struct SearchReleaseGroupResponseReleaseGroup { pub score: u8, pub id: Mbid, - pub title: String, + pub title: AlbumId, pub first_release_date: AlbumDate, pub primary_type: AlbumPrimaryType, pub secondary_types: Option>, @@ -82,7 +82,7 @@ pub struct SearchReleaseGroupResponseReleaseGroup { #[derive(Clone, Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] -struct DeserializeSearchReleaseGroupResponseReleaseGroup { +pub struct DeserializeSearchReleaseGroupResponseReleaseGroup { score: u8, id: SerdeMbid, title: String, @@ -98,7 +98,7 @@ impl From SearchReleaseGroupResponseReleaseGroup { score: value.score, id: value.id.into(), - title: value.title, + title: value.title.into(), first_release_date: value.first_release_date.into(), primary_type: value.primary_type.into(), secondary_types: value @@ -110,33 +110,13 @@ impl From #[cfg(test)] mod tests { - use mockall::{predicate, Sequence}; + use mockall::predicate; - use crate::{ - collection::album::AlbumId, - external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp}, - }; + use crate::external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp}; use super::*; - #[test] - fn search_release_group() { - let mut http = MockIMusicBrainzHttp::new(); - let url_title = format!( - "https://musicbrainz.org/ws/2\ - /release-group\ - ?query=arid%3A{arid}+AND+firstreleasedate%3A{date}+AND+releasegroup%3A%22{title}%22", - arid = "00000000-0000-0000-0000-000000000000", - date = "1986-04", - title = "an+album", - ); - let url_rgid = format!( - "https://musicbrainz.org/ws/2\ - /release-group\ - ?query=rgid%3A{rgid}", - rgid = "11111111-1111-1111-1111-111111111111", - ); - + fn de_response() -> DeserializeSearchReleaseGroupResponse { let de_release_group = DeserializeSearchReleaseGroupResponseReleaseGroup { score: 67, id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()), @@ -145,78 +125,72 @@ mod tests { primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album), secondary_types: Some(vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Live)]), }; - let de_response = DeserializeSearchReleaseGroupResponse { + DeserializeSearchReleaseGroupResponse { release_groups: vec![de_release_group.clone()], - }; + } + } - let release_group = SearchReleaseGroupResponseReleaseGroup { - score: 67, - id: de_release_group.id.0, - title: de_release_group.title, - first_release_date: de_release_group.first_release_date.0, - primary_type: de_release_group.primary_type.0, - secondary_types: de_release_group - .secondary_types - .map(|v| v.into_iter().map(|st| st.0).collect()), - }; - let response = SearchReleaseGroupResponse { - release_groups: vec![release_group.clone()], - }; + fn response(de_response: DeserializeSearchReleaseGroupResponse) -> SearchReleaseGroupResponse { + SearchReleaseGroupResponse { + release_groups: de_response + .release_groups + .into_iter() + .map(|rg| SearchReleaseGroupResponseReleaseGroup { + score: 67, + id: rg.id.0, + title: rg.title.into(), + first_release_date: rg.first_release_date.0, + primary_type: rg.primary_type.0, + secondary_types: rg + .secondary_types + .map(|v| v.into_iter().map(|st| st.0).collect()), + }) + .collect(), + } + } - let mut seq = Sequence::new(); + #[test] + fn search_no_field() { + let mut http = MockIMusicBrainzHttp::new(); + let url = format!( + "https://musicbrainz.org/ws/2\ + /release-group\ + ?query=%22{title}%22", + title = "an+album", + ); + + let de_response = de_response(); + let response = response(de_response.clone()); - let title_response = de_response.clone(); http.expect_get() .times(1) - .with(predicate::eq(url_title)) - .return_once(|_| Ok(title_response)) - .in_sequence(&mut seq); - - let rgid_response = de_response; - http.expect_get() - .times(1) - .with(predicate::eq(url_rgid)) - .return_once(|_| Ok(rgid_response)) - .in_sequence(&mut seq); + .with(predicate::eq(url)) + .return_once(|_| Ok(de_response)); let mut client = MusicBrainzClient::new(http); - let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap(); - let title: AlbumId = AlbumId::new("an album"); - let date = (1986, 4).into(); + let title = "an album"; - let query = SearchReleaseGroupRequest::new() - .arid(&arid) - .and() - .release_group(&title.title) - .and() - .first_release_date(&date); - - let matches = client.search_release_group(query).unwrap(); - assert_eq!(matches, response); - - let rgid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); - - let query = SearchReleaseGroupRequest::new().rgid(&rgid); + let query = SearchReleaseGroupRequest::new().no_field(title); let matches = client.search_release_group(query).unwrap(); assert_eq!(matches, response); } #[test] - fn search_release_group_empty_date() { + fn search_arid_album_date_release_group() { let mut http = MockIMusicBrainzHttp::new(); let url = format!( "https://musicbrainz.org/ws/2\ /release-group\ - ?query=arid%3A{arid}+AND+releasegroup%3A%22{title}%22", + ?query=arid%3A{arid}+AND+releasegroup%3A%22{title}%22+AND+firstreleasedate%3A{date}", arid = "00000000-0000-0000-0000-000000000000", + date = "1986-04", title = "an+album", ); - let de_response = DeserializeSearchReleaseGroupResponse { - release_groups: vec![], - }; + let de_response = de_response(); + let response = response(de_response.clone()); http.expect_get() .times(1) @@ -226,16 +200,45 @@ mod tests { let mut client = MusicBrainzClient::new(http); let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap(); - let title: AlbumId = AlbumId::new("an album"); - let date = AlbumDate::default(); + let title = "an album"; + let date = (1986, 4).into(); let query = SearchReleaseGroupRequest::new() .arid(&arid) .and() - .release_group(&title.title) + .release_group(title) .and() .first_release_date(&date); - let _ = client.search_release_group(query).unwrap(); + let matches = client.search_release_group(query).unwrap(); + assert_eq!(matches, response); + } + + #[test] + fn search_rgid() { + let mut http = MockIMusicBrainzHttp::new(); + let url = format!( + "https://musicbrainz.org/ws/2\ + /release-group\ + ?query=rgid%3A{rgid}", + rgid = "11111111-1111-1111-1111-111111111111", + ); + + let de_response = de_response(); + let response = response(de_response.clone()); + + http.expect_get() + .times(1) + .with(predicate::eq(url)) + .return_once(|_| Ok(de_response)); + + let mut client = MusicBrainzClient::new(http); + + let rgid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + + let query = SearchReleaseGroupRequest::new().rgid(&rgid); + + let matches = client.search_release_group(query).unwrap(); + assert_eq!(matches, response); } }