From b4a99d16651eb02815a6a1a3001b909c5089f737 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 6 Jan 2026 10:24:51 -0700 Subject: [PATCH] feat: Created Lidarr commands: 'get artist-details' and 'get system-status' --- src/cli/lidarr/get_command_handler.rs | 79 ++++++++++++ src/cli/lidarr/get_command_handler_tests.rs | 127 ++++++++++++++++++++ src/cli/lidarr/lidarr_command_tests.rs | 23 ++++ src/cli/lidarr/mod.rs | 12 ++ 4 files changed, 241 insertions(+) create mode 100644 src/cli/lidarr/get_command_handler.rs create mode 100644 src/cli/lidarr/get_command_handler_tests.rs diff --git a/src/cli/lidarr/get_command_handler.rs b/src/cli/lidarr/get_command_handler.rs new file mode 100644 index 0000000..c731212 --- /dev/null +++ b/src/cli/lidarr/get_command_handler.rs @@ -0,0 +1,79 @@ +use std::sync::Arc; + +use anyhow::Result; +use clap::Subcommand; +use tokio::sync::Mutex; + +use crate::{ + app::App, + cli::{CliCommandHandler, Command}, + network::{NetworkTrait, lidarr_network::LidarrEvent}, +}; + +use super::LidarrCommand; + +#[cfg(test)] +#[path = "get_command_handler_tests.rs"] +mod get_command_handler_tests; + +#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] +pub enum LidarrGetCommand { + #[command(about = "Get detailed information for the artist with the given ID")] + ArtistDetails { + #[arg( + long, + help = "The Lidarr ID of the artist whose details you wish to fetch", + required = true + )] + artist_id: i64, + }, + #[command(about = "Get the system status")] + SystemStatus, +} + +impl From for Command { + fn from(value: LidarrGetCommand) -> Self { + Command::Lidarr(LidarrCommand::Get(value)) + } +} + +pub(super) struct LidarrGetCommandHandler<'a, 'b> { + _app: &'a Arc>>, + command: LidarrGetCommand, + network: &'a mut dyn NetworkTrait, +} + +impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHandler<'a, 'b> { + fn with( + _app: &'a Arc>>, + command: LidarrGetCommand, + network: &'a mut dyn NetworkTrait, + ) -> Self { + LidarrGetCommandHandler { + _app, + command, + network, + } + } + + async fn handle(self) -> Result { + let result = match self.command { + LidarrGetCommand::ArtistDetails { artist_id } => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetArtistDetails(artist_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + LidarrGetCommand::SystemStatus => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetStatus.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + }; + + Ok(result) + } +} diff --git a/src/cli/lidarr/get_command_handler_tests.rs b/src/cli/lidarr/get_command_handler_tests.rs new file mode 100644 index 0000000..0474ae6 --- /dev/null +++ b/src/cli/lidarr/get_command_handler_tests.rs @@ -0,0 +1,127 @@ +#[cfg(test)] +mod tests { + use crate::Cli; + use crate::cli::{ + Command, + lidarr::{LidarrCommand, get_command_handler::LidarrGetCommand}, + }; + use clap::CommandFactory; + use pretty_assertions::assert_eq; + + #[test] + fn test_lidarr_get_command_from() { + let command = LidarrGetCommand::SystemStatus; + + let result = Command::from(command.clone()); + + assert_eq!(result, Command::Lidarr(LidarrCommand::Get(command))); + } + + mod cli { + use clap::error::ErrorKind; + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_artist_details_requires_artist_id() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "artist-details"]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_artist_details_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "get", + "artist-details", + "--artist-id", + "1", + ]); + + assert_ok!(&result); + } + + #[test] + fn test_system_status_has_no_arg_requirements() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "system-status"]); + + assert_ok!(&result); + } + } + + mod handler { + use std::sync::Arc; + + use mockall::predicate::eq; + use serde_json::json; + use tokio::sync::Mutex; + + use crate::{ + app::App, + cli::{ + CliCommandHandler, + lidarr::get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler}, + }, + models::{Serdeable, lidarr_models::LidarrSerdeable}, + network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent}, + }; + + #[tokio::test] + async fn test_handle_get_artist_details_command() { + let expected_artist_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::GetArtistDetails(expected_artist_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_artist_details_command = LidarrGetCommand::ArtistDetails { artist_id: 1 }; + + let result = + LidarrGetCommandHandler::with(&app_arc, get_artist_details_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + + #[tokio::test] + async fn test_handle_get_system_status_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(LidarrEvent::GetStatus.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_system_status_command = LidarrGetCommand::SystemStatus; + + let result = + LidarrGetCommandHandler::with(&app_arc, get_system_status_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 dede7cc..2ca231b 100644 --- a/src/cli/lidarr/lidarr_command_tests.rs +++ b/src/cli/lidarr/lidarr_command_tests.rs @@ -91,6 +91,29 @@ mod tests { }, network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent}, }; + use crate::cli::lidarr::get_command_handler::LidarrGetCommand; + + #[tokio::test] + async fn test_lidarr_cli_handler_delegates_get_commands_to_the_get_command_handler() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(LidarrEvent::GetStatus.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_system_status_command = LidarrCommand::Get(LidarrGetCommand::SystemStatus); + + let result = LidarrCliHandler::with(&app_arc, get_system_status_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } #[tokio::test] async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() { diff --git a/src/cli/lidarr/mod.rs b/src/cli/lidarr/mod.rs index 43f2bed..dc533ab 100644 --- a/src/cli/lidarr/mod.rs +++ b/src/cli/lidarr/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::Result; use clap::{Subcommand, arg}; use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler}; +use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler}; use list_command_handler::{LidarrListCommand, LidarrListCommandHandler}; use tokio::sync::Mutex; @@ -12,6 +13,7 @@ use crate::{app::App, network::NetworkTrait}; use super::{CliCommandHandler, Command}; mod delete_command_handler; +mod get_command_handler; mod list_command_handler; #[cfg(test)] @@ -25,6 +27,11 @@ pub enum LidarrCommand { about = "Commands to delete resources from your Lidarr instance" )] Delete(LidarrDeleteCommand), + #[command( + subcommand, + about = "Commands to fetch details of the resources in your Lidarr instance" + )] + Get(LidarrGetCommand), #[command( subcommand, about = "Commands to list attributes from your Lidarr instance" @@ -75,6 +82,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, ' .handle() .await? } + LidarrCommand::Get(get_command) => { + LidarrGetCommandHandler::with(self.app, get_command, self.network) + .handle() + .await? + } LidarrCommand::List(list_command) => { LidarrListCommandHandler::with(self.app, list_command, self.network) .handle()