Make fetch also fetch artist MBID if it is missing #201

Merged
wojtek merged 13 commits from 191---make-fetch-also-fetch-artist-mbid-if-it-is-missing into main 2024-08-30 17:58:44 +02:00
4 changed files with 196 additions and 89 deletions
Showing only changes of commit ab8bdf9f3e - Show all commits

View File

@ -70,7 +70,7 @@ impl ApiDisplay {
}, },
None => format!("{year}"), None => format!("{year}"),
}, },
None => format!("*"), None => String::from("*"),
} }
} }
} }

View File

@ -2,7 +2,10 @@ use std::fmt;
use serde::Deserialize; 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}; use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin};
@ -74,3 +77,69 @@ impl From<DeserializeSearchArtistResponseArtist> 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);
}
}

View File

@ -141,6 +141,7 @@ impl<Entity: fmt::Display> fmt::Display for Query<Entity> {
} }
impl<Entity> Query<Entity> { impl<Entity> Query<Entity> {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> EmptyQuery<Entity> { pub fn new() -> EmptyQuery<Entity> {
EmptyQuery::default() EmptyQuery::default()
} }
@ -219,30 +220,54 @@ pub(crate) use impl_term;
#[cfg(test)] #[cfg(test)]
mod tests { 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<TestEntity<'a>>;
impl_term!(no_field, TestEntity<'a>, NoField, &'a str);
#[test] #[test]
fn lucene_logical() { fn lucene_logical() {
let query = SearchReleaseGroupRequest::new() let query = TestEntityRequest::new()
.no_field("jakarta apache") .no_field("jakarta apache")
.or() .or()
.no_field("jakarta"); .no_field("jakarta");
assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\""); assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\"");
let query = SearchReleaseGroupRequest::new() let query = TestEntityRequest::new()
.no_field("jakarta apache") .no_field("jakarta apache")
.and() .and()
.no_field("jakarta"); .no_field("jakarta");
assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\""); assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\"");
let query = SearchReleaseGroupRequest::new() let query = TestEntityRequest::new()
.require() .require()
.no_field("jakarta") .no_field("jakarta")
.or() .or()
.no_field("lucene"); .no_field("lucene");
assert_eq!(format!("{query}"), "+\"jakarta\" OR \"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") .no_field("jakarta apache")
.not() .not()
.no_field("Apache Lucene"); .no_field("Apache Lucene");
@ -251,7 +276,17 @@ mod tests {
"\"jakarta apache\" NOT \"Apache Lucene\"" "\"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") .no_field("jakarta apache")
.prohibit() .prohibit()
.no_field("Apache Lucene"); .no_field("Apache Lucene");
@ -260,9 +295,9 @@ mod tests {
#[test] #[test]
fn lucene_grouping() { fn lucene_grouping() {
let query = SearchReleaseGroupRequest::new() let query = TestEntityRequest::new()
.expression( .expression(
SearchReleaseGroupRequest::new() TestEntityRequest::new()
.no_field("jakarta") .no_field("jakarta")
.or() .or()
.no_field("apache"), .no_field("apache"),

View File

@ -4,7 +4,7 @@ use serde::Deserialize;
use crate::{ use crate::{
collection::{ collection::{
album::{AlbumDate, AlbumPrimaryType, AlbumSecondaryType}, album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
musicbrainz::Mbid, musicbrainz::Mbid,
}, },
external::musicbrainz::api::{ external::musicbrainz::api::{
@ -74,7 +74,7 @@ impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse
pub struct SearchReleaseGroupResponseReleaseGroup { pub struct SearchReleaseGroupResponseReleaseGroup {
pub score: u8, pub score: u8,
pub id: Mbid, pub id: Mbid,
pub title: String, pub title: AlbumId,
pub first_release_date: AlbumDate, pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType, pub primary_type: AlbumPrimaryType,
pub secondary_types: Option<Vec<AlbumSecondaryType>>, pub secondary_types: Option<Vec<AlbumSecondaryType>>,
@ -82,7 +82,7 @@ pub struct SearchReleaseGroupResponseReleaseGroup {
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchReleaseGroupResponseReleaseGroup { pub struct DeserializeSearchReleaseGroupResponseReleaseGroup {
score: u8, score: u8,
id: SerdeMbid, id: SerdeMbid,
title: String, title: String,
@ -98,7 +98,7 @@ impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
SearchReleaseGroupResponseReleaseGroup { SearchReleaseGroupResponseReleaseGroup {
score: value.score, score: value.score,
id: value.id.into(), id: value.id.into(),
title: value.title, title: value.title.into(),
first_release_date: value.first_release_date.into(), first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(), primary_type: value.primary_type.into(),
secondary_types: value secondary_types: value
@ -110,33 +110,13 @@ impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use mockall::{predicate, Sequence}; use mockall::predicate;
use crate::{ use crate::external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp};
collection::album::AlbumId,
external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp},
};
use super::*; use super::*;
#[test] fn de_response() -> DeserializeSearchReleaseGroupResponse {
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",
);
let de_release_group = DeserializeSearchReleaseGroupResponseReleaseGroup { let de_release_group = DeserializeSearchReleaseGroupResponseReleaseGroup {
score: 67, score: 67,
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()), id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
@ -145,78 +125,72 @@ mod tests {
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album), primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: Some(vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Live)]), secondary_types: Some(vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Live)]),
}; };
let de_response = DeserializeSearchReleaseGroupResponse { DeserializeSearchReleaseGroupResponse {
release_groups: vec![de_release_group.clone()], release_groups: vec![de_release_group.clone()],
}; }
}
let release_group = SearchReleaseGroupResponseReleaseGroup { fn response(de_response: DeserializeSearchReleaseGroupResponse) -> SearchReleaseGroupResponse {
SearchReleaseGroupResponse {
release_groups: de_response
.release_groups
.into_iter()
.map(|rg| SearchReleaseGroupResponseReleaseGroup {
score: 67, score: 67,
id: de_release_group.id.0, id: rg.id.0,
title: de_release_group.title, title: rg.title.into(),
first_release_date: de_release_group.first_release_date.0, first_release_date: rg.first_release_date.0,
primary_type: de_release_group.primary_type.0, primary_type: rg.primary_type.0,
secondary_types: de_release_group secondary_types: rg
.secondary_types .secondary_types
.map(|v| v.into_iter().map(|st| st.0).collect()), .map(|v| v.into_iter().map(|st| st.0).collect()),
}; })
let response = SearchReleaseGroupResponse { .collect(),
release_groups: vec![release_group.clone()], }
}; }
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() http.expect_get()
.times(1) .times(1)
.with(predicate::eq(url_title)) .with(predicate::eq(url))
.return_once(|_| Ok(title_response)) .return_once(|_| Ok(de_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);
let mut client = MusicBrainzClient::new(http); let mut client = MusicBrainzClient::new(http);
let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap(); let title = "an album";
let title: AlbumId = AlbumId::new("an album");
let date = (1986, 4).into();
let query = SearchReleaseGroupRequest::new() let query = SearchReleaseGroupRequest::new().no_field(title);
.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 matches = client.search_release_group(query).unwrap(); let matches = client.search_release_group(query).unwrap();
assert_eq!(matches, response); assert_eq!(matches, response);
} }
#[test] #[test]
fn search_release_group_empty_date() { fn search_arid_album_date_release_group() {
let mut http = MockIMusicBrainzHttp::new(); let mut http = MockIMusicBrainzHttp::new();
let url = format!( let url = format!(
"https://musicbrainz.org/ws/2\ "https://musicbrainz.org/ws/2\
/release-group\ /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", arid = "00000000-0000-0000-0000-000000000000",
date = "1986-04",
title = "an+album", title = "an+album",
); );
let de_response = DeserializeSearchReleaseGroupResponse { let de_response = de_response();
release_groups: vec![], let response = response(de_response.clone());
};
http.expect_get() http.expect_get()
.times(1) .times(1)
@ -226,16 +200,45 @@ mod tests {
let mut client = MusicBrainzClient::new(http); let mut client = MusicBrainzClient::new(http);
let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap(); let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let title: AlbumId = AlbumId::new("an album"); let title = "an album";
let date = AlbumDate::default(); let date = (1986, 4).into();
let query = SearchReleaseGroupRequest::new() let query = SearchReleaseGroupRequest::new()
.arid(&arid) .arid(&arid)
.and() .and()
.release_group(&title.title) .release_group(title)
.and() .and()
.first_release_date(&date); .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);
} }
} }