diff --git a/src/cli/lidarr/get_command_handler.rs b/src/cli/lidarr/get_command_handler.rs index c731212..64063b8 100644 --- a/src/cli/lidarr/get_command_handler.rs +++ b/src/cli/lidarr/get_command_handler.rs @@ -27,6 +27,10 @@ pub enum LidarrGetCommand { )] artist_id: i64, }, + #[command(about = "Fetch the host config for your Lidarr instance")] + HostConfig, + #[command(about = "Fetch the security config for your Lidarr instance")] + SecurityConfig, #[command(about = "Get the system status")] SystemStatus, } @@ -65,6 +69,20 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHan .await?; serde_json::to_string_pretty(&resp)? } + LidarrGetCommand::HostConfig => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetHostConfig.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } + LidarrGetCommand::SecurityConfig => { + let resp = self + .network + .handle_network_event(LidarrEvent::GetSecurityConfig.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } LidarrGetCommand::SystemStatus => { let resp = self .network diff --git a/src/cli/lidarr/get_command_handler_tests.rs b/src/cli/lidarr/get_command_handler_tests.rs index 0474ae6..4ada9a7 100644 --- a/src/cli/lidarr/get_command_handler_tests.rs +++ b/src/cli/lidarr/get_command_handler_tests.rs @@ -49,6 +49,22 @@ mod tests { assert_ok!(&result); } + #[test] + fn test_host_config_has_no_arg_requirements() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "host-config"]); + + assert_ok!(&result); + } + + #[test] + fn test_security_config_has_no_arg_requirements() { + let result = + Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "security-config"]); + + assert_ok!(&result); + } + #[test] fn test_system_status_has_no_arg_requirements() { let result = @@ -101,6 +117,52 @@ mod tests { assert_ok!(&result); } + #[tokio::test] + async fn test_handle_get_host_config_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(LidarrEvent::GetHostConfig.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_host_config_command = LidarrGetCommand::HostConfig; + + let result = + LidarrGetCommandHandler::with(&app_arc, get_host_config_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + + #[tokio::test] + async fn test_handle_get_security_config_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(LidarrEvent::GetSecurityConfig.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_security_config_command = LidarrGetCommand::SecurityConfig; + + let result = + LidarrGetCommandHandler::with(&app_arc, get_security_config_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(); diff --git a/src/models/lidarr_models.rs b/src/models/lidarr_models.rs index 0cc6884..945c689 100644 --- a/src/models/lidarr_models.rs +++ b/src/models/lidarr_models.rs @@ -7,7 +7,7 @@ use strum::{Display, EnumIter}; use super::{ HorizontallyScrollableText, Serdeable, - servarr_models::{DiskSpace, QualityProfile, RootFolder, Tag}, + servarr_models::{DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag}, }; use crate::serde_enum_from; @@ -217,9 +217,11 @@ serde_enum_from!( Artists(Vec), DiskSpaces(Vec), DownloadsResponse(DownloadsResponse), + HostConfig(HostConfig), MetadataProfiles(Vec), QualityProfiles(Vec), RootFolders(Vec), + SecurityConfig(SecurityConfig), SystemStatus(SystemStatus), Tags(Vec), Value(Value), diff --git a/src/models/lidarr_models_tests.rs b/src/models/lidarr_models_tests.rs index a280538..bb3f478 100644 --- a/src/models/lidarr_models_tests.rs +++ b/src/models/lidarr_models_tests.rs @@ -8,7 +8,9 @@ mod tests { DownloadRecord, DownloadStatus, DownloadsResponse, Member, MetadataProfile, NewItemMonitorType, SystemStatus, }; - use crate::models::servarr_models::{DiskSpace, QualityProfile, RootFolder, Tag}; + use crate::models::servarr_models::{ + DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag, + }; use crate::models::{ Serdeable, lidarr_models::{Artist, ArtistStatistics, ArtistStatus, LidarrSerdeable, Ratings}, @@ -291,6 +293,18 @@ mod tests { ); } + #[test] + fn test_lidarr_serdeable_from_host_config() { + let host_config = HostConfig { + port: 8686, + ..HostConfig::default() + }; + + let lidarr_serdeable: LidarrSerdeable = host_config.clone().into(); + + assert_eq!(lidarr_serdeable, LidarrSerdeable::HostConfig(host_config)); + } + #[test] fn test_lidarr_serdeable_from_quality_profiles() { let quality_profiles = vec![QualityProfile { @@ -321,6 +335,21 @@ mod tests { assert_eq!(lidarr_serdeable, LidarrSerdeable::RootFolders(root_folders)); } + #[test] + fn test_lidarr_serdeable_from_security_config() { + let security_config = SecurityConfig { + api_key: "test-key".to_owned(), + ..SecurityConfig::default() + }; + + let lidarr_serdeable: LidarrSerdeable = security_config.clone().into(); + + assert_eq!( + lidarr_serdeable, + LidarrSerdeable::SecurityConfig(security_config) + ); + } + #[test] fn test_lidarr_serdeable_from_system_status() { let system_status = SystemStatus { diff --git a/src/models/servarr_data/lidarr/lidarr_data.rs b/src/models/servarr_data/lidarr/lidarr_data.rs index 5d87459..01b0bed 100644 --- a/src/models/servarr_data/lidarr/lidarr_data.rs +++ b/src/models/servarr_data/lidarr/lidarr_data.rs @@ -1,6 +1,6 @@ use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES; use crate::models::{ - Route, TabRoute, TabState, + BlockSelectionState, Route, TabRoute, TabState, lidarr_models::{Artist, DownloadRecord}, servarr_models::{DiskSpace, RootFolder}, stateful_table::StatefulTable, @@ -28,7 +28,7 @@ pub struct LidarrData<'a> { pub prompt_confirm_action: Option, pub quality_profile_map: BiMap, pub root_folders: StatefulTable, - pub selected_block: crate::models::BlockSelectionState<'a, ActiveLidarrBlock>, + pub selected_block: BlockSelectionState<'a, ActiveLidarrBlock>, pub start_time: DateTime, pub tags_map: BiMap, pub version: String, @@ -54,7 +54,7 @@ impl<'a> Default for LidarrData<'a> { prompt_confirm_action: None, quality_profile_map: BiMap::new(), root_folders: StatefulTable::default(), - selected_block: crate::models::BlockSelectionState::default(), + selected_block: BlockSelectionState::default(), start_time: Utc::now(), tags_map: BiMap::new(), version: String::new(), diff --git a/src/network/lidarr_network/lidarr_network_tests.rs b/src/network/lidarr_network/lidarr_network_tests.rs index f97f4e5..3a43da1 100644 --- a/src/network/lidarr_network/lidarr_network_tests.rs +++ b/src/network/lidarr_network/lidarr_network_tests.rs @@ -20,6 +20,13 @@ mod tests { assert_str_eq!(event.resource(), "/artist"); } + #[rstest] + fn test_resource_config( + #[values(LidarrEvent::GetHostConfig, LidarrEvent::GetSecurityConfig)] event: LidarrEvent, + ) { + assert_str_eq!(event.resource(), "/config/host"); + } + #[rstest] #[case(LidarrEvent::GetDiskSpace, "/diskspace")] #[case(LidarrEvent::GetDownloads(500), "/queue")] @@ -41,6 +48,19 @@ mod tests { ); } + #[tokio::test] + async fn test_handle_get_lidarr_healthcheck_event() { + let (mock, app, _server) = MockServarrApi::get() + .build_for(LidarrEvent::HealthCheck) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let _ = network.handle_lidarr_event(LidarrEvent::HealthCheck).await; + + mock.assert_async().await; + } + #[tokio::test] async fn test_handle_get_metadata_profiles_event() { let metadata_profiles_json = json!([{ diff --git a/src/network/lidarr_network/mod.rs b/src/network/lidarr_network/mod.rs index db62bef..af16346 100644 --- a/src/network/lidarr_network/mod.rs +++ b/src/network/lidarr_network/mod.rs @@ -21,9 +21,11 @@ pub enum LidarrEvent { GetArtistDetails(i64), GetDiskSpace, GetDownloads(u64), + GetHostConfig, GetMetadataProfiles, GetQualityProfiles, GetRootFolders, + GetSecurityConfig, GetStatus, GetTags, HealthCheck, @@ -40,6 +42,7 @@ impl NetworkResource for LidarrEvent { | LidarrEvent::ToggleArtistMonitoring(_) => "/artist", LidarrEvent::GetDiskSpace => "/diskspace", LidarrEvent::GetDownloads(_) => "/queue", + LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host", LidarrEvent::GetMetadataProfiles => "/metadataprofile", LidarrEvent::GetQualityProfiles => "/qualityprofile", LidarrEvent::GetRootFolders => "/rootfolder", @@ -74,6 +77,10 @@ impl Network<'_, '_> { .get_lidarr_downloads(count) .await .map(LidarrSerdeable::from), + LidarrEvent::GetHostConfig => self + .get_lidarr_host_config() + .await + .map(LidarrSerdeable::from), LidarrEvent::GetMetadataProfiles => self .get_lidarr_metadata_profiles() .await @@ -86,6 +93,10 @@ impl Network<'_, '_> { .get_lidarr_root_folders() .await .map(LidarrSerdeable::from), + LidarrEvent::GetSecurityConfig => self + .get_lidarr_security_config() + .await + .map(LidarrSerdeable::from), LidarrEvent::GetStatus => self.get_lidarr_status().await.map(LidarrSerdeable::from), LidarrEvent::GetTags => self.get_lidarr_tags().await.map(LidarrSerdeable::from), LidarrEvent::HealthCheck => self @@ -100,6 +111,19 @@ impl Network<'_, '_> { } } + pub(in crate::network::lidarr_network) async fn get_lidarr_healthcheck(&mut self) -> Result<()> { + info!("Performing Lidarr health check"); + let event = LidarrEvent::HealthCheck; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), ()>(request_props, |_, _| ()) + .await + } + async fn get_lidarr_metadata_profiles(&mut self) -> Result> { info!("Fetching Lidarr metadata profiles"); let event = LidarrEvent::GetMetadataProfiles; diff --git a/src/network/lidarr_network/system/lidarr_system_network_tests.rs b/src/network/lidarr_network/system/lidarr_system_network_tests.rs index 1457437..2c9f52c 100644 --- a/src/network/lidarr_network/system/lidarr_system_network_tests.rs +++ b/src/network/lidarr_network/system/lidarr_system_network_tests.rs @@ -1,31 +1,24 @@ #[cfg(test)] mod tests { use crate::models::lidarr_models::{LidarrSerdeable, SystemStatus}; - use crate::models::servarr_models::DiskSpace; + use crate::models::servarr_models::{DiskSpace, HostConfig, SecurityConfig}; use crate::network::lidarr_network::LidarrEvent; use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; use pretty_assertions::assert_eq; use serde_json::json; - #[tokio::test] - async fn test_handle_get_lidarr_healthcheck_event() { - let (mock, app, _server) = MockServarrApi::get() - .build_for(LidarrEvent::HealthCheck) - .await; - app.lock().await.server_tabs.set_index(2); - let mut network = test_network(&app); - - let _ = network.handle_lidarr_event(LidarrEvent::HealthCheck).await; - - mock.assert_async().await; - } - #[tokio::test] async fn test_handle_get_diskspace_event() { - let diskspace_json = json!([{ - "freeSpace": 50000000000i64, - "totalSpace": 100000000000i64 - }]); + let diskspace_json = json!([ + { + "freeSpace": 1111, + "totalSpace": 2222, + }, + { + "freeSpace": 3333, + "totalSpace": 4444 + } + ]); let response: Vec = serde_json::from_value(diskspace_json.clone()).unwrap(); let (mock, app, _server) = MockServarrApi::get() .returns(diskspace_json) @@ -46,6 +39,71 @@ mod tests { assert!(!app.lock().await.data.lidarr_data.disk_space_vec.is_empty()); } + #[tokio::test] + async fn test_handle_get_host_config_event() { + let host_config_json = json!({ + "bindAddress": "*", + "port": 8686, + "urlBase": "some.test.site/lidarr", + "instanceName": "Lidarr", + "applicationUrl": "https://some.test.site:8686/lidarr", + "enableSsl": true, + "sslPort": 6868, + "sslCertPath": "/app/lidarr.pfx", + "sslCertPassword": "test" + }); + let response: HostConfig = serde_json::from_value(host_config_json.clone()).unwrap(); + let (mock, app, _server) = MockServarrApi::get() + .returns(host_config_json) + .build_for(LidarrEvent::GetHostConfig) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let result = network + .handle_lidarr_event(LidarrEvent::GetHostConfig) + .await; + + mock.assert_async().await; + + let LidarrSerdeable::HostConfig(host_config) = result.unwrap() else { + panic!("Expected HostConfig"); + }; + + assert_eq!(host_config, response); + } + + #[tokio::test] + async fn test_handle_get_security_config_event() { + let security_config_json = json!({ + "authenticationMethod": "forms", + "authenticationRequired": "disabledForLocalAddresses", + "username": "test", + "password": "some password", + "apiKey": "someApiKey12345", + "certificateValidation": "disabledForLocalAddresses" + }); + let response: SecurityConfig = serde_json::from_value(security_config_json.clone()).unwrap(); + let (mock, app, _server) = MockServarrApi::get() + .returns(security_config_json) + .build_for(LidarrEvent::GetSecurityConfig) + .await; + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let result = network + .handle_lidarr_event(LidarrEvent::GetSecurityConfig) + .await; + + mock.assert_async().await; + + let LidarrSerdeable::SecurityConfig(security_config) = result.unwrap() else { + panic!("Expected SecurityConfig"); + }; + + assert_eq!(security_config, response); + } + #[tokio::test] async fn test_handle_get_status_event() { let status_json = json!({ diff --git a/src/network/lidarr_network/system/mod.rs b/src/network/lidarr_network/system/mod.rs index 2cb9196..1b33ac2 100644 --- a/src/network/lidarr_network/system/mod.rs +++ b/src/network/lidarr_network/system/mod.rs @@ -2,7 +2,7 @@ use anyhow::Result; use log::info; use crate::models::lidarr_models::SystemStatus; -use crate::models::servarr_models::DiskSpace; +use crate::models::servarr_models::{DiskSpace, HostConfig, SecurityConfig}; use crate::network::lidarr_network::LidarrEvent; use crate::network::{Network, RequestMethod}; @@ -11,16 +11,33 @@ use crate::network::{Network, RequestMethod}; mod lidarr_system_network_tests; impl Network<'_, '_> { - pub(in crate::network::lidarr_network) async fn get_lidarr_healthcheck(&mut self) -> Result<()> { - info!("Performing Lidarr health check"); - let event = LidarrEvent::HealthCheck; + pub(in crate::network::lidarr_network) async fn get_lidarr_host_config( + &mut self, + ) -> Result { + info!("Fetching Lidarr host config"); + let event = LidarrEvent::GetHostConfig; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) .await; self - .handle_request::<(), ()>(request_props, |_, _| ()) + .handle_request::<(), HostConfig>(request_props, |_, _| ()) + .await + } + + pub(in crate::network::lidarr_network) async fn get_lidarr_security_config( + &mut self, + ) -> Result { + info!("Fetching Lidarr security config"); + let event = LidarrEvent::GetSecurityConfig; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), SecurityConfig>(request_props, |_, _| ()) .await } diff --git a/src/network/network_tests.rs b/src/network/network_tests.rs index 7e7abc2..871e9d6 100644 --- a/src/network/network_tests.rs +++ b/src/network/network_tests.rs @@ -18,6 +18,7 @@ mod tests { use crate::app::{App, AppConfig, ServarrConfig}; use crate::models::HorizontallyScrollableText; use crate::network::NetworkResource; + use crate::network::lidarr_network::LidarrEvent; use crate::network::network_tests::test_utils::test_network; use crate::network::radarr_network::RadarrEvent; use crate::network::sonarr_network::SonarrEvent; @@ -421,11 +422,13 @@ mod tests { } #[rstest] - #[case(RadarrEvent::GetMovies, 7878)] - #[case(SonarrEvent::ListSeries, 8989)] + #[case(RadarrEvent::GetMovies, "v3", 7878)] + #[case(SonarrEvent::ListSeries, "v3", 8989)] + #[case(LidarrEvent::ListArtists, "v1", 8686)] #[tokio::test] async fn test_request_props_from_default_config( #[case] network_event: impl Into + NetworkResource, + #[case] api_version: &str, #[case] default_port: u16, ) { let app_arc = Arc::new(Mutex::new(App::test_default())); @@ -435,6 +438,7 @@ mod tests { let mut app = app_arc.lock().await; app.server_tabs.tabs[0].config = Some(ServarrConfig::default()); app.server_tabs.tabs[1].config = Some(ServarrConfig::default()); + app.server_tabs.tabs[2].config = Some(ServarrConfig::default()); } let request_props = network @@ -443,7 +447,7 @@ mod tests { assert_str_eq!( request_props.uri, - format!("http://localhost:{default_port}/api/v3{resource}") + format!("http://localhost:{default_port}/api/{api_version}{resource}") ); assert_eq!(request_props.method, RequestMethod::Get); assert_eq!(request_props.body, None); @@ -564,11 +568,13 @@ mod tests { } #[rstest] - #[case(RadarrEvent::GetMovies, 7878)] - #[case(SonarrEvent::ListSeries, 8989)] + #[case(RadarrEvent::GetMovies, "v3", 7878)] + #[case(SonarrEvent::ListSeries, "v3", 8989)] + #[case(LidarrEvent::ListArtists, "v1", 8686)] #[tokio::test] async fn test_request_props_from_default_config_with_path_and_query_params( #[case] network_event: impl Into + NetworkResource, + #[case] api_version: &str, #[case] default_port: u16, ) { let app_arc = Arc::new(Mutex::new(App::test_default())); @@ -578,6 +584,7 @@ mod tests { let mut app = app_arc.lock().await; app.server_tabs.tabs[0].config = Some(ServarrConfig::default()); app.server_tabs.tabs[1].config = Some(ServarrConfig::default()); + app.server_tabs.tabs[2].config = Some(ServarrConfig::default()); } let request_props = network @@ -592,7 +599,7 @@ mod tests { assert_str_eq!( request_props.uri, - format!("http://localhost:{default_port}/api/v3{resource}/test?id=1") + format!("http://localhost:{default_port}/api/{api_version}{resource}/test?id=1") ); assert_eq!(request_props.method, RequestMethod::Get); assert_eq!(request_props.body, None);