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!("*"),
None => String::from("*"),
}
}
}

View File

@ -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<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> {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> EmptyQuery<Entity> {
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<TestEntity<'a>>;
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"),

View File

@ -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<DeserializeSearchReleaseGroupResponse> 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<Vec<AlbumSecondaryType>>,
@ -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<DeserializeSearchReleaseGroupResponseReleaseGroup>
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<DeserializeSearchReleaseGroupResponseReleaseGroup>
#[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 {
fn response(de_response: DeserializeSearchReleaseGroupResponse) -> SearchReleaseGroupResponse {
SearchReleaseGroupResponse {
release_groups: de_response
.release_groups
.into_iter()
.map(|rg| 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
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()),
};
let response = SearchReleaseGroupResponse {
release_groups: vec![release_group.clone()],
};
})
.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);
}
}