diff --git a/src/cli/sonarr/mod.rs b/src/cli/sonarr/mod.rs index b19e837..ddc9f43 100644 --- a/src/cli/sonarr/mod.rs +++ b/src/cli/sonarr/mod.rs @@ -146,6 +146,17 @@ pub enum SonarrCommand { )] season_number: i64, }, + #[command( + about = "Toggle monitoring for the specified series corresponding to the given series ID" + )] + ToggleSeriesMonitoring { + #[arg( + long, + help = "The Sonarr ID of the series to toggle monitoring on", + required = true + )] + series_id: i64, + }, } impl From for Command { @@ -290,6 +301,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' .await?; serde_json::to_string_pretty(&resp)? } + SonarrCommand::ToggleSeriesMonitoring { series_id } => { + let resp = self + .network + .handle_network_event(SonarrEvent::ToggleSeriesMonitoring(series_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/sonarr/sonarr_command_tests.rs b/src/cli/sonarr/sonarr_command_tests.rs index dd92be7..838ef30 100644 --- a/src/cli/sonarr/sonarr_command_tests.rs +++ b/src/cli/sonarr/sonarr_command_tests.rs @@ -216,6 +216,31 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn test_toggle_series_monitoring_requires_series_id() { + let result = + Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-series-monitoring"]); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_toggle_series_monitoring_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "sonarr", + "toggle-series-monitoring", + "--series-id", + "1", + ]); + + assert!(result.is_ok()); + } } mod handler { @@ -692,7 +717,7 @@ mod tests { } #[tokio::test] - async fn test_list_toggle_episode_monitoring_command() { + async fn test_toggle_episode_monitoring_command() { let expected_episode_id = 1; let mut mock_network = MockNetworkTrait::new(); mock_network @@ -722,7 +747,7 @@ mod tests { } #[tokio::test] - async fn test_list_toggle_season_monitoring_command() { + async fn test_toggle_season_monitoring_command() { let expected_series_id = 1; let expected_season_number = 1; let mut mock_network = MockNetworkTrait::new(); @@ -753,5 +778,34 @@ mod tests { assert!(result.is_ok()); } + + #[tokio::test] + async fn test_toggle_series_monitoring_command() { + let expected_series_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + SonarrEvent::ToggleSeriesMonitoring(expected_series_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Sonarr(SonarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let toggle_series_monitoring_command = SonarrCommand::ToggleSeriesMonitoring { series_id: 1 }; + + let result = SonarrCliHandler::with( + &app_arc, + toggle_series_monitoring_command, + &mut mock_network, + ) + .handle() + .await; + + assert!(result.is_ok()); + } } } diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index c37b84c..afd31d3 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -87,6 +87,7 @@ pub enum SonarrEvent { TestIndexer(i64), TestAllIndexers, ToggleSeasonMonitoring((i64, i64)), + ToggleSeriesMonitoring(i64), ToggleEpisodeMonitoring(i64), TriggerAutomaticEpisodeSearch(i64), TriggerAutomaticSeasonSearch((i64, i64)), @@ -141,7 +142,8 @@ impl NetworkResource for SonarrEvent { | SonarrEvent::GetSeriesDetails(_) | SonarrEvent::DeleteSeries(_) | SonarrEvent::EditSeries(_) - | SonarrEvent::ToggleSeasonMonitoring(_) => "/series", + | SonarrEvent::ToggleSeasonMonitoring(_) + | SonarrEvent::ToggleSeriesMonitoring(_) => "/series", SonarrEvent::SearchNewSeries(_) => "/series/lookup", SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", SonarrEvent::TestIndexer(_) => "/indexer/test", @@ -335,6 +337,10 @@ impl Network<'_, '_> { .toggle_sonarr_season_monitoring(params) .await .map(SonarrSerdeable::from), + SonarrEvent::ToggleSeriesMonitoring(series_id) => self + .toggle_sonarr_series_monitoring(series_id) + .await + .map(SonarrSerdeable::from), SonarrEvent::TriggerAutomaticSeasonSearch(params) => self .trigger_automatic_season_search(params) .await @@ -1042,6 +1048,66 @@ impl Network<'_, '_> { } } + async fn toggle_sonarr_series_monitoring(&mut self, series_id: i64) -> Result<()> { + let event = SonarrEvent::ToggleSeriesMonitoring(series_id); + + let detail_event = SonarrEvent::GetSeriesDetails(series_id); + info!("Toggling series monitoring for series with ID: {series_id}"); + info!("Fetching series details for series with ID: {series_id}"); + + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{series_id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_series_body, _| { + response = detailed_series_body.to_string() + }) + .await?; + + info!("Constructing toggle series monitoring body"); + + match serde_json::from_str::(&response) { + Ok(mut detailed_series_body) => { + let monitored = detailed_series_body + .get("monitored") + .unwrap() + .as_bool() + .unwrap(); + + *detailed_series_body.get_mut("monitored").unwrap() = json!(!monitored); + + debug!("Toggle series monitoring body: {detailed_series_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_series_body), + Some(format!("/{series_id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + Err(_) => { + warn!("Request for detailed series body was interrupted"); + Ok(()) + } + } + } + async fn get_all_sonarr_indexer_settings(&mut self) -> Result { info!("Fetching Sonarr indexer settings"); let event = SonarrEvent::GetAllIndexerSettings; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 895c475..4a729eb 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -162,7 +162,8 @@ mod test { SonarrEvent::GetSeriesDetails(0), SonarrEvent::DeleteSeries(DeleteSeriesParams::default()), SonarrEvent::EditSeries(EditSeriesParams::default()), - SonarrEvent::ToggleSeasonMonitoring((0, 0)) + SonarrEvent::ToggleSeasonMonitoring((0, 0)), + SonarrEvent::ToggleSeriesMonitoring(0), )] event: SonarrEvent, ) { @@ -5208,6 +5209,52 @@ mod test { async_toggle_server.assert_async().await; } + #[tokio::test] + async fn test_handle_toggle_series_monitoring_event() { + let mut expected_body: Value = serde_json::from_str(SERIES_JSON).unwrap(); + *expected_body.get_mut("monitored").unwrap() = json!(false); + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(SERIES_JSON).unwrap()), + None, + SonarrEvent::GetSeriesDetails(1), + Some("/1"), + None, + ) + .await; + let async_toggle_server = server + .mock( + "PUT", + format!( + "/api/v3{}/1", + SonarrEvent::ToggleSeriesMonitoring(1).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.sonarr_data.series.set_items(vec![series()]); + app.data.sonarr_data.seasons.set_items(vec![season()]); + } + app_arc.lock().await.server_tabs.next(); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::ToggleSeriesMonitoring(1)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_toggle_server.assert_async().await; + } + #[tokio::test] async fn test_handle_trigger_automatic_episode_search_event() { let (async_server, app_arc, _server) = mock_servarr_api(