Compare commits

..

4 Commits

Author SHA1 Message Date
780e6b8350 Finish UTs
All checks were successful
Cargo CI / Build and Test (pull_request) Successful in 2m47s
Cargo CI / Lint (pull_request) Successful in 1m6s
2024-09-23 22:36:18 +02:00
6a9919fac9 fix 2024-09-23 22:06:58 +02:00
2b72726428 api complete coverage 2024-09-23 22:06:10 +02:00
369b4ecf98 app complete coverage 2024-09-23 22:01:56 +02:00
7 changed files with 470 additions and 140 deletions

View File

@ -162,19 +162,9 @@ mod tests {
release_groups: Some(vec![de_release_group.clone()]), release_groups: Some(vec![de_release_group.clone()]),
}; };
let release_group = MbReleaseGroupMeta {
id: de_release_group.id.0,
title: de_release_group.title.into(),
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
.as_ref()
.map(|v| v.into_iter().map(|st| st.0).collect()),
};
let response = LookupArtistResponse { let response = LookupArtistResponse {
meta: de_meta.into(), meta: de_meta.into(),
release_groups: vec![release_group], release_groups: vec![de_release_group.into()],
}; };
http.expect_get() http.expect_get()
@ -191,4 +181,41 @@ mod tests {
assert_eq!(result, response); assert_eq!(result, response);
} }
#[test]
fn lookup_release_group() {
let mbid = "00000000-0000-0000-0000-000000000000";
let mut http = MockIMusicBrainzHttp::new();
let url = format!("https://musicbrainz.org/ws/2/release-group/{mbid}",);
let de_meta = SerdeMbReleaseGroupMeta {
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
title: String::from("an album"),
first_release_date: SerdeAlbumDate((1986, 4).into()),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: Some(vec![SerdeAlbumSecondaryType(
AlbumSecondaryType::Compilation,
)]),
};
let de_response = DeserializeLookupReleaseGroupResponse {
meta: de_meta.clone(),
};
let response = LookupReleaseGroupResponse {
meta: de_meta.into(),
};
http.expect_get()
.times(1)
.with(predicate::eq(url))
.return_once(|_| Ok(de_response));
let mut client = MusicBrainzClient::new(http);
let mbid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let request = LookupReleaseGroupRequest::new(&mbid);
let result = client.lookup_release_group(request).unwrap();
assert_eq!(result, response);
}
} }

View File

@ -19,12 +19,12 @@ use crate::tui::{
}, },
}; };
pub type FetchReceiver = mpsc::Receiver<MbApiResult>;
pub struct FetchState { pub struct FetchState {
fetch_rx: FetchReceiver, fetch_rx: FetchReceiver,
lookup_rx: Option<FetchReceiver>, lookup_rx: Option<FetchReceiver>,
} }
pub type FetchReceiver = mpsc::Receiver<MbApiResult>;
impl FetchState { impl FetchState {
pub fn new(fetch_rx: FetchReceiver) -> Self { pub fn new(fetch_rx: FetchReceiver) -> Self {
FetchState { FetchState {
@ -38,7 +38,7 @@ impl FetchState {
let result = lookup_rx.try_recv(); let result = lookup_rx.try_recv();
match result { match result {
Ok(_) | Err(TryRecvError::Empty) => return result, Ok(_) | Err(TryRecvError::Empty) => return result,
_ => { Err(TryRecvError::Disconnected) => {
self.lookup_rx.take(); self.lookup_rx.take();
} }
} }
@ -214,12 +214,47 @@ mod tests {
machine::tests::{inner, music_hoard}, machine::tests::{inner, music_hoard},
Delta, IApp, IAppAccess, IAppInteractBrowse, MatchStateInfo, MissOption, SearchOption, Delta, IApp, IAppAccess, IAppInteractBrowse, MatchStateInfo, MissOption, SearchOption,
}, },
lib::interface::musicbrainz::{self, api::Match, daemon::MockIMbJobSender}, lib::interface::musicbrainz::{
self,
api::{Lookup, Match},
daemon::MockIMbJobSender,
},
testmod::COLLECTION, testmod::COLLECTION,
}; };
use super::*; use super::*;
fn mbid() -> Mbid {
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
}
#[test]
fn try_recv() {
let (fetch_tx, fetch_rx) = mpsc::channel();
let (lookup_tx, lookup_rx) = mpsc::channel();
let mut fetch = FetchState::new(fetch_rx);
fetch.lookup_rx.replace(lookup_rx);
let artist = COLLECTION[3].meta.clone();
let matches: Vec<Match<ArtistMeta>> = vec![];
let fetch_result = MatchStateInfo::artist_search(artist.clone(), matches);
fetch_tx.send(Ok(fetch_result.clone())).unwrap();
assert_eq!(fetch.try_recv(), Err(TryRecvError::Empty));
let lookup = Lookup::new(artist.clone());
let lookup_result = MatchStateInfo::artist_lookup(artist.clone(), lookup);
lookup_tx.send(Ok(lookup_result.clone())).unwrap();
assert_eq!(fetch.try_recv(), Ok(Ok(lookup_result)));
assert_eq!(fetch.try_recv(), Err(TryRecvError::Empty));
drop(lookup_tx);
assert_eq!(fetch.try_recv(), Ok(Ok(fetch_result)));
}
#[test] #[test]
fn fetch_no_artist() { fn fetch_no_artist() {
let app = AppMachine::app_fetch_new(inner(music_hoard(vec![]))); let app = AppMachine::app_fetch_new(inner(music_hoard(vec![])));
@ -265,6 +300,31 @@ mod tests {
assert!(matches!(app, AppState::Match(_))); assert!(matches!(app, AppState::Match(_)));
} }
fn lookup_album_expectation(job_sender: &mut MockIMbJobSender, album: &AlbumMeta) {
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]);
job_sender
.expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests))
.times(1)
.return_once(|_, _| Ok(()));
}
#[test]
fn lookup_album() {
let mut mb_job_sender = MockIMbJobSender::new();
let album = COLLECTION[1].albums[0].meta.clone();
lookup_album_expectation(&mut mb_job_sender, &album);
let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = AppInner::new(music_hoard, mb_job_sender);
let (_fetch_tx, fetch_rx) = mpsc::channel();
let fetch = FetchState::new(fetch_rx);
AppMachine::app_lookup_album(inner, fetch, &album, mbid());
}
fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) { fn search_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
let requests = VecDeque::from([MbParams::search_artist(artist.clone())]); let requests = VecDeque::from([MbParams::search_artist(artist.clone())]);
job_sender job_sender
@ -294,6 +354,31 @@ mod tests {
assert!(matches!(app, AppState::Match(_))); assert!(matches!(app, AppState::Match(_)));
} }
fn lookup_artist_expectation(job_sender: &mut MockIMbJobSender, artist: &ArtistMeta) {
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid())]);
job_sender
.expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests))
.times(1)
.return_once(|_, _| Ok(()));
}
#[test]
fn lookup_artist() {
let mut mb_job_sender = MockIMbJobSender::new();
let artist = COLLECTION[3].meta.clone();
lookup_artist_expectation(&mut mb_job_sender, &artist);
let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = AppInner::new(music_hoard, mb_job_sender);
let (_fetch_tx, fetch_rx) = mpsc::channel();
let fetch = FetchState::new(fetch_rx);
AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
}
#[test] #[test]
fn fetch_artist_job_sender_err() { fn fetch_artist_job_sender_err() {
let mut mb_job_sender = MockIMbJobSender::new(); let mut mb_job_sender = MockIMbJobSender::new();
@ -310,6 +395,26 @@ mod tests {
assert!(matches!(app, AppState::Error(_))); assert!(matches!(app, AppState::Error(_)));
} }
#[test]
fn lookup_artist_job_sender_err() {
let mut mb_job_sender = MockIMbJobSender::new();
mb_job_sender
.expect_submit_foreground_job()
.return_once(|_, _| Err(DaemonError::JobChannelDisconnected));
let artist = COLLECTION[3].meta.clone();
let music_hoard = music_hoard(COLLECTION.to_owned());
let inner = AppInner::new(music_hoard, mb_job_sender);
let (_fetch_tx, fetch_rx) = mpsc::channel();
let fetch = FetchState::new(fetch_rx);
let app = AppMachine::app_lookup_artist(inner, fetch, &artist, mbid());
assert!(matches!(app, AppState::Error(_)));
}
#[test] #[test]
fn recv_ok_fetch_ok() { fn recv_ok_fetch_ok() {
let (tx, rx) = mpsc::channel::<MbApiResult>(); let (tx, rx) = mpsc::channel::<MbApiResult>();

View File

@ -64,20 +64,12 @@ impl IAppInput for AppInputMode {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tui::app::{ use crate::tui::app::{
machine::tests::{mb_job_sender, music_hoard_init}, machine::tests::{input_event, mb_job_sender, music_hoard_init},
IApp, IApp,
}; };
use super::*; use super::*;
fn input_event(c: char) -> InputEvent {
crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Char(c),
crossterm::event::KeyModifiers::empty(),
)
.into()
}
#[test] #[test]
fn handle_input() { fn handle_input() {
let mut app = App::new(music_hoard_init(vec![]), mb_job_sender()); let mut app = App::new(music_hoard_init(vec![]), mb_job_sender());

View File

@ -86,14 +86,14 @@ impl MatchStateInfo {
} }
} }
fn push_cannot_have_mbid(&mut self) { pub fn push_cannot_have_mbid(&mut self) {
match self { match self {
Self::Artist(a) => a.push_cannot_have_mbid(), Self::Artist(a) => a.push_cannot_have_mbid(),
Self::Album(a) => a.push_cannot_have_mbid(), Self::Album(a) => a.push_cannot_have_mbid(),
} }
} }
fn push_manual_input_mbid(&mut self) { pub fn push_manual_input_mbid(&mut self) {
match self { match self {
Self::Artist(a) => a.push_manual_input_mbid(), Self::Artist(a) => a.push_manual_input_mbid(),
Self::Album(a) => a.push_manual_input_mbid(), Self::Album(a) => a.push_manual_input_mbid(),
@ -218,8 +218,9 @@ impl IAppInteractMatch for AppMachine<MatchState> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::mpsc; use std::{collections::VecDeque, sync::mpsc};
use mockall::predicate;
use musichoard::collection::{ use musichoard::collection::{
album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType}, album::{AlbumDate, AlbumId, AlbumMeta, AlbumPrimaryType, AlbumSecondaryType},
artist::{ArtistId, ArtistMeta}, artist::{ArtistId, ArtistMeta},
@ -227,10 +228,13 @@ mod tests {
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::tests::{inner, music_hoard}, machine::tests::{inner, inner_with_mb, input_event, music_hoard},
IApp, IAppAccess, IAppInput, IApp, IAppAccess, IAppInput,
}, },
lib::interface::musicbrainz::api::Match, lib::interface::musicbrainz::{
api::{Lookup, Match},
daemon::{MbParams, MockIMbJobSender},
},
}; };
use super::*; use super::*;
@ -245,6 +249,15 @@ mod tests {
} }
} }
impl<T> Lookup<T> {
pub fn new(item: T) -> Self {
Lookup {
item,
disambiguation: None,
}
}
}
fn artist_match() -> MatchStateInfo { fn artist_match() -> MatchStateInfo {
let artist = ArtistMeta::new(ArtistId::new("Artist")); let artist = ArtistMeta::new(ArtistId::new("Artist"));
@ -259,6 +272,12 @@ mod tests {
MatchStateInfo::artist_search(artist, list) MatchStateInfo::artist_search(artist, list)
} }
fn artist_lookup() -> MatchStateInfo {
let artist = ArtistMeta::new(ArtistId::new("Artist"));
let lookup = Lookup::new(artist.clone());
MatchStateInfo::artist_lookup(artist, lookup)
}
fn album_match() -> MatchStateInfo { fn album_match() -> MatchStateInfo {
let album = AlbumMeta::new( let album = AlbumMeta::new(
AlbumId::new("Album"), AlbumId::new("Album"),
@ -279,6 +298,17 @@ mod tests {
MatchStateInfo::album_search(album, list) MatchStateInfo::album_search(album, list)
} }
fn album_lookup() -> MatchStateInfo {
let album = AlbumMeta::new(
AlbumId::new("Album"),
AlbumDate::new(Some(1990), Some(5), None),
Some(AlbumPrimaryType::Album),
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
);
let lookup = Lookup::new(album.clone());
MatchStateInfo::album_lookup(album, lookup)
}
fn fetch_state() -> FetchState { fn fetch_state() -> FetchState {
let (_, rx) = mpsc::channel(); let (_, rx) = mpsc::channel();
FetchState::new(rx) FetchState::new(rx)
@ -329,7 +359,7 @@ mod tests {
assert_eq!(public_matches.state, &widget_state); assert_eq!(public_matches.state, &widget_state);
} }
fn match_state_flow(mut matches_info: MatchStateInfo) { fn match_state_flow(mut matches_info: MatchStateInfo, len: usize) {
// tx must exist for rx to return Empty rather than Disconnected. // tx must exist for rx to return Empty rather than Disconnected.
#[allow(unused_variables)] #[allow(unused_variables)]
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
@ -350,27 +380,30 @@ mod tests {
assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(0)); assert_eq!(matches.state.state.list.selected(), Some(0));
let matches = matches.next_match().unwrap_match(); let mut matches = matches;
for ii in 1..len {
matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(1)); assert_eq!(matches.state.state.list.selected(), Some(ii));
}
// Next is CannotHaveMBID // Next is CannotHaveMBID
let matches = matches.next_match().unwrap_match(); let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(2)); assert_eq!(matches.state.state.list.selected(), Some(len));
// Next is ManualInputMbid // Next is ManualInputMbid
let matches = matches.next_match().unwrap_match(); let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(3)); assert_eq!(matches.state.state.list.selected(), Some(len + 1));
let matches = matches.next_match().unwrap_match(); let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info)); assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(3)); assert_eq!(matches.state.state.list.selected(), Some(len + 1));
// Go prev_match first as selecting on manual input does not go back to fetch. // Go prev_match first as selecting on manual input does not go back to fetch.
let matches = matches.prev_match().unwrap_match(); let matches = matches.prev_match().unwrap_match();
@ -379,12 +412,22 @@ mod tests {
#[test] #[test]
fn artist_matches_flow() { fn artist_matches_flow() {
match_state_flow(artist_match()); match_state_flow(artist_match(), 2);
}
#[test]
fn artist_lookup_flow() {
match_state_flow(artist_lookup(), 1);
} }
#[test] #[test]
fn album_matches_flow() { fn album_matches_flow() {
match_state_flow(album_match()); match_state_flow(album_match(), 2);
}
#[test]
fn album_lookup_flow() {
match_state_flow(album_lookup(), 1);
} }
#[test] #[test]
@ -430,4 +473,73 @@ mod tests {
let input = app.mode().unwrap_input(); let input = app.mode().unwrap_input();
input.confirm().unwrap_error(); input.confirm().unwrap_error();
} }
fn mbid() -> Mbid {
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
}
fn input_mbid(mut app: App) -> App {
let mbid = mbid().uuid().to_string();
for c in mbid.chars() {
let input = app.mode().unwrap_input();
app = input.input(input_event(c));
}
app
}
#[test]
fn select_manual_input_artist() {
let mut mb_job_sender = MockIMbJobSender::new();
let artist = ArtistMeta::new(ArtistId::new("Artist"));
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid())]);
mb_job_sender
.expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests))
.return_once(|_, _| Ok(()));
let matches_vec: Vec<Match<ArtistMeta>> = vec![];
let artist_match = MatchStateInfo::artist_search(artist.clone(), matches_vec);
let matches = AppMachine::match_state(
inner_with_mb(music_hoard(vec![]), mb_job_sender),
match_state(Some(artist_match)),
);
// There are no matches which means that the second option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let mut app = matches.select();
app = input_mbid(app);
let input = app.mode().unwrap_input();
input.confirm();
}
#[test]
fn select_manual_input_album() {
let mut mb_job_sender = MockIMbJobSender::new();
let album = AlbumMeta::new("Album", 1990, None, vec![]);
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid())]);
mb_job_sender
.expect_submit_foreground_job()
.with(predicate::always(), predicate::eq(requests))
.return_once(|_, _| Ok(()));
let matches_vec: Vec<Match<AlbumMeta>> = vec![];
let album_match = MatchStateInfo::album_search(album.clone(), matches_vec);
let matches = AppMachine::match_state(
inner_with_mb(music_hoard(vec![]), mb_job_sender),
match_state(Some(album_match)),
);
// There are no matches which means that the second option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let mut app = matches.select();
app = input_mbid(app);
let input = app.mode().unwrap_input();
input.confirm();
}
} }

View File

@ -222,7 +222,7 @@ mod tests {
use musichoard::collection::Collection; use musichoard::collection::Collection;
use crate::tui::{ use crate::tui::{
app::{AppState, IApp, IAppInput, IAppInteractBrowse}, app::{AppState, IApp, IAppInput, IAppInteractBrowse, InputEvent},
lib::{interface::musicbrainz::daemon::MockIMbJobSender, MockIMusicHoard}, lib::{interface::musicbrainz::daemon::MockIMbJobSender, MockIMusicHoard},
}; };
@ -355,6 +355,14 @@ mod tests {
AppInner::new(music_hoard, mb_job_sender) AppInner::new(music_hoard, mb_job_sender)
} }
pub fn input_event(c: char) -> InputEvent {
crossterm::event::KeyEvent::new(
crossterm::event::KeyCode::Char(c),
crossterm::event::KeyModifiers::empty(),
)
.into()
}
#[test] #[test]
fn input_mode() { fn input_mode() {
let app = App::new(music_hoard_init(vec![]), mb_job_sender()); let app = App::new(music_hoard_init(vec![]), mb_job_sender());

View File

@ -110,7 +110,7 @@ impl IMbJobSender for JobSender {
result_sender: ResultSender, result_sender: ResultSender,
requests: VecDeque<MbParams>, requests: VecDeque<MbParams>,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.send_foreground_job(result_sender, requests) self.send_job(JobPriority::Foreground, result_sender, requests)
} }
fn submit_background_job( fn submit_background_job(
@ -118,27 +118,11 @@ impl IMbJobSender for JobSender {
result_sender: ResultSender, result_sender: ResultSender,
requests: VecDeque<MbParams>, requests: VecDeque<MbParams>,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.send_background_job(result_sender, requests) self.send_job(JobPriority::Background, result_sender, requests)
} }
} }
impl JobSender { impl JobSender {
fn send_foreground_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_job(JobPriority::Foreground, result_sender, requests)
}
fn send_background_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_job(JobPriority::Background, result_sender, requests)
}
fn send_job( fn send_job(
&self, &self,
priority: JobPriority, priority: JobPriority,
@ -337,7 +321,7 @@ mod tests {
use crate::tui::{ use crate::tui::{
event::{Event, EventError, MockIFetchCompleteEventSender}, event::{Event, EventError, MockIFetchCompleteEventSender},
lib::interface::musicbrainz::api::{Match, MockIMusicBrainz}, lib::interface::musicbrainz::api::{Lookup, Match, MockIMusicBrainz},
testmod::COLLECTION, testmod::COLLECTION,
}; };
@ -387,12 +371,28 @@ mod tests {
} }
} }
fn mbid() -> Mbid {
"00000000-0000-0000-0000-000000000000".try_into().unwrap()
}
fn lookup_artist_requests() -> VecDeque<MbParams> {
let artist = COLLECTION[3].meta.clone();
let mbid = mbid();
VecDeque::from([MbParams::lookup_artist(artist, mbid)])
}
fn lookup_release_group_requests() -> VecDeque<MbParams> {
let album = COLLECTION[1].albums[0].meta.clone();
let mbid = mbid();
VecDeque::from([MbParams::lookup_release_group(album, mbid)])
}
fn search_artist_requests() -> VecDeque<MbParams> { fn search_artist_requests() -> VecDeque<MbParams> {
let artist = COLLECTION[3].meta.clone(); let artist = COLLECTION[3].meta.clone();
VecDeque::from([MbParams::search_artist(artist)]) VecDeque::from([MbParams::search_artist(artist)])
} }
fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) { fn search_artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
let artist = COLLECTION[3].meta.clone(); let artist = COLLECTION[3].meta.clone();
let artist_match_1 = Match::new(100, artist.clone()); let artist_match_1 = Match::new(100, artist.clone());
@ -420,7 +420,7 @@ mod tests {
mbref.unwrap().mbid().clone() mbref.unwrap().mbid().clone()
} }
fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) { fn search_album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
let album_1 = COLLECTION[1].albums[0].meta.clone(); let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone(); let album_4 = COLLECTION[1].albums[3].meta.clone();
@ -431,7 +431,7 @@ mod tests {
(album_1, matches_1) (album_1, matches_1)
} }
fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) { fn search_album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
let album_1 = COLLECTION[1].albums[0].meta.clone(); let album_1 = COLLECTION[1].albums[0].meta.clone();
let album_4 = COLLECTION[1].albums[3].meta.clone(); let album_4 = COLLECTION[1].albums[3].meta.clone();
@ -515,6 +515,90 @@ mod tests {
assert_eq!(result, Err(JobError::JobQueueEmpty)); assert_eq!(result, Err(JobError::JobQueueEmpty));
} }
fn lookup_artist_expectation(
musicbrainz: &mut MockIMusicBrainz,
mbid: &Mbid,
lookup: &Lookup<ArtistMeta>,
) {
let result = Ok(lookup.clone());
musicbrainz
.expect_lookup_artist()
.with(predicate::eq(mbid.clone()))
.times(1)
.return_once(|_| result);
}
#[test]
fn execute_lookup_artist() {
let mut musicbrainz = musicbrainz();
let mbid = mbid();
let artist = COLLECTION[3].meta.clone();
let lookup = Lookup::new(artist.clone());
lookup_artist_expectation(&mut musicbrainz, &mbid, &lookup);
let mut event_sender = event_sender();
fetch_complete_expectation(&mut event_sender, 1);
let (job_sender, job_receiver) = job_channel();
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
let requests = lookup_artist_requests();
let (result_sender, result_receiver) = mpsc::channel();
let result = job_sender.submit_foreground_job(result_sender, requests);
assert_eq!(result, Ok(()));
let result = daemon.enqueue_all_pending_jobs();
assert_eq!(result, Ok(()));
let result = daemon.execute_next_job();
assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::artist_lookup(artist, lookup)));
}
fn lookup_release_group_expectation(
musicbrainz: &mut MockIMusicBrainz,
mbid: &Mbid,
lookup: &Lookup<AlbumMeta>,
) {
let result = Ok(lookup.clone());
musicbrainz
.expect_lookup_release_group()
.with(predicate::eq(mbid.clone()))
.times(1)
.return_once(|_| result);
}
#[test]
fn execute_lookup_release_group() {
let mut musicbrainz = musicbrainz();
let mbid = mbid();
let album = COLLECTION[1].albums[0].meta.clone();
let lookup = Lookup::new(album.clone());
lookup_release_group_expectation(&mut musicbrainz, &mbid, &lookup);
let mut event_sender = event_sender();
fetch_complete_expectation(&mut event_sender, 1);
let (job_sender, job_receiver) = job_channel();
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
let requests = lookup_release_group_requests();
let (result_sender, result_receiver) = mpsc::channel();
let result = job_sender.submit_foreground_job(result_sender, requests);
assert_eq!(result, Ok(()));
let result = daemon.enqueue_all_pending_jobs();
assert_eq!(result, Ok(()));
let result = daemon.execute_next_job();
assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::album_lookup(album, lookup)));
}
fn search_artist_expectation( fn search_artist_expectation(
musicbrainz: &mut MockIMusicBrainz, musicbrainz: &mut MockIMusicBrainz,
artist: &ArtistMeta, artist: &ArtistMeta,
@ -531,7 +615,7 @@ mod tests {
#[test] #[test]
fn execute_search_artist() { fn execute_search_artist() {
let mut musicbrainz = musicbrainz(); let mut musicbrainz = musicbrainz();
let (artist, matches) = artist_expectations(); let (artist, matches) = search_artist_expectations();
search_artist_expectation(&mut musicbrainz, &artist, &matches); search_artist_expectation(&mut musicbrainz, &artist, &matches);
let mut event_sender = event_sender(); let mut event_sender = event_sender();
@ -575,8 +659,8 @@ mod tests {
fn execute_search_release_groups() { fn execute_search_release_groups() {
let mut musicbrainz = musicbrainz(); let mut musicbrainz = musicbrainz();
let arid = album_arid_expectation(); let arid = album_arid_expectation();
let (album_1, matches_1) = album_expectations_1(); let (album_1, matches_1) = search_album_expectations_1();
let (album_4, matches_4) = album_expectations_4(); let (album_4, matches_4) = search_album_expectations_4();
let mut seq = Sequence::new(); let mut seq = Sequence::new();
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1); search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
@ -613,7 +697,7 @@ mod tests {
fn execute_search_release_groups_result_disconnect() { fn execute_search_release_groups_result_disconnect() {
let mut musicbrainz = musicbrainz(); let mut musicbrainz = musicbrainz();
let arid = album_arid_expectation(); let arid = album_arid_expectation();
let (album_1, matches_1) = album_expectations_1(); let (album_1, matches_1) = search_album_expectations_1();
let mut seq = Sequence::new(); let mut seq = Sequence::new();
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1); search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
@ -643,7 +727,7 @@ mod tests {
#[test] #[test]
fn execute_search_artist_event_disconnect() { fn execute_search_artist_event_disconnect() {
let mut musicbrainz = musicbrainz(); let mut musicbrainz = musicbrainz();
let (artist, matches) = artist_expectations(); let (artist, matches) = search_artist_expectations();
search_artist_expectation(&mut musicbrainz, &artist, &matches); search_artist_expectation(&mut musicbrainz, &artist, &matches);
let mut event_sender = event_sender(); let mut event_sender = event_sender();

View File

@ -206,8 +206,8 @@ mod tests {
}; };
use crate::tui::{ use crate::tui::{
app::{AppPublic, AppPublicInner, Delta, MatchStatePublic, MissOption, SearchOption}, app::{AppPublic, AppPublicInner, Delta, MatchStatePublic},
lib::interface::musicbrainz::api::Match, lib::interface::musicbrainz::api::{Lookup, Match},
testmod::COLLECTION, testmod::COLLECTION,
tests::terminal, tests::terminal,
}; };
@ -250,20 +250,6 @@ mod tests {
} }
} }
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> MatchStateInfo {
let mut list: Vec<SearchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
list.push(SearchOption::None(MissOption::CannotHaveMbid));
list.push(SearchOption::None(MissOption::ManualInputMbid));
MatchStateInfo::artist_search(matching, list)
}
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> MatchStateInfo {
let mut list: Vec<SearchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
list.push(SearchOption::None(MissOption::CannotHaveMbid));
list.push(SearchOption::None(MissOption::ManualInputMbid));
MatchStateInfo::album_search(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();
@ -354,76 +340,92 @@ mod tests {
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }
#[test] fn artist_meta() -> ArtistMeta {
fn draw_artist_matches() { ArtistMeta::new(ArtistId::new("an artist"))
let collection = &COLLECTION;
let mut selection = Selection::new(collection);
let mut terminal = terminal();
let artist = ArtistMeta::new(ArtistId::new("an artist"));
let artist_match = Match {
score: 80,
item: artist.clone(),
disambiguation: None,
};
let list = vec![artist_match.clone(), artist_match.clone()];
let artist_matches = artist_matches(artist, list);
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Match(MatchStatePublic {
info: Some(&artist_matches),
state: &mut widget_state,
}),
input: None,
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let input = tui_input::Input::default();
app.input = Some(&input);
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }
#[test] fn artist_matches() -> MatchStateInfo {
fn draw_album_matches() { let artist = artist_meta();
let collection = &COLLECTION; let artist_match = Match::new(80, artist.clone());
let mut selection = Selection::new(collection); let list = vec![artist_match.clone(), artist_match.clone()];
let mut terminal = terminal(); let mut info = MatchStateInfo::artist_search(artist, list);
info.push_cannot_have_mbid();
info.push_manual_input_mbid();
info
}
let album = AlbumMeta::new( fn artist_lookup() -> MatchStateInfo {
let artist = artist_meta();
let artist_lookup = Lookup::new(artist.clone());
let mut info = MatchStateInfo::artist_lookup(artist, artist_lookup);
info.push_cannot_have_mbid();
info.push_manual_input_mbid();
info
}
fn album_meta() -> AlbumMeta {
AlbumMeta::new(
AlbumId::new("An Album"), AlbumId::new("An Album"),
AlbumDate::new(Some(1990), Some(5), None), AlbumDate::new(Some(1990), Some(5), None),
Some(AlbumPrimaryType::Album), Some(AlbumPrimaryType::Album),
vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation], vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation],
); )
let album_match = Match { }
score: 80,
item: album.clone(), fn album_matches() -> MatchStateInfo {
disambiguation: None, let album = album_meta();
}; let album_match = Match::new(80, album.clone());
let list = vec![album_match.clone(), album_match.clone()]; let list = vec![album_match.clone(), album_match.clone()];
let album_matches = album_matches(album, list);
let mut widget_state = WidgetState::default(); let mut info = MatchStateInfo::album_search(album, list);
widget_state.list.select(Some(0)); info.push_cannot_have_mbid();
info.push_manual_input_mbid();
info
}
let mut app = AppPublic { fn album_lookup() -> MatchStateInfo {
inner: public_inner(collection, &mut selection), let album = album_meta();
state: AppState::Match(MatchStatePublic { let album_lookup = Lookup::new(album.clone());
info: Some(&album_matches),
state: &mut widget_state,
}),
input: None,
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let input = tui_input::Input::default(); let mut info = MatchStateInfo::album_lookup(album, album_lookup);
app.input = Some(&input); info.push_cannot_have_mbid();
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); info.push_manual_input_mbid();
info
}
#[test]
fn draw_matche_state_suite() {
let collection = &COLLECTION;
let mut selection = Selection::new(collection);
let mut terminal = terminal();
let match_state_infos = vec![
artist_matches(),
album_matches(),
artist_lookup(),
album_lookup(),
];
for info in match_state_infos.iter() {
let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0));
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Match(MatchStatePublic {
info: Some(info),
state: &mut widget_state,
}),
input: None,
};
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let input = tui_input::Input::default();
app.input = Some(&input);
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
}
} }
} }