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
5 changed files with 204 additions and 96 deletions
Showing only changes of commit bea62a334e - Show all commits

View File

@ -354,7 +354,29 @@ mod tests {
} }
#[test] #[test]
fn fetch_musicbrainz_api_error() { fn fetch_musicbrainz_artist_api_error() {
let mut mb_api = Box::new(MockIMusicBrainz::new());
let error = Err(musicbrainz::Error::RateLimit);
mb_api
.expect_search_artist()
.times(1)
.return_once(|_| error);
let browse = AppMachine::browse(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
// Use the fourth artist for this test as they have no MBID.
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
let app = browse.fetch_musicbrainz();
app.unwrap_error();
}
#[test]
fn fetch_musicbrainz_album_api_error() {
let mut mb_api = Box::new(MockIMusicBrainz::new()); let mut mb_api = Box::new(MockIMusicBrainz::new());
let error = Err(musicbrainz::Error::RateLimit); let error = Err(musicbrainz::Error::RateLimit);

View File

@ -204,7 +204,10 @@ impl IAppInteractMatches for AppMachine<AppMatches> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use musichoard::collection::album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType}; use musichoard::collection::{
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
artist::ArtistId,
};
use crate::tui::app::{ use crate::tui::app::{
machine::tests::{inner, music_hoard}, machine::tests::{inner, music_hoard},
@ -222,7 +225,31 @@ mod tests {
} }
} }
fn matches_info_vec() -> Vec<AppMatchesInfo> { fn artist_matches_info_vec() -> Vec<AppMatchesInfo> {
let artist_1 = Artist::new(ArtistId::new("Artist 1"));
let artist_1_1 = artist_1.clone();
let artist_match_1_1 = Match::new(100, artist_1_1);
let artist_1_2 = artist_1.clone();
let mut artist_match_1_2 = Match::new(100, artist_1_2);
artist_match_1_2.set_disambiguation("some disambiguation");
let list = vec![artist_match_1_1.clone(), artist_match_1_2.clone()];
let matches_info_1 = AppMatchesInfo::artist(artist_1.clone(), list);
let artist_2 = Artist::new(ArtistId::new("Artist 2"));
let artist_2_1 = artist_1.clone();
let album_match_2_1 = Match::new(100, artist_2_1);
let list = vec![album_match_2_1.clone()];
let matches_info_2 = AppMatchesInfo::artist(artist_2.clone(), list);
vec![matches_info_1, matches_info_2]
}
fn album_matches_info_vec() -> Vec<AppMatchesInfo> {
let album_1 = Album::new( let album_1 = Album::new(
AlbumId::new("Album 1"), AlbumId::new("Album 1"),
AlbumDate::new(Some(1990), Some(5), None), AlbumDate::new(Some(1990), Some(5), None),
@ -231,25 +258,15 @@ mod tests {
); );
let album_1_1 = album_1.clone(); let album_1_1 = album_1.clone();
let album_match_1_1 = Match { let album_match_1_1 = Match::new(100, album_1_1);
score: 100,
item: album_1_1,
disambiguation: None,
};
let mut album_1_2 = album_1.clone(); let mut album_1_2 = album_1.clone();
album_1_2.id.title.push_str(" extra title part"); album_1_2.id.title.push_str(" extra title part");
album_1_2.secondary_types.pop(); album_1_2.secondary_types.pop();
let album_match_1_2 = Match { let album_match_1_2 = Match::new(100, album_1_2);
score: 100,
item: album_1_2,
disambiguation: None,
};
let matches_info_1 = AppMatchesInfo::Album(AppAlbumMatchesInfo { let list = vec![album_match_1_1.clone(), album_match_1_2.clone()];
matching: album_1.clone(), let matches_info_1 = AppMatchesInfo::album(album_1.clone(), list);
list: vec![album_match_1_1.clone(), album_match_1_2.clone()],
});
let album_2 = Album::new( let album_2 = Album::new(
AlbumId::new("Album 2"), AlbumId::new("Album 2"),
@ -259,16 +276,10 @@ mod tests {
); );
let album_2_1 = album_1.clone(); let album_2_1 = album_1.clone();
let album_match_2_1 = Match { let album_match_2_1 = Match::new(100, album_2_1);
score: 100,
item: album_2_1,
disambiguation: None,
};
let matches_info_2 = AppMatchesInfo::Album(AppAlbumMatchesInfo { let list = vec![album_match_2_1.clone()];
matching: album_2.clone(), let matches_info_2 = AppMatchesInfo::album(album_2.clone(), list);
list: vec![album_match_2_1.clone()],
});
vec![matches_info_1, matches_info_2] vec![matches_info_1, matches_info_2]
} }
@ -293,7 +304,7 @@ mod tests {
#[test] #[test]
fn create_nonempty() { fn create_nonempty() {
let matches_info_vec = matches_info_vec(); let matches_info_vec = album_matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone()); let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();
@ -323,9 +334,7 @@ mod tests {
assert_eq!(public_matches.state, &widget_state); assert_eq!(public_matches.state, &widget_state);
} }
#[test] fn matches_flow(matches_info_vec: Vec<AppMatchesInfo>) {
fn matches_flow() {
let matches_info_vec = matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone()); let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();
@ -359,9 +368,19 @@ mod tests {
matches.select().unwrap_browse(); matches.select().unwrap_browse();
} }
#[test]
fn artist_matches_flow() {
matches_flow(artist_matches_info_vec());
}
#[test]
fn album_matches_flow() {
matches_flow(album_matches_info_vec());
}
#[test] #[test]
fn matches_abort() { fn matches_abort() {
let matches_info_vec = matches_info_vec(); let matches_info_vec = album_matches_info_vec();
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone()); let matches = AppMachine::matches(inner(music_hoard(vec![])), matches_info_vec.clone());
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();

View File

@ -98,11 +98,11 @@ impl UiDisplay {
} }
} }
pub fn display_artist_matching_info(artist: &Artist) -> String { pub fn display_artist_matching(artist: &Artist) -> String {
format!("Matching artist: {}", &artist.id.name) format!("Matching artist: {}", &artist.id.name)
} }
pub fn display_album_matching_info(album: &Album) -> String { pub fn display_album_matching(album: &Album) -> String {
format!( format!(
"Matching album: {} | {}", "Matching album: {} | {}",
UiDisplay::display_album_date(&album.date), UiDisplay::display_album_date(&album.date),
@ -110,25 +110,34 @@ impl UiDisplay {
) )
} }
pub fn display_matching_nothing_info() -> &'static str { pub fn display_nothing_matching() -> &'static str {
"Matching nothing" "Matching nothing"
} }
pub fn display_matching_info(matches: Option<&AppPublicMatchesKind>) -> String { pub fn display_matching_info(matches: Option<&AppPublicMatchesKind>) -> String {
match matches.as_ref() { match matches.as_ref() {
Some(kind) => match kind { Some(kind) => match kind {
AppPublicMatchesKind::Artist(m) => { AppPublicMatchesKind::Artist(m) => UiDisplay::display_artist_matching(m.matching),
UiDisplay::display_artist_matching_info(m.matching) AppPublicMatchesKind::Album(m) => UiDisplay::display_album_matching(m.matching),
}
AppPublicMatchesKind::Album(m) => {
UiDisplay::display_album_matching_info(m.matching)
}
}, },
None => UiDisplay::display_matching_nothing_info().to_string(), None => UiDisplay::display_nothing_matching().to_string(),
} }
} }
pub fn display_album_match_string(match_album: &Match<Album>) -> String { pub fn display_artist_match(match_artist: &Match<Artist>) -> String {
format!(
"{}{} ({}%)",
&match_artist.item.id.name,
&match_artist
.disambiguation
.as_ref()
.map(|d| format!(" ({d})"))
.unwrap_or_default(),
match_artist.score,
)
}
pub fn display_album_match(match_album: &Match<Album>) -> String {
format!( format!(
"{:010} | {} [{}] ({}%)", "{:010} | {} [{}] ({}%)",
UiDisplay::display_album_date(&match_album.item.date), UiDisplay::display_album_date(&match_album.item.date),
@ -140,17 +149,6 @@ impl UiDisplay {
match_album.score, match_album.score,
) )
} }
pub fn display_artist_match_string(match_artist: &Match<Artist>) -> String {
let disambiguation_string = match match_artist.disambiguation.as_ref() {
Some(d) => format!(" ({d})"),
None => String::from(""),
};
format!(
"{}{} ({}%)",
&match_artist.item.id.name, &disambiguation_string, match_artist.score,
)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -26,19 +26,19 @@ impl<'a, 'b> MatchesState<'a, 'b> {
fn empty(state: &'b mut WidgetState) -> Self { fn empty(state: &'b mut WidgetState) -> Self {
MatchesState { MatchesState {
matching: UiDisplay::display_matching_nothing_info().to_string(), matching: UiDisplay::display_nothing_matching().to_string(),
list: List::default(), list: List::default(),
state, state,
} }
} }
fn artists(matching: &Artist, matches: &[Match<Artist>], state: &'b mut WidgetState) -> Self { fn artists(matching: &Artist, matches: &[Match<Artist>], state: &'b mut WidgetState) -> Self {
let matching = UiDisplay::display_artist_matching_info(matching); let matching = UiDisplay::display_artist_matching(matching);
let list = List::new( let list = List::new(
matches matches
.iter() .iter()
.map(UiDisplay::display_artist_match_string) .map(UiDisplay::display_artist_match)
.map(ListItem::new) .map(ListItem::new)
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
@ -51,12 +51,12 @@ impl<'a, 'b> MatchesState<'a, 'b> {
} }
fn albums(matching: &Album, matches: &[Match<Album>], state: &'b mut WidgetState) -> Self { fn albums(matching: &Album, matches: &[Match<Album>], state: &'b mut WidgetState) -> Self {
let matching = UiDisplay::display_album_matching_info(matching); let matching = UiDisplay::display_album_matching(matching);
let list = List::new( let list = List::new(
matches matches
.iter() .iter()
.map(UiDisplay::display_album_match_string) .map(UiDisplay::display_album_match)
.map(ListItem::new) .map(ListItem::new)
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );

View File

@ -246,27 +246,35 @@ mod tests {
} }
} }
fn public_inner<'app>(
collection: &'app Collection,
selection: &'app mut Selection,
) -> AppPublicInner<'app> {
AppPublicInner {
collection,
selection,
}
}
fn artist_matches<'app>(
matching: &'app Artist,
list: &'app [Match<Artist>],
) -> AppPublicMatchesKind<'app> {
AppPublicMatchesKind::Artist(AppPublicArtistMatches { matching, list })
}
fn album_matches<'app>(
matching: &'app Album,
list: &'app [Match<Album>],
) -> AppPublicMatchesKind<'app> {
AppPublicMatchesKind::Album(AppPublicAlbumMatches { matching, list })
}
fn draw_test_suite(collection: &Collection, selection: &mut Selection) { fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
let mut terminal = terminal(); let mut terminal = terminal();
let album = Album::new(
AlbumId::new("An Album"),
AlbumDate::new(Some(1990), Some(5), None),
Some(AlbumPrimaryType::Album),
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
);
let album_match = Match {
score: 80,
item: album.clone(),
disambiguation: None,
};
let mut app = AppPublic { let mut app = AppPublic {
inner: AppPublicInner { inner: public_inner(collection, selection),
collection,
selection,
},
state: AppState::Browse(()), state: AppState::Browse(()),
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
@ -280,27 +288,6 @@ mod tests {
app.state = AppState::Search(""); app.state = AppState::Search("");
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let album_matches = [album_match.clone(), album_match.clone()];
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
app.state = AppState::Matches(AppPublicMatches {
matches: Some(AppPublicMatchesKind::Album(AppPublicAlbumMatches {
matching: &album,
list: &album_matches,
})),
state: &mut widget_state,
});
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let mut widget_state = WidgetState::default();
app.state = AppState::Matches(AppPublicMatches {
matches: None,
state: &mut widget_state,
});
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
app.state = AppState::Error("get rekt scrub"); app.state = AppState::Error("get rekt scrub");
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
@ -348,4 +335,86 @@ mod tests {
draw_test_suite(artists, &mut selection); draw_test_suite(artists, &mut selection);
} }
#[test]
fn draw_empty_matches() {
let collection = &COLLECTION;
let mut selection = Selection::new(collection);
let mut terminal = terminal();
let mut widget_state = WidgetState::default();
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: None,
state: &mut widget_state,
}),
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
}
#[test]
fn draw_artist_matches() {
let collection = &COLLECTION;
let mut selection = Selection::new(collection);
let mut terminal = terminal();
let artist = Artist::new(ArtistId::new("an artist"));
let artist_match = Match {
score: 80,
item: artist.clone(),
disambiguation: None,
};
let list = [artist_match.clone(), artist_match.clone()];
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: Some(artist_matches(&artist, &list)),
state: &mut widget_state,
}),
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
}
#[test]
fn draw_album_matches() {
let collection = &COLLECTION;
let mut selection = Selection::new(collection);
let mut terminal = terminal();
let album = Album::new(
AlbumId::new("An Album"),
AlbumDate::new(Some(1990), Some(5), None),
Some(AlbumPrimaryType::Album),
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
);
let album_match = Match {
score: 80,
item: album.clone(),
disambiguation: None,
};
let list = [album_match.clone(), album_match.clone()];
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: Some(album_matches(&album, &list)),
state: &mut widget_state,
}),
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
}
} }