diff --git a/src/cli/lidarr/add_command_handler.rs b/src/cli/lidarr/add_command_handler.rs new file mode 100644 index 0000000..5ffc0f2 --- /dev/null +++ b/src/cli/lidarr/add_command_handler.rs @@ -0,0 +1,65 @@ +use std::sync::Arc; + +use anyhow::Result; +use clap::{Subcommand, arg}; +use tokio::sync::Mutex; + +use super::LidarrCommand; +use crate::{ + app::App, + cli::{CliCommandHandler, Command}, + network::{NetworkTrait, lidarr_network::LidarrEvent}, +}; + +#[cfg(test)] +#[path = "add_command_handler_tests.rs"] +mod add_command_handler_tests; + +#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] +pub enum LidarrAddCommand { + #[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: LidarrAddCommand) -> Self { + Command::Lidarr(LidarrCommand::Add(value)) + } +} + +pub(super) struct LidarrAddCommandHandler<'a, 'b> { + _app: &'a Arc>>, + command: LidarrAddCommand, + network: &'a mut dyn NetworkTrait, +} + +impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrAddCommand> for LidarrAddCommandHandler<'a, 'b> { + fn with( + app: &'a Arc>>, + command: LidarrAddCommand, + network: &'a mut dyn NetworkTrait, + ) -> Self { + LidarrAddCommandHandler { + _app: app, + command, + network, + } + } + + async fn handle(self) -> Result { + let result = match self.command { + LidarrAddCommand::Tag { name } => { + let resp = self + .network + .handle_network_event(LidarrEvent::AddTag(name).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + }; + + Ok(result) + } +} diff --git a/src/cli/lidarr/add_command_handler_tests.rs b/src/cli/lidarr/add_command_handler_tests.rs new file mode 100644 index 0000000..351fee3 --- /dev/null +++ b/src/cli/lidarr/add_command_handler_tests.rs @@ -0,0 +1,101 @@ +#[cfg(test)] +mod tests { + use clap::{CommandFactory, Parser, error::ErrorKind}; + + use crate::{ + Cli, + cli::{ + Command, + lidarr::{LidarrCommand, add_command_handler::LidarrAddCommand}, + }, + }; + use pretty_assertions::assert_eq; + + #[test] + fn test_lidarr_add_command_from() { + let command = LidarrAddCommand::Tag { + name: String::new(), + }; + + let result = Command::from(command.clone()); + + assert_eq!(result, Command::Lidarr(LidarrCommand::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", "lidarr", "add", "tag"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_add_tag_success() { + let expected_args = LidarrAddCommand::Tag { + name: "test".to_owned(), + }; + + let result = Cli::try_parse_from(["managarr", "lidarr", "add", "tag", "--name", "test"]); + + assert_ok!(&result); + + let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else { + panic!("Unexpected command type") + }; + assert_eq!(add_command, expected_args); + } + } + + mod handler { + use std::sync::Arc; + + use mockall::predicate::eq; + use serde_json::json; + use tokio::sync::Mutex; + + use crate::cli::CliCommandHandler; + use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler}; + use crate::models::Serdeable; + use crate::models::lidarr_models::LidarrSerdeable; + use crate::network::lidarr_network::LidarrEvent; + use crate::{ + app::App, + network::{MockNetworkTrait, NetworkEvent}, + }; + + #[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::( + LidarrEvent::AddTag(expected_tag_name.clone()).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let add_tag_command = LidarrAddCommand::Tag { + name: expected_tag_name, + }; + + let result = LidarrAddCommandHandler::with(&app_arc, add_tag_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + } +} diff --git a/src/cli/lidarr/lidarr_command_tests.rs b/src/cli/lidarr/lidarr_command_tests.rs index 484c62d..4dbafb1 100644 --- a/src/cli/lidarr/lidarr_command_tests.rs +++ b/src/cli/lidarr/lidarr_command_tests.rs @@ -36,6 +36,13 @@ mod tests { assert_err!(&result); } + #[test] + fn test_lidarr_add_subcommand_requires_subcommand() { + let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add"]); + + assert_err!(&result); + } + #[test] fn test_lidarr_delete_subcommand_requires_subcommand() { let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete"]); @@ -76,6 +83,7 @@ mod tests { use serde_json::json; use tokio::sync::Mutex; + use crate::cli::lidarr::add_command_handler::LidarrAddCommand; use crate::cli::lidarr::get_command_handler::LidarrGetCommand; use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand; use crate::{ @@ -94,6 +102,33 @@ mod tests { network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent}, }; + #[tokio::test] + async fn test_lidarr_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::( + LidarrEvent::AddTag(expected_tag_name.clone()).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let add_tag_command = LidarrCommand::Add(LidarrAddCommand::Tag { + name: expected_tag_name, + }); + + let result = LidarrCliHandler::with(&app_arc, add_tag_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + #[tokio::test] async fn test_lidarr_cli_handler_delegates_get_commands_to_the_get_command_handler() { let mut mock_network = MockNetworkTrait::new(); diff --git a/src/cli/lidarr/list_command_handler.rs b/src/cli/lidarr/list_command_handler.rs index cf56ccb..cb0c1a8 100644 --- a/src/cli/lidarr/list_command_handler.rs +++ b/src/cli/lidarr/list_command_handler.rs @@ -20,6 +20,8 @@ mod list_command_handler_tests; pub enum LidarrListCommand { #[command(about = "List all artists in your Lidarr library")] Artists, + #[command(about = "List all Lidarr tags")] + Tags, } impl From for Command { @@ -56,6 +58,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH .await?; serde_json::to_string_pretty(&resp)? } + LidarrListCommand::Tags => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetTags.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/lidarr/list_command_handler_tests.rs b/src/cli/lidarr/list_command_handler_tests.rs index 6dfbae9..d01b7bd 100644 --- a/src/cli/lidarr/list_command_handler_tests.rs +++ b/src/cli/lidarr/list_command_handler_tests.rs @@ -18,11 +18,17 @@ mod tests { } mod cli { + use rstest::rstest; use super::*; - #[test] - fn test_list_artists_has_no_arg_requirements() { - let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]); + #[rstest] + fn test_list_commands_have_no_arg_requirements( + #[values( + "artists", + "tags" + )] subcommand: &str + ) { + let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", subcommand]); assert_ok!(&result); } @@ -32,6 +38,7 @@ mod tests { use std::sync::Arc; use mockall::predicate::eq; + use rstest::rstest; use serde_json::json; use tokio::sync::Mutex; @@ -45,12 +52,18 @@ mod tests { network::{MockNetworkTrait, NetworkEvent}, }; + #[rstest] + #[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)] + #[case(LidarrListCommand::Tags, LidarrEvent::GetTags)] #[tokio::test] - async fn test_handle_list_artists_command() { + async fn test_handle_list_command( + #[case] list_command: LidarrListCommand, + #[case] expected_lidarr_event: LidarrEvent + ) { let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() - .with(eq::(LidarrEvent::ListArtists.into())) + .with(eq::(expected_lidarr_event.into())) .times(1) .returning(|_| { Ok(Serdeable::Lidarr(LidarrSerdeable::Value( @@ -60,7 +73,7 @@ mod tests { let app_arc = Arc::new(Mutex::new(App::test_default())); let result = - LidarrListCommandHandler::with(&app_arc, LidarrListCommand::Artists, &mut mock_network) + LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network) .handle() .await; diff --git a/src/cli/lidarr/mod.rs b/src/cli/lidarr/mod.rs index a7a4fcc..306051b 100644 --- a/src/cli/lidarr/mod.rs +++ b/src/cli/lidarr/mod.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler}; use anyhow::Result; use clap::{Subcommand, arg}; use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler}; @@ -14,6 +15,7 @@ use crate::{app::App, network::NetworkTrait}; use super::{CliCommandHandler, Command}; +mod add_command_handler; mod delete_command_handler; mod edit_command_handler; mod get_command_handler; @@ -26,6 +28,11 @@ mod lidarr_command_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum LidarrCommand { + #[command( + subcommand, + about = "Commands to add or create new resources within your Lidarr instance" + )] + Add(LidarrAddCommand), #[command( subcommand, about = "Commands to delete resources from your Lidarr instance" @@ -91,6 +98,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, ' async fn handle(self) -> Result { let result = match self.command { + LidarrCommand::Add(add_command) => { + LidarrAddCommandHandler::with(self.app, add_command, self.network) + .handle() + .await? + } LidarrCommand::Delete(delete_command) => { LidarrDeleteCommandHandler::with(self.app, delete_command, self.network) .handle() diff --git a/src/handlers/lidarr_handlers/library/library_handler_tests.rs b/src/handlers/lidarr_handlers/library/library_handler_tests.rs index 9801a8d..7678f34 100644 --- a/src/handlers/lidarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/lidarr_handlers/library/library_handler_tests.rs @@ -532,7 +532,7 @@ mod tests { ActiveLidarrBlock::EditArtistSelectMonitorNewItems, ActiveLidarrBlock::EditArtistSelectQualityProfile, ActiveLidarrBlock::EditArtistTagsInput, - ActiveLidarrBlock::EditArtistPathInput, + ActiveLidarrBlock::EditArtistPathInput )] active_lidarr_block: ActiveLidarrBlock, ) { diff --git a/src/network/lidarr_network/library/lidarr_library_network_tests.rs b/src/network/lidarr_network/library/lidarr_library_network_tests.rs index ebdefaf..b0045f1 100644 --- a/src/network/lidarr_network/library/lidarr_library_network_tests.rs +++ b/src/network/lidarr_network/library/lidarr_library_network_tests.rs @@ -5,12 +5,12 @@ mod tests { }; use crate::network::NetworkResource; use crate::network::lidarr_network::LidarrEvent; + use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::ARTIST_JSON; use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; use bimap::BiMap; use mockito::Matcher; use pretty_assertions::assert_eq; use serde_json::{Value, json}; - use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::ARTIST_JSON; #[tokio::test] async fn test_handle_list_artists_event() { diff --git a/src/network/lidarr_network/lidarr_network_tests.rs b/src/network/lidarr_network/lidarr_network_tests.rs index 0e487a3..ee96299 100644 --- a/src/network/lidarr_network/lidarr_network_tests.rs +++ b/src/network/lidarr_network/lidarr_network_tests.rs @@ -1,7 +1,8 @@ #[cfg(test)] mod tests { - use std::sync::Arc; + use crate::app::App; use crate::models::lidarr_models::{LidarrSerdeable, MetadataProfile}; + use crate::models::servarr_data::lidarr::modals::EditArtistModal; use crate::models::servarr_models::{QualityProfile, Tag}; use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; use crate::network::{NetworkEvent, NetworkResource, lidarr_network::LidarrEvent}; @@ -9,9 +10,8 @@ mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; use serde_json::json; + use std::sync::Arc; use tokio::sync::Mutex; - use crate::app::App; - use crate::models::servarr_data::lidarr::modals::EditArtistModal; #[rstest] fn test_resource_artist(