diff --git a/src/cli/readarr/mod.rs b/src/cli/readarr/mod.rs index 1b8ac8f5..0fdafc66 100644 --- a/src/cli/readarr/mod.rs +++ b/src/cli/readarr/mod.rs @@ -7,6 +7,8 @@ use list_command_handler::{ReadarrListCommand, ReadarrListCommandHandler}; use tokio::sync::Mutex; use super::{CliCommandHandler, Command}; +use crate::models::readarr_models::ReadarrTaskName; +use crate::network::readarr_network::ReadarrEvent; use crate::{app::App, network::NetworkTrait}; mod get_command_handler; @@ -28,6 +30,16 @@ pub enum ReadarrCommand { about = "Commands to list attributes from your Readarr instance" )] List(ReadarrListCommand), + #[command(about = "Start the specified Readarr task")] + StartTask { + #[arg( + long, + help = "The name of the task to trigger", + value_enum, + required = true + )] + task_name: ReadarrTaskName, + }, } impl From for Command { @@ -67,6 +79,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrCommand> for ReadarrCliHandler<'a, .handle() .await? } + ReadarrCommand::StartTask { task_name } => { + let resp = self + .network + .handle_network_event(ReadarrEvent::StartTask(task_name).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/readarr/readarr_command_tests.rs b/src/cli/readarr/readarr_command_tests.rs index f07a7d9b..00cb2582 100644 --- a/src/cli/readarr/readarr_command_tests.rs +++ b/src/cli/readarr/readarr_command_tests.rs @@ -2,6 +2,8 @@ mod tests { mod cli { use clap::CommandFactory; + use clap::error::ErrorKind; + use pretty_assertions::assert_eq; use crate::Cli; @@ -11,5 +13,89 @@ mod tests { assert_err!(&result); } + + #[test] + fn test_start_task_requires_task_name() { + let result = Cli::command().try_get_matches_from(["managarr", "readarr", "start-task"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_start_task_task_name_validation() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "readarr", + "start-task", + "--task-name", + "test", + ]); + + assert_err!(&result); + assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); + } + + #[test] + fn test_start_task_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "readarr", + "start-task", + "--task-name", + "check-health", + ]); + + assert_ok!(&result); + } + } + + mod handler { + use std::sync::Arc; + + use mockall::predicate::eq; + use serde_json::json; + use tokio::sync::Mutex; + + use crate::models::readarr_models::{ReadarrSerdeable, ReadarrTaskName}; + use crate::{ + app::App, + cli::{ + CliCommandHandler, + readarr::{ReadarrCliHandler, ReadarrCommand}, + }, + models::Serdeable, + network::{MockNetworkTrait, NetworkEvent, readarr_network::ReadarrEvent}, + }; + + #[tokio::test] + async fn test_start_task_command() { + let expected_task_name = ReadarrTaskName::CheckHealth; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + ReadarrEvent::StartTask(expected_task_name).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Readarr(ReadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let start_task_command = ReadarrCommand::StartTask { + task_name: ReadarrTaskName::CheckHealth, + }; + + let result = ReadarrCliHandler::with(&app_arc, start_task_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } } } diff --git a/src/network/readarr_network/mod.rs b/src/network/readarr_network/mod.rs index 6e30e081..3c0b233a 100644 --- a/src/network/readarr_network/mod.rs +++ b/src/network/readarr_network/mod.rs @@ -2,7 +2,7 @@ use anyhow::Result; use log::info; use super::{NetworkEvent, NetworkResource}; -use crate::models::readarr_models::ReadarrSerdeable; +use crate::models::readarr_models::{ReadarrSerdeable, ReadarrTaskName}; use crate::network::{Network, RequestMethod}; mod system; @@ -21,11 +21,13 @@ pub enum ReadarrEvent { GetTasks, GetUpdates, HealthCheck, + StartTask(ReadarrTaskName), } impl NetworkResource for ReadarrEvent { fn resource(&self) -> &'static str { match &self { + ReadarrEvent::StartTask(_) => "/command", ReadarrEvent::GetHostConfig | ReadarrEvent::GetSecurityConfig => "/config/host", ReadarrEvent::GetDiskSpace => "/diskspace", ReadarrEvent::HealthCheck => "/health", @@ -72,6 +74,10 @@ impl Network<'_, '_> { .get_readarr_healthcheck() .await .map(ReadarrSerdeable::from), + ReadarrEvent::StartTask(task_name) => self + .start_readarr_task(task_name) + .await + .map(ReadarrSerdeable::from), } } diff --git a/src/network/readarr_network/readarr_network_tests.rs b/src/network/readarr_network/readarr_network_tests.rs index 96ecf18d..d845e79f 100644 --- a/src/network/readarr_network/readarr_network_tests.rs +++ b/src/network/readarr_network/readarr_network_tests.rs @@ -6,6 +6,7 @@ mod tests { use rstest::rstest; #[rstest] + #[case(ReadarrEvent::StartTask(Default::default()), "/command")] #[case(ReadarrEvent::GetHostConfig, "/config/host")] #[case(ReadarrEvent::GetSecurityConfig, "/config/host")] #[case(ReadarrEvent::GetDiskSpace, "/diskspace")] diff --git a/src/network/readarr_network/system/mod.rs b/src/network/readarr_network/system/mod.rs index 4f28dcb2..a20bdac8 100644 --- a/src/network/readarr_network/system/mod.rs +++ b/src/network/readarr_network/system/mod.rs @@ -1,6 +1,6 @@ -use crate::models::readarr_models::ReadarrTask; +use crate::models::readarr_models::{ReadarrTask, ReadarrTaskName}; use crate::models::servarr_models::{ - DiskSpace, HostConfig, LogResponse, SecurityConfig, SystemStatus, Update, + CommandBody, DiskSpace, HostConfig, LogResponse, SecurityConfig, SystemStatus, Update, }; use crate::models::{HorizontallyScrollableText, Scrollable, ScrollableText}; use crate::network::readarr_network::ReadarrEvent; @@ -8,6 +8,7 @@ use crate::network::{Network, RequestMethod}; use anyhow::Result; use indoc::formatdoc; use log::info; +use serde_json::Value; #[cfg(test)] #[path = "readarr_system_network_tests.rs"] @@ -231,4 +232,23 @@ impl Network<'_, '_> { }) .await } + + pub(in crate::network::readarr_network) async fn start_readarr_task( + &mut self, + task: ReadarrTaskName, + ) -> Result { + let event = ReadarrEvent::StartTask(task); + let task_name = task.to_string(); + info!("Starting Readarr task: {task_name}"); + + let body = CommandBody { name: task_name }; + + let request_props = self + .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } } diff --git a/src/network/readarr_network/system/readarr_system_network_tests.rs b/src/network/readarr_network/system/readarr_system_network_tests.rs index 5a50a7c2..871c9b92 100644 --- a/src/network/readarr_network/system/readarr_system_network_tests.rs +++ b/src/network/readarr_network/system/readarr_system_network_tests.rs @@ -514,4 +514,51 @@ mod tests { assert_err!(result); assert_is_empty!(app.lock().await.data.readarr_data.updates.get_text()); } + + #[tokio::test] + async fn test_handle_start_readarr_task_event() { + let response = json!({ "test": "test" }); + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "CheckHealth" + })) + .returns(response.clone()) + .build_for(ReadarrEvent::StartTask(ReadarrTaskName::CheckHealth)) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::StartTask(ReadarrTaskName::CheckHealth)) + .await; + + mock.assert_async().await; + + let ReadarrSerdeable::Value(value) = result.unwrap() else { + panic!("Expected Value") + }; + + assert_eq!(value, response); + } + + #[tokio::test] + async fn test_handle_start_readarr_task_event_failure() { + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "CheckHealth" + })) + .returns(json!({})) + .status(500) + .build_for(ReadarrEvent::StartTask(ReadarrTaskName::CheckHealth)) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::StartTask(ReadarrTaskName::CheckHealth)) + .await; + + mock.assert_async().await; + assert_err!(result); + } }