From f48db09300f1e928558e4da063d17d3be2b3a947 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sat, 21 Sep 2024 22:45:03 +0200 Subject: [PATCH] Complete unit tests --- src/tui/event.rs | 2 +- .../lib/external/musicbrainz/daemon/mod.rs | 512 ++++++++++++------ .../lib/interface/musicbrainz/daemon/mod.rs | 20 +- 3 files changed, 377 insertions(+), 157 deletions(-) diff --git a/src/tui/event.rs b/src/tui/event.rs index c4b9710..34c5bb1 100644 --- a/src/tui/event.rs +++ b/src/tui/event.rs @@ -53,11 +53,11 @@ pub struct EventChannel { receiver: mpsc::Receiver, } -#[cfg_attr(test, automock)] pub trait IKeyEventSender { fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>; } +#[cfg_attr(test, automock)] pub trait IFetchCompleteEventSender { fn send_fetch_complete(&self) -> Result<(), EventError>; } diff --git a/src/tui/lib/external/musicbrainz/daemon/mod.rs b/src/tui/lib/external/musicbrainz/daemon/mod.rs index 8560acd..dbce340 100644 --- a/src/tui/lib/external/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/external/musicbrainz/daemon/mod.rs @@ -21,6 +21,7 @@ struct JobQueue { background_queue: VecDeque, } +#[derive(Debug)] struct Job { priority: JobPriority, instance: JobInstance, @@ -32,21 +33,26 @@ impl Job { } } +#[derive(Debug)] enum JobPriority { + #[cfg(test)] Foreground, Background, } +#[derive(Debug)] struct JobInstance { result_sender: ResultSender, requests: VecDeque, } +#[derive(Debug, PartialEq, Eq)] enum JobInstanceStatus { Continue, Complete, } +#[derive(Debug, PartialEq, Eq)] enum JobInstanceError { ReturnChannelDisconnected, EventChannelDisconnected, @@ -61,6 +67,7 @@ impl JobInstance { } } +#[derive(Debug, PartialEq, Eq)] enum JobError { JobQueueEmpty, EventChannelDisconnected, @@ -130,6 +137,7 @@ impl JobSender { } impl MusicBrainzDaemon { + // GRCOV_EXCL_START pub fn run( musicbrainz: MB, job_receiver: JobReceiver, @@ -163,6 +171,7 @@ impl MusicBrainzDaemon { } } } + // GRCOV_EXCL_STOP fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> { loop { @@ -178,7 +187,7 @@ impl MusicBrainzDaemon { fn execute_next_job(&mut self) -> Result<(), JobError> { if let Some(instance) = self.job_queue.front_mut() { - let result = instance.execute_next(&mut self.musicbrainz, &mut self.event_sender); + let result = instance.execute_next(&mut *self.musicbrainz, &mut *self.event_sender); match result { Ok(JobInstanceStatus::Continue) => {} Ok(JobInstanceStatus::Complete) @@ -209,8 +218,8 @@ impl MusicBrainzDaemon { impl JobInstance { fn execute_next( &mut self, - musicbrainz: &mut Box, - event_sender: &mut Box, + musicbrainz: &mut dyn IMusicBrainz, + event_sender: &mut dyn IFetchCompleteEventSender, ) -> Result { // self.requests can be empty if the caller submits an empty job. if let Some(params) = self.requests.pop_front() { @@ -226,8 +235,8 @@ impl JobInstance { fn execute( &mut self, - musicbrainz: &mut Box, - event_sender: &mut Box, + musicbrainz: &mut dyn IMusicBrainz, + event_sender: &mut dyn IFetchCompleteEventSender, api_params: MbParams, ) -> Result<(), JobInstanceError> { match api_params { @@ -248,7 +257,7 @@ impl JobInstance { fn return_result( &mut self, - event_sender: &mut Box, + event_sender: &mut dyn IFetchCompleteEventSender, result: Result, ) -> Result<(), JobInstanceError> { self.result_sender @@ -291,6 +300,7 @@ impl JobQueue { fn push_back(&mut self, job: Job) { match job.priority { + #[cfg(test)] JobPriority::Foreground => self.foreground_queue.push_back(job.instance), JobPriority::Background => self.background_queue.push_back(job.instance), } @@ -299,190 +309,388 @@ impl JobQueue { #[cfg(test)] mod tests { - // fn event_channel() -> (EventSender, EventReceiver) { - // let event_channel = EventChannel::new(); - // let events_tx = event_channel.sender(); - // let events_rx = event_channel.receiver(); - // (events_tx, events_rx) - // } + use mockall::{predicate, Sequence}; + use musichoard::collection::{ + album::AlbumMeta, + artist::ArtistMeta, + musicbrainz::{IMusicBrainzRef, Mbid}, + }; + use crate::tui::{ + event::{Event, EventError, MockIFetchCompleteEventSender}, + lib::interface::musicbrainz::api::{Match, MockIMusicBrainz}, + testmod::COLLECTION, + }; - // fn album_expectations_1() -> (AlbumMeta, Vec>) { - // let album_1 = COLLECTION[1].albums[0].meta.clone(); - // let album_4 = COLLECTION[1].albums[3].meta.clone(); + use super::*; - // let album_match_1_1 = Match::new(100, album_1.clone()); - // let album_match_1_2 = Match::new(50, album_4.clone()); - // let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()]; + fn musicbrainz() -> MockIMusicBrainz { + MockIMusicBrainz::new() + } - // (album_1, matches_1) - // } + fn job_channel() -> (JobSender, JobReceiver) { + let channel = JobChannel::new(); + let sender = channel.sender(); + let receiver = channel.receiver(); + (sender, receiver) + } - // fn album_expectations_4() -> (AlbumMeta, Vec>) { - // let album_1 = COLLECTION[1].albums[0].meta.clone(); - // let album_4 = COLLECTION[1].albums[3].meta.clone(); + fn event_sender() -> MockIFetchCompleteEventSender { + MockIFetchCompleteEventSender::new() + } - // let album_match_4_1 = Match::new(100, album_4.clone()); - // let album_match_4_2 = Match::new(30, album_1.clone()); - // let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()]; + fn fetch_complete_expectation(event_sender: &mut MockIFetchCompleteEventSender, times: usize) { + event_sender + .expect_send_fetch_complete() + .times(times) + .returning(|| Ok(())); + } - // (album_4, matches_4) - // } + fn daemon(job_receiver: JobReceiver) -> MusicBrainzDaemon { + MusicBrainzDaemon { + musicbrainz: Box::new(musicbrainz()), + job_receiver: job_receiver.receiver, + job_queue: JobQueue::new(), + event_sender: Box::new(event_sender()), + } + } - // fn search_release_group_expectation( - // api: &mut MockIMusicBrainz, - // seq: &mut Sequence, - // arid: &Mbid, - // album: &AlbumMeta, - // matches: &[Match], - // ) { - // let result = Ok(matches.to_owned()); - // api.expect_search_release_group() - // .with(predicate::eq(arid.clone()), predicate::eq(album.clone())) - // .times(1) - // .in_sequence(seq) - // .return_once(|_, _| result); - // } + fn daemon_with( + musicbrainz: MockIMusicBrainz, + job_receiver: JobReceiver, + event_sender: MockIFetchCompleteEventSender, + ) -> MusicBrainzDaemon { + MusicBrainzDaemon { + musicbrainz: Box::new(musicbrainz), + job_receiver: job_receiver.receiver, + job_queue: JobQueue::new(), + event_sender: Box::new(event_sender), + } + } - // #[test] - // fn fetch_albums() { - // let mut mb_api = MockIMusicBrainz::new(); + fn search_artist_requests() -> VecDeque { + let artist = COLLECTION[3].meta.clone(); + VecDeque::from([MbParams::search_artist(artist)]) + } - // let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + fn artist_expectations() -> (ArtistMeta, Vec>) { + let artist = COLLECTION[3].meta.clone(); - // let (album_1, matches_1) = album_expectations_1(); - // let (album_4, matches_4) = album_expectations_4(); + let artist_match_1 = Match::new(100, artist.clone()); + let artist_match_2 = Match::new(50, artist.clone()); + let matches = vec![artist_match_1.clone(), artist_match_2.clone()]; - // // Other albums have an MBID and so they will be skipped. - // let mut seq = Sequence::new(); + (artist, matches) + } - // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); - // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4); + fn search_albums_requests() -> VecDeque { + let mbref = COLLECTION[1].meta.musicbrainz.as_ref(); + let arid = mbref.unwrap().mbid().clone(); - // let music_hoard = music_hoard(COLLECTION.to_owned()); - // let (events_tx, events_rx) = event_channel(); - // let inner = AppInner::new(music_hoard, mb_api, events_tx); + let album_1 = COLLECTION[1].albums[0].meta.clone(); + let album_4 = COLLECTION[1].albums[3].meta.clone(); - // let (fetch_tx, fetch_rx) = mpsc::channel(); - // // Use the second artist for this test. - // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); - // handle.join().unwrap(); + VecDeque::from([ + MbParams::search_release_group(arid.clone(), album_1), + MbParams::search_release_group(arid.clone(), album_4), + ]) + } - // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); - // let result = fetch_rx.try_recv().unwrap(); - // let expected = Ok(MatchStateInfo::Album(AlbumMatches { - // matching: album_1.clone(), - // list: matches_1.iter().cloned().map(Into::into).collect(), - // })); - // assert_eq!(result, expected); + fn album_arid_expectation() -> Mbid { + let mbref = COLLECTION[1].meta.musicbrainz.as_ref(); + mbref.unwrap().mbid().clone() + } - // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); - // let result = fetch_rx.try_recv().unwrap(); - // let expected = Ok(MatchStateInfo::Album(AlbumMatches { - // matching: album_4.clone(), - // list: matches_4.iter().cloned().map(Into::into).collect(), - // })); - // assert_eq!(result, expected); - // } + fn album_expectations_1() -> (AlbumMeta, Vec>) { + let album_1 = COLLECTION[1].albums[0].meta.clone(); + let album_4 = COLLECTION[1].albums[3].meta.clone(); - // fn artist_expectations() -> (ArtistMeta, Vec>) { - // let artist = COLLECTION[3].meta.clone(); + let album_match_1_1 = Match::new(100, album_1.clone()); + let album_match_1_2 = Match::new(50, album_4.clone()); + let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()]; - // let artist_match_1 = Match::new(100, artist.clone()); - // let artist_match_2 = Match::new(50, artist.clone()); - // let matches = vec![artist_match_1.clone(), artist_match_2.clone()]; + (album_1, matches_1) + } - // (artist, matches) - // } + fn album_expectations_4() -> (AlbumMeta, Vec>) { + let album_1 = COLLECTION[1].albums[0].meta.clone(); + let album_4 = COLLECTION[1].albums[3].meta.clone(); - // fn search_artist_expectation( - // api: &mut MockIMusicBrainz, - // seq: &mut Sequence, - // artist: &ArtistMeta, - // matches: &[Match], - // ) { - // let result = Ok(matches.to_owned()); - // api.expect_search_artist() - // .with(predicate::eq(artist.clone())) - // .times(1) - // .in_sequence(seq) - // .return_once(|_| result); - // } + let album_match_4_1 = Match::new(100, album_4.clone()); + let album_match_4_2 = Match::new(30, album_1.clone()); + let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()]; - // #[test] - // fn fetch_artist() { - // let mut mb_api = MockIMusicBrainz::new(); + (album_4, matches_4) + } - // let (artist, matches) = artist_expectations(); - // let mut seq = Sequence::new(); - // search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + #[test] + fn enqueue_job_channel_disconnected() { + let (_, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); + let result = daemon.enqueue_all_pending_jobs(); + assert_eq!(result, Err(Error::JobChannelDisconnected)); + } - // let music_hoard = music_hoard(COLLECTION.to_owned()); - // let (events_tx, events_rx) = event_channel(); - // let inner = AppInner::new(music_hoard, mb_api, events_tx); + #[test] + fn wait_for_job_channel_disconnected() { + let (_, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); + let result = daemon.wait_for_jobs(); + assert_eq!(result, Err(Error::JobChannelDisconnected)); + } - // let (fetch_tx, fetch_rx) = mpsc::channel(); - // // Use the fourth artist for this test as they have no MBID. - // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); - // handle.join().unwrap(); + #[test] + fn enqueue_job_queue_empty() { + let (_job_sender, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); + let result = daemon.enqueue_all_pending_jobs(); + assert_eq!(result, Ok(())); + } - // assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); - // let result = fetch_rx.try_recv().unwrap(); - // let expected = Ok(MatchStateInfo::Artist(ArtistMatches { - // matching: artist.clone(), - // list: matches.iter().cloned().map(Into::into).collect(), - // })); - // assert_eq!(result, expected); - // } + #[test] + fn enqueue_job() { + let (job_sender, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); - // #[test] - // fn fetch_artist_fetch_disconnect() { - // let mut mb_api = MockIMusicBrainz::new(); + let requests = search_artist_requests(); + let (result_sender, _) = mpsc::channel(); + let result = job_sender.submit_background_job(result_sender, requests.clone()); + assert_eq!(result, Ok(())); - // let (artist, matches) = artist_expectations(); - // let mut seq = Sequence::new(); - // search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); + let result = daemon.enqueue_all_pending_jobs(); + assert_eq!(result, Ok(())); - // let music_hoard = music_hoard(COLLECTION.to_owned()); - // let (events_tx, events_rx) = event_channel(); - // let inner = AppInner::new(music_hoard, mb_api, events_tx); + assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests); + } - // let (fetch_tx, _) = mpsc::channel(); - // // Use the fourth artist for this test as they have no MBID. - // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); - // handle.join().unwrap(); + #[test] + fn wait_for_jobs_job() { + let (job_sender, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); - // assert!(events_rx.try_recv().is_err()); - // } + let requests = search_artist_requests(); + let (result_sender, _) = mpsc::channel(); + let result = job_sender.submit_background_job(result_sender, requests.clone()); + assert_eq!(result, Ok(())); - // #[test] - // fn fetch_albums_event_disconnect() { - // let mut mb_api = MockIMusicBrainz::new(); + let result = daemon.wait_for_jobs(); + assert_eq!(result, Ok(())); - // let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); + assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests); + } - // let (album_1, matches_1) = album_expectations_1(); + #[test] + fn execute_empty() { + let (_job_sender, job_receiver) = job_channel(); + let mut daemon = daemon(job_receiver); - // let mut seq = Sequence::new(); - // search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); + let (result_sender, _) = mpsc::channel(); + let mut instance = JobInstance::new(result_sender, VecDeque::new()); + let result = instance.execute_next(&mut musicbrainz(), &mut event_sender()); + assert_eq!(result, Ok(JobInstanceStatus::Complete)); - // let music_hoard = music_hoard(COLLECTION.to_owned()); - // let (events_tx, _) = event_channel(); - // let inner = AppInner::new(music_hoard, mb_api, events_tx); + let result = daemon.enqueue_all_pending_jobs(); + assert_eq!(result, Ok(())); - // let (fetch_tx, fetch_rx) = mpsc::channel(); - // // Use the second artist for this test. - // let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); - // handle.join().unwrap(); + let result = daemon.execute_next_job(); + assert_eq!(result, Err(JobError::JobQueueEmpty)); + } - // let result = fetch_rx.try_recv().unwrap(); - // let expected = Ok(MatchStateInfo::Album(AlbumMatches { - // matching: album_1.clone(), - // list: matches_1.iter().cloned().map(Into::into).collect(), - // })); - // assert_eq!(result, expected); + fn search_artist_expectation( + musicbrainz: &mut MockIMusicBrainz, + artist: &ArtistMeta, + matches: &[Match], + ) { + let result = Ok(matches.to_owned()); + musicbrainz + .expect_search_artist() + .with(predicate::eq(artist.clone())) + .times(1) + .return_once(|_| result); + } - // assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected); - // } + #[test] + fn execute_search_artist() { + let mut musicbrainz = musicbrainz(); + let (artist, matches) = artist_expectations(); + search_artist_expectation(&mut musicbrainz, &artist, &matches); + 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 = search_artist_requests(); + let (result_sender, result_receiver) = mpsc::channel(); + let result = job_sender.submit_background_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(artist, matches))); + } + + fn search_release_group_expectation( + musicbrainz: &mut MockIMusicBrainz, + seq: &mut Sequence, + arid: &Mbid, + album: &AlbumMeta, + matches: &[Match], + ) { + let result = Ok(matches.to_owned()); + musicbrainz + .expect_search_release_group() + .with(predicate::eq(arid.clone()), predicate::eq(album.clone())) + .times(1) + .in_sequence(seq) + .return_once(|_, _| result); + } + + #[test] + fn execute_search_release_groups() { + let mut musicbrainz = musicbrainz(); + let arid = album_arid_expectation(); + let (album_1, matches_1) = album_expectations_1(); + let (album_4, matches_4) = album_expectations_4(); + + 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_4, &matches_4); + + let mut event_sender = event_sender(); + fetch_complete_expectation(&mut event_sender, 2); + + let (job_sender, job_receiver) = job_channel(); + let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender); + + let requests = search_albums_requests(); + let (result_sender, result_receiver) = mpsc::channel(); + let result = job_sender.submit_background_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 = daemon.execute_next_job(); + assert_eq!(result, Ok(())); + + let result = result_receiver.try_recv().unwrap(); + assert_eq!(result, Ok(MatchStateInfo::album(album_1, matches_1))); + + let result = result_receiver.try_recv().unwrap(); + assert_eq!(result, Ok(MatchStateInfo::album(album_4, matches_4))); + } + + #[test] + fn execute_search_release_groups_result_disconnect() { + let mut musicbrainz = musicbrainz(); + let arid = album_arid_expectation(); + let (album_1, matches_1) = album_expectations_1(); + + let mut seq = Sequence::new(); + search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1); + + let mut event_sender = event_sender(); + fetch_complete_expectation(&mut event_sender, 0); + + let (job_sender, job_receiver) = job_channel(); + let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender); + + let requests = search_albums_requests(); + let (result_sender, _) = mpsc::channel(); + let result = job_sender.submit_background_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(())); + + // Two albums were submitted, but as one job. If the first one fails due to the + // result_channel disconnecting, all remaining requests in that job are dropped. + assert!(daemon.job_queue.pop_front().is_none()); + } + + #[test] + fn execute_search_artist_event_disconnect() { + let mut musicbrainz = musicbrainz(); + let (artist, matches) = artist_expectations(); + search_artist_expectation(&mut musicbrainz, &artist, &matches); + + let mut event_sender = event_sender(); + event_sender + .expect_send_fetch_complete() + .times(1) + .return_once(|| Err(EventError::Send(Event::FetchComplete))); + + let (job_sender, job_receiver) = job_channel(); + let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender); + + let requests = search_artist_requests(); + let (result_sender, _result_receiver) = mpsc::channel(); + let result = job_sender.submit_background_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, Err(JobError::EventChannelDisconnected)); + } + + #[test] + fn job_queue() { + let mut queue = JobQueue::new(); + assert!(queue.is_empty()); + assert_eq!(queue.foreground_queue.len(), 0); + assert_eq!(queue.background_queue.len(), 0); + + let (result_sender, _) = mpsc::channel(); + let bg = Job::new( + JobPriority::Background, + JobInstance::new(result_sender, VecDeque::new()), + ); + queue.push_back(bg); + + assert!(!queue.is_empty()); + assert_eq!(queue.foreground_queue.len(), 0); + assert_eq!(queue.background_queue.len(), 1); + + let (result_sender, _) = mpsc::channel(); + let fg = Job::new( + JobPriority::Foreground, + JobInstance::new(result_sender, VecDeque::new()), + ); + queue.push_back(fg); + + assert!(!queue.is_empty()); + assert_eq!(queue.foreground_queue.len(), 1); + assert_eq!(queue.background_queue.len(), 1); + + let instance = queue.pop_front(); + assert!(instance.is_some()); + assert!(!queue.is_empty()); + assert_eq!(queue.foreground_queue.len(), 0); + assert_eq!(queue.background_queue.len(), 1); + + let instance = queue.pop_front(); + assert!(instance.is_some()); + assert!(queue.is_empty()); + assert_eq!(queue.foreground_queue.len(), 0); + assert_eq!(queue.background_queue.len(), 0); + + let instance = queue.pop_front(); + assert!(instance.is_none()); + assert!(queue.is_empty()); + } } diff --git a/src/tui/lib/interface/musicbrainz/daemon/mod.rs b/src/tui/lib/interface/musicbrainz/daemon/mod.rs index 1107d00..7647f99 100644 --- a/src/tui/lib/interface/musicbrainz/daemon/mod.rs +++ b/src/tui/lib/interface/musicbrainz/daemon/mod.rs @@ -7,6 +7,7 @@ use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as #[cfg(test)] use mockall::automock; +#[derive(Debug, PartialEq, Eq)] pub enum Error { EventChannelDisconnected, JobChannelDisconnected, @@ -33,23 +34,23 @@ pub trait IMbJobSender { ) -> Result<(), Error>; } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum MbParams { Search(SearchParams), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SearchParams { Artist(SearchArtistParams), ReleaseGroup(SearchReleaseGroupParams), } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SearchArtistParams { pub artist: ArtistMeta, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SearchReleaseGroupParams { pub arid: Mbid, pub album: AlbumMeta, @@ -67,3 +68,14 @@ impl MbParams { })) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn errors() { + assert!(!format!("{}", Error::EventChannelDisconnected).is_empty()); + assert!(!format!("{}", Error::JobChannelDisconnected).is_empty()); + } +}