From 1cc95e2cd1a360a1ea8b7a55214a8b18fa777d3f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 22 Nov 2024 15:22:45 -0700 Subject: [PATCH] feat(cli): CLI support for adding a tag to Sonarr --- src/cli/radarr/add_command_handler.rs | 6 +- src/cli/sonarr/add_command_handler.rs | 65 ++++++++++++++ src/cli/sonarr/add_command_handler_tests.rs | 98 +++++++++++++++++++++ src/cli/sonarr/mod.rs | 12 +++ src/cli/sonarr/sonarr_command_tests.rs | 32 ++++++- 5 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 src/cli/sonarr/add_command_handler.rs create mode 100644 src/cli/sonarr/add_command_handler_tests.rs diff --git a/src/cli/radarr/add_command_handler.rs b/src/cli/radarr/add_command_handler.rs index 007306f..70efb70 100644 --- a/src/cli/radarr/add_command_handler.rs +++ b/src/cli/radarr/add_command_handler.rs @@ -132,21 +132,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan }; let resp = self .network - .handle_network_event((RadarrEvent::AddMovie(Some(body))).into()) + .handle_network_event(RadarrEvent::AddMovie(Some(body)).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrAddCommand::RootFolder { root_folder_path } => { let resp = self .network - .handle_network_event((RadarrEvent::AddRootFolder(Some(root_folder_path.clone()))).into()) + .handle_network_event(RadarrEvent::AddRootFolder(Some(root_folder_path)).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrAddCommand::Tag { name } => { let resp = self .network - .handle_network_event((RadarrEvent::AddTag(name.clone())).into()) + .handle_network_event(RadarrEvent::AddTag(name).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/add_command_handler.rs b/src/cli/sonarr/add_command_handler.rs new file mode 100644 index 0000000..2b0d44c --- /dev/null +++ b/src/cli/sonarr/add_command_handler.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use anyhow::Result; +use clap::Subcommand; +use tokio::sync::Mutex; + +use crate::{ + app::App, + cli::{CliCommandHandler, Command}, + network::{sonarr_network::SonarrEvent, NetworkTrait}, +}; + +use super::SonarrCommand; + +#[cfg(test)] +#[path = "add_command_handler_tests.rs"] +mod add_command_handler_tests; + +#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] +pub enum SonarrAddCommand { + #[command(about = "Add new tag")] + Tag { + #[arg(long, help = "The name of the tag to be added", required = true)] + name: String, + }, +} + +impl From for Command { + fn from(value: SonarrAddCommand) -> Self { + Command::Sonarr(SonarrCommand::Add(value)) + } +} + +pub(super) struct SonarrAddCommandHandler<'a, 'b> { + _app: &'a Arc>>, + command: SonarrAddCommand, + network: &'a mut dyn NetworkTrait, +} + +impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHandler<'a, 'b> { + fn with( + _app: &'a Arc>>, + command: SonarrAddCommand, + network: &'a mut dyn NetworkTrait, + ) -> Self { + SonarrAddCommandHandler { + _app, + command, + network, + } + } + + async fn handle(self) -> Result { + let result = match self.command { + SonarrAddCommand::Tag { name } => { + let resp = self + .network + .handle_network_event(SonarrEvent::AddTag(name).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + }; + Ok(result) + } +} diff --git a/src/cli/sonarr/add_command_handler_tests.rs b/src/cli/sonarr/add_command_handler_tests.rs new file mode 100644 index 0000000..a50781e --- /dev/null +++ b/src/cli/sonarr/add_command_handler_tests.rs @@ -0,0 +1,98 @@ +#[cfg(test)] +mod tests { + use clap::{error::ErrorKind, CommandFactory, Parser}; + + use crate::{ + cli::{ + sonarr::{add_command_handler::SonarrAddCommand, SonarrCommand}, + Command, + }, + Cli, + }; + + #[test] + fn test_sonarr_add_command_from() { + let command = SonarrAddCommand::Tag { + name: String::new(), + }; + + let result = Command::from(command.clone()); + + assert_eq!(result, Command::Sonarr(SonarrCommand::Add(command))); + } + + mod cli { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_add_tag_requires_arguments() { + let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_add_tag_success() { + let expected_args = SonarrAddCommand::Tag { + name: "test".to_owned(), + }; + + let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]); + + assert!(result.is_ok()); + + if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { + assert_eq!(add_command, expected_args); + } + } + } + + mod handler { + use std::sync::Arc; + + use crate::{ + app::App, + cli::{sonarr::add_command_handler::SonarrAddCommandHandler, CliCommandHandler}, + models::{sonarr_models::SonarrSerdeable, Serdeable}, + network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, + }; + + use super::*; + use mockall::predicate::eq; + + use serde_json::json; + use tokio::sync::Mutex; + + #[tokio::test] + async fn test_handle_add_tag_command() { + let expected_tag_name = "test".to_owned(); + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + SonarrEvent::AddTag(expected_tag_name.clone()).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Sonarr(SonarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::default())); + let add_tag_command = SonarrAddCommand::Tag { + name: expected_tag_name, + }; + + let result = SonarrAddCommandHandler::with(&app_arc, add_tag_command, &mut mock_network) + .handle() + .await; + + assert!(result.is_ok()); + } + } +} diff --git a/src/cli/sonarr/mod.rs b/src/cli/sonarr/mod.rs index 52239a7..73fd53d 100644 --- a/src/cli/sonarr/mod.rs +++ b/src/cli/sonarr/mod.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use add_command_handler::{SonarrAddCommand, SonarrAddCommandHandler}; use anyhow::Result; use clap::Subcommand; use delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler}; @@ -14,6 +15,7 @@ use crate::{ use super::{CliCommandHandler, Command}; +mod add_command_handler; mod delete_command_handler; mod get_command_handler; mod list_command_handler; @@ -24,6 +26,11 @@ mod sonarr_command_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum SonarrCommand { + #[command( + subcommand, + about = "Commands to add or create new resources within your Sonarr instance" + )] + Add(SonarrAddCommand), #[command( subcommand, about = "Commands to delete resources from your Sonarr instance" @@ -92,6 +99,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' async fn handle(self) -> Result { let result = match self.command { + SonarrCommand::Add(add_command) => { + SonarrAddCommandHandler::with(self.app, add_command, self.network) + .handle() + .await? + } SonarrCommand::Delete(delete_command) => { SonarrDeleteCommandHandler::with(self.app, delete_command, self.network) .handle() diff --git a/src/cli/sonarr/sonarr_command_tests.rs b/src/cli/sonarr/sonarr_command_tests.rs index cf22b4b..0d72ec9 100644 --- a/src/cli/sonarr/sonarr_command_tests.rs +++ b/src/cli/sonarr/sonarr_command_tests.rs @@ -114,8 +114,9 @@ mod tests { app::App, cli::{ sonarr::{ - delete_command_handler::SonarrDeleteCommand, get_command_handler::SonarrGetCommand, - list_command_handler::SonarrListCommand, SonarrCliHandler, SonarrCommand, + add_command_handler::SonarrAddCommand, delete_command_handler::SonarrDeleteCommand, + get_command_handler::SonarrGetCommand, list_command_handler::SonarrListCommand, + SonarrCliHandler, SonarrCommand, }, CliCommandHandler, }, @@ -215,6 +216,33 @@ mod tests { assert!(result.is_ok()); } + #[tokio::test] + async fn test_sonarr_cli_handler_delegates_add_commands_to_the_add_command_handler() { + let expected_tag_name = "test".to_owned(); + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + SonarrEvent::AddTag(expected_tag_name.clone()).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Sonarr(SonarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::default())); + let add_tag_command = SonarrCommand::Add(SonarrAddCommand::Tag { + name: expected_tag_name, + }); + + let result = SonarrCliHandler::with(&app_arc, add_tag_command, &mut mock_network) + .handle() + .await; + + assert!(result.is_ok()); + } + #[tokio::test] async fn test_sonarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() { let expected_blocklist_item_id = 1;