diff --git a/src/cli/readarr/list_command_handler.rs b/src/cli/readarr/list_command_handler.rs index 55c2cb76..33959fbf 100644 --- a/src/cli/readarr/list_command_handler.rs +++ b/src/cli/readarr/list_command_handler.rs @@ -19,6 +19,8 @@ mod list_command_handler_tests; pub enum ReadarrListCommand { #[command(about = "List disk space details for all provisioned root folders in Readarr")] DiskSpace, + #[command(about = "List all Readarr updates")] + Updates, } impl From for Command { @@ -55,6 +57,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrListCommand> for ReadarrListComman .await?; serde_json::to_string_pretty(&resp)? } + ReadarrListCommand::Updates => { + let resp = self + .network + .handle_network_event(ReadarrEvent::GetUpdates.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/readarr/list_command_handler_tests.rs b/src/cli/readarr/list_command_handler_tests.rs index 85568cad..88f4958d 100644 --- a/src/cli/readarr/list_command_handler_tests.rs +++ b/src/cli/readarr/list_command_handler_tests.rs @@ -23,7 +23,9 @@ mod tests { use super::*; #[rstest] - fn test_list_commands_have_no_arg_requirements(#[values("disk-space")] subcommand: &str) { + fn test_list_commands_have_no_arg_requirements( + #[values("disk-space", "updates")] subcommand: &str, + ) { let result = Cli::command().try_get_matches_from(["managarr", "readarr", "list", subcommand]); assert_ok!(&result); @@ -69,5 +71,28 @@ mod tests { assert_ok!(&result); } + + #[tokio::test] + async fn test_handle_list_updates_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(ReadarrEvent::GetUpdates.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Readarr(ReadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let list_updates_command = ReadarrListCommand::Updates; + + let result = + ReadarrListCommandHandler::with(&app_arc, list_updates_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 b61e1875..d8313d75 100644 --- a/src/network/readarr_network/mod.rs +++ b/src/network/readarr_network/mod.rs @@ -17,6 +17,7 @@ pub enum ReadarrEvent { GetHostConfig, GetSecurityConfig, GetStatus, + GetUpdates, HealthCheck, } @@ -27,6 +28,7 @@ impl NetworkResource for ReadarrEvent { ReadarrEvent::GetDiskSpace => "/diskspace", ReadarrEvent::HealthCheck => "/health", ReadarrEvent::GetStatus => "/system/status", + ReadarrEvent::GetUpdates => "/update", } } } @@ -56,6 +58,7 @@ impl Network<'_, '_> { .await .map(ReadarrSerdeable::from), ReadarrEvent::GetStatus => self.get_readarr_status().await.map(ReadarrSerdeable::from), + ReadarrEvent::GetUpdates => self.get_readarr_updates().await.map(ReadarrSerdeable::from), ReadarrEvent::HealthCheck => self .get_readarr_healthcheck() .await diff --git a/src/network/readarr_network/readarr_network_tests.rs b/src/network/readarr_network/readarr_network_tests.rs index 66899705..5375665c 100644 --- a/src/network/readarr_network/readarr_network_tests.rs +++ b/src/network/readarr_network/readarr_network_tests.rs @@ -11,6 +11,7 @@ mod tests { #[case(ReadarrEvent::GetDiskSpace, "/diskspace")] #[case(ReadarrEvent::HealthCheck, "/health")] #[case(ReadarrEvent::GetStatus, "/system/status")] + #[case(ReadarrEvent::GetUpdates, "/update")] fn test_resource(#[case] event: ReadarrEvent, #[case] expected_uri: &str) { assert_str_eq!(event.resource(), expected_uri); } diff --git a/src/network/readarr_network/system/mod.rs b/src/network/readarr_network/system/mod.rs index 013c240a..47a8cd3b 100644 --- a/src/network/readarr_network/system/mod.rs +++ b/src/network/readarr_network/system/mod.rs @@ -1,7 +1,9 @@ -use crate::models::servarr_models::{DiskSpace, HostConfig, SecurityConfig, SystemStatus}; +use crate::models::ScrollableText; +use crate::models::servarr_models::{DiskSpace, HostConfig, SecurityConfig, SystemStatus, Update}; use crate::network::readarr_network::ReadarrEvent; use crate::network::{Network, RequestMethod}; use anyhow::Result; +use indoc::formatdoc; use log::info; #[cfg(test)] @@ -73,4 +75,84 @@ impl Network<'_, '_> { }) .await } + + pub(in crate::network::readarr_network) async fn get_readarr_updates( + &mut self, + ) -> Result> { + info!("Fetching Readarr updates"); + let event = ReadarrEvent::GetUpdates; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), Vec>(request_props, |updates_vec, mut app| { + let latest_installed = if updates_vec + .iter() + .any(|update| update.latest && update.installed_on.is_some()) + { + "already".to_owned() + } else { + "not".to_owned() + }; + let updates = updates_vec + .into_iter() + .map(|update| { + let install_status = if update.installed_on.is_some() { + if update.installed { + " (Currently Installed)".to_owned() + } else { + " (Previously Installed)".to_owned() + } + } else { + String::new() + }; + let vec_to_bullet_points = |vec: Vec| { + vec + .iter() + .map(|change| format!(" * {change}")) + .collect::>() + .join("\n") + }; + + let mut update_info = formatdoc!( + "{} - {}{install_status} + {}", + update.version, + update.release_date, + "-".repeat(200) + ); + + if let Some(new_changes) = update.changes.new { + let changes = vec_to_bullet_points(new_changes); + update_info = formatdoc!( + "{update_info} + New: + {changes}" + ) + } + + if let Some(fixes) = update.changes.fixed { + let fixes = vec_to_bullet_points(fixes); + update_info = formatdoc!( + "{update_info} + Fixed: + {fixes}" + ); + } + + update_info + }) + .reduce(|version_1, version_2| format!("{version_1}\n\n\n{version_2}")) + .unwrap(); + + app.data.readarr_data.updates = ScrollableText::with_string(formatdoc!( + "The latest version of Readarr is {latest_installed} installed + + {updates}" + )); + }) + .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 512bf6b1..958448cf 100644 --- a/src/network/readarr_network/system/readarr_system_network_tests.rs +++ b/src/network/readarr_network/system/readarr_system_network_tests.rs @@ -4,11 +4,12 @@ mod tests { use crate::models::readarr_models::ReadarrSerdeable; use crate::models::servarr_models::{ AuthenticationMethod, AuthenticationRequired, CertificateValidation, DiskSpace, HostConfig, - SecurityConfig, SystemStatus, + SecurityConfig, SystemStatus, Update, }; use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; use crate::network::readarr_network::ReadarrEvent; use chrono::DateTime; + use indoc::formatdoc; use pretty_assertions::{assert_eq, assert_str_eq}; use serde_json::json; @@ -249,4 +250,105 @@ mod tests { assert_err!(result); assert_is_empty!(app.lock().await.data.readarr_data.version); } + + #[tokio::test] + async fn test_handle_get_readarr_updates_event() { + let updates_json = json!([{ + "version": "4.3.2.1", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": true, + "installedOn": "2023-04-15T02:02:53Z", + "latest": true, + "changes": { "new": ["Cool new thing"], "fixed": ["Some bugs killed"] }, + }, + { + "version": "3.2.1.0", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": false, + "installedOn": "2023-04-15T02:02:53Z", + "latest": false, + "changes": { "new": ["Cool new thing (old)", "Other cool new thing (old)"] }, + }, + { + "version": "2.1.0", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": false, + "latest": false, + "changes": { "fixed": ["Killed bug 1", "Fixed bug 2"] }, + }, + { + "version": "1.0.0", + "releaseDate": "2023-04-15T02:02:53Z", + "installed": false, + "latest": false, + }]); + let response: Vec = serde_json::from_value(updates_json.clone()).unwrap(); + let line_break = "-".repeat(200); + let expected_text = formatdoc!( + "The latest version of Readarr is already installed + + 4.3.2.1 - 2023-04-15 02:02:53 UTC (Currently Installed) + {line_break} + New: + * Cool new thing + Fixed: + * Some bugs killed + + + 3.2.1.0 - 2023-04-15 02:02:53 UTC (Previously Installed) + {line_break} + New: + * Cool new thing (old) + * Other cool new thing (old) + + + 2.1.0 - 2023-04-15 02:02:53 UTC + {line_break} + Fixed: + * Killed bug 1 + * Fixed bug 2 + + + 1.0.0 - 2023-04-15 02:02:53 UTC + {line_break}" + ); + let (mock, app, _server) = MockServarrApi::get() + .returns(updates_json) + .build_for(ReadarrEvent::GetUpdates) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network.handle_readarr_event(ReadarrEvent::GetUpdates).await; + + mock.assert_async().await; + + let ReadarrSerdeable::Updates(updates) = result.unwrap() else { + panic!("Expected Updates") + }; + + assert_eq!(updates, response); + + assert_str_eq!( + app.lock().await.data.readarr_data.updates.get_text(), + expected_text + ); + } + + #[tokio::test] + async fn test_handle_get_readarr_updates_event_failure() { + let (mock, app, _server) = MockServarrApi::get() + .returns(json!({})) + .status(500) + .build_for(ReadarrEvent::GetUpdates) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network.handle_readarr_event(ReadarrEvent::GetUpdates).await; + + mock.assert_async().await; + assert_err!(result); + assert_is_empty!(app.lock().await.data.readarr_data.updates.get_text()); + } }