From 1193b8c8484595d49c2ff043de12f6c1f2304f85 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 18 Dec 2024 00:49:36 -0700 Subject: [PATCH] fix(sonarr): Pass the search query directly to the networking channel when searching for a new series --- src/app/sonarr/mod.rs | 15 +++- src/app/sonarr/sonarr_tests.rs | 22 ++++- src/cli/sonarr/mod.rs | 2 +- src/cli/sonarr/sonarr_command_tests.rs | 2 +- src/network/sonarr_network.rs | 83 ++++++------------- src/network/sonarr_network_tests.rs | 106 ++----------------------- 6 files changed, 70 insertions(+), 160 deletions(-) diff --git a/src/app/sonarr/mod.rs b/src/app/sonarr/mod.rs index 3b3b0e1..9198335 100644 --- a/src/app/sonarr/mod.rs +++ b/src/app/sonarr/mod.rs @@ -165,7 +165,9 @@ impl<'a> App<'a> { } ActiveSonarrBlock::AddSeriesSearchResults => { self - .dispatch_network_event(SonarrEvent::SearchNewSeries(None).into()) + .dispatch_network_event( + SonarrEvent::SearchNewSeries(self.extract_add_new_series_search_query().await).into(), + ) .await; } ActiveSonarrBlock::SystemUpdates => { @@ -287,4 +289,15 @@ impl<'a> App<'a> { .season_number; (series_id, season_number) } + + async fn extract_add_new_series_search_query(&self) -> String { + self + .data + .sonarr_data + .add_series_search + .as_ref() + .expect("Add series search is empty") + .text + .clone() + } } diff --git a/src/app/sonarr/sonarr_tests.rs b/src/app/sonarr/sonarr_tests.rs index 7266ab8..4981bb1 100644 --- a/src/app/sonarr/sonarr_tests.rs +++ b/src/app/sonarr/sonarr_tests.rs @@ -532,6 +532,7 @@ mod tests { #[tokio::test] async fn test_dispatch_by_add_series_search_results_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.add_series_search = Some("test search".into()); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults) @@ -540,7 +541,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::SearchNewSeries(None).into() + SonarrEvent::SearchNewSeries("test search".into()).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -829,6 +830,25 @@ mod tests { assert_eq!(app.extract_series_id_season_number_tuple().await, (1, 1)); } + #[tokio::test] + async fn test_extract_add_new_series_search_query() { + let mut app = App::default(); + app.data.sonarr_data.add_series_search = Some("test search".into()); + + assert_str_eq!( + app.extract_add_new_series_search_query().await, + "test search" + ); + } + + #[tokio::test] + #[should_panic(expected = "Add series search is empty")] + async fn test_extract_add_new_series_search_query_panics_when_the_query_is_not_set() { + let app = App::default(); + + app.extract_add_new_series_search_query().await; + } + fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); let mut app = App { diff --git a/src/cli/sonarr/mod.rs b/src/cli/sonarr/mod.rs index 3a19110..946fa9f 100644 --- a/src/cli/sonarr/mod.rs +++ b/src/cli/sonarr/mod.rs @@ -245,7 +245,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' SonarrCommand::SearchNewSeries { query } => { let resp = self .network - .handle_network_event(SonarrEvent::SearchNewSeries(Some(query)).into()) + .handle_network_event(SonarrEvent::SearchNewSeries(query).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/sonarr_command_tests.rs b/src/cli/sonarr/sonarr_command_tests.rs index c4fc0f1..c973713 100644 --- a/src/cli/sonarr/sonarr_command_tests.rs +++ b/src/cli/sonarr/sonarr_command_tests.rs @@ -597,7 +597,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::SearchNewSeries(Some(expected_search_query)).into(), + SonarrEvent::SearchNewSeries(expected_search_query).into(), )) .times(1) .returning(|_| { diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 01871ef..db7106b 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -82,7 +82,7 @@ pub enum SonarrEvent { HealthCheck, ListSeries, MarkHistoryItemAsFailed(i64), - SearchNewSeries(Option), + SearchNewSeries(String), StartTask(Option), TestIndexer(Option), TestAllIndexers, @@ -1995,65 +1995,34 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn search_sonarr_series( - &mut self, - query: Option, - ) -> Result> { + async fn search_sonarr_series(&mut self, query: String) -> Result> { info!("Searching for specific Sonarr series"); - let event = SonarrEvent::SearchNewSeries(None); - let search = if let Some(search_query) = query { - Ok(search_query.into()) - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .add_series_search - .clone() - .ok_or(anyhow!("Encountered a race condition")) - }; + let event = SonarrEvent::SearchNewSeries(query.clone()); - match search { - Ok(search_string) => { - let request_props = self - .request_props_from( - event, - RequestMethod::Get, - None::<()>, - None, - Some(format!("term={}", encode(&search_string.text))), - ) - .await; + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + None, + Some(format!("term={}", encode(&query))), + ) + .await; - self - .handle_request::<(), Vec>(request_props, |series_vec, mut app| { - if series_vec.is_empty() { - app.pop_and_push_navigation_stack( - ActiveSonarrBlock::AddSeriesEmptySearchResults.into(), - ); - } else if let Some(add_searched_seriess) = - app.data.sonarr_data.add_searched_series.as_mut() - { - add_searched_seriess.set_items(series_vec); - } else { - let mut add_searched_seriess = StatefulTable::default(); - add_searched_seriess.set_items(series_vec); - app.data.sonarr_data.add_searched_series = Some(add_searched_seriess); - } - }) - .await - } - Err(e) => { - warn!( - "Encountered a race condition: {e}\n \ - This is most likely caused by the user trying to navigate between modals rapidly. \ - Ignoring search request." - ); - Ok(Vec::default()) - } - } + self + .handle_request::<(), Vec>(request_props, |series_vec, mut app| { + if series_vec.is_empty() { + app.pop_and_push_navigation_stack(ActiveSonarrBlock::AddSeriesEmptySearchResults.into()); + } else if let Some(add_searched_seriess) = app.data.sonarr_data.add_searched_series.as_mut() + { + add_searched_seriess.set_items(series_vec); + } else { + let mut add_searched_seriess = StatefulTable::default(); + add_searched_seriess.set_items(series_vec); + app.data.sonarr_data.add_searched_series = Some(add_searched_seriess); + } + }) + .await } async fn start_sonarr_task(&mut self, task: Option) -> Result { diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index ab27672..efb6d17 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -5,7 +5,7 @@ mod test { use bimap::BiMap; use chrono::DateTime; use indoc::formatdoc; - use mockito::{Matcher, Server}; + use mockito::Matcher; use pretty_assertions::{assert_eq, assert_str_eq}; use reqwest::Client; use rstest::rstest; @@ -19,7 +19,7 @@ mod test { DownloadStatus, EditSeriesParams, IndexerSettings, MonitorEpisodeBody, SonarrHistoryEventType, }; - use crate::app::{App, ServarrConfig}; + use crate::app::App; use crate::models::radarr_models::IndexerTestResult; use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::sonarr::modals::{ @@ -286,7 +286,7 @@ mod test { #[case(SonarrEvent::GetTasks, "/system/task")] #[case(SonarrEvent::GetUpdates, "/update")] #[case(SonarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")] - #[case(SonarrEvent::SearchNewSeries(None), "/series/lookup")] + #[case(SonarrEvent::SearchNewSeries(String::new()), "/series/lookup")] #[case(SonarrEvent::TestIndexer(None), "/indexer/test")] #[case(SonarrEvent::TestAllIndexers, "/indexer/testall")] #[case(SonarrEvent::ToggleEpisodeMonitoring(None), "/episode/monitor")] @@ -4682,7 +4682,7 @@ mod test { None, Some(add_series_search_result_json), None, - SonarrEvent::SearchNewSeries(None), + SonarrEvent::SearchNewSeries("test term".into()), None, Some("term=test%20term"), ) @@ -4691,7 +4691,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::AddSeriesSearchResults(add_series_search_results) = network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) + .handle_sonarr_event(SonarrEvent::SearchNewSeries("test term".into())) .await .unwrap() { @@ -4719,43 +4719,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_search_new_series_event_uses_provided_query() { - let add_series_search_result_json = json!([{ - "tvdbId": 1234, - "title": "Test", - "status": "continuing", - "ended": false, - "overview": "New series blah blah blah", - "genres": ["cool", "family", "fun"], - "year": 2023, - "network": "Prime Video", - "runtime": 60, - "ratings": { "votes": 406744, "value": 8.4 }, - "statistics": { "seasonCount": 3 } - }]); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(add_series_search_result_json), - None, - SonarrEvent::SearchNewSeries(None), - None, - Some("term=test%20term"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::AddSeriesSearchResults(add_series_search_results) = network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(Some("test term".into()))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(add_series_search_results, vec![add_series_search_result()]); - } - } - #[tokio::test] async fn test_handle_search_new_series_event_no_results() { let (async_server, app_arc, _server) = mock_servarr_api( @@ -4763,7 +4726,7 @@ mod test { None, Some(json!([])), None, - SonarrEvent::SearchNewSeries(None), + SonarrEvent::SearchNewSeries("test term".into()), None, Some("term=test%20term"), ) @@ -4772,7 +4735,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) + .handle_sonarr_event(SonarrEvent::SearchNewSeries("test term".into())) .await .is_ok()); @@ -4790,61 +4753,6 @@ mod test { ); } - #[tokio::test] - async fn test_handle_search_new_series_event_no_panic_on_race_condition() { - let resource = format!( - "{}?term=test%20term", - SonarrEvent::SearchNewSeries(None).resource() - ); - let mut server = Server::new_async().await; - let mut async_server = server - .mock( - &RequestMethod::Get.to_string().to_uppercase(), - format!("/api/v3{resource}").as_str(), - ) - .match_header("X-Api-Key", "test1234"); - async_server = async_server.expect_at_most(0).create_async().await; - - let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); - let port = Some( - server.host_with_port().split(':').collect::>()[1] - .parse() - .unwrap(), - ); - let mut app = App::default(); - let sonarr_config = ServarrConfig { - host, - port, - api_token: "test1234".to_owned(), - ..ServarrConfig::default() - }; - app.config.sonarr = Some(sonarr_config); - let app_arc = Arc::new(Mutex::new(app)); - app_arc - .lock() - .await - .push_navigation_stack(ActiveSonarrBlock::Series.into()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .add_searched_series - .is_none()); - assert_eq!( - app_arc.lock().await.get_current_route(), - ActiveSonarrBlock::Series.into() - ); - } - #[tokio::test] async fn test_handle_start_sonarr_task_event() { let response = json!({ "test": "test"});