From 0867ae918e50f6f3b896bf9750845ee9c39f19ae Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 2 Sep 2026 13:52:26 -0600 Subject: [PATCH] feat(readarr): fetch the shared indexer settings and expose them on the CLI The fetched settings are only written into app state when nothing is already held there, so a refresh landing while the user has the settings prompt open cannot overwrite edits in progress. The fixtures give every field a distinct value. Settings whose fields all share one value cannot detect two of them being mapped to each other's keys. --- src/cli/readarr/get_command_handler.rs | 9 +++ src/cli/readarr/get_command_handler_tests.rs | 36 ++++++++++ src/network/readarr_network/indexers/mod.rs | 25 ++++++- .../readarr_indexers_network_tests.rs | 72 ++++++++++++++++++- src/network/readarr_network/mod.rs | 6 ++ .../readarr_network_test_utils.rs | 32 ++++++++- .../readarr_network/readarr_network_tests.rs | 1 + 7 files changed, 177 insertions(+), 4 deletions(-) diff --git a/src/cli/readarr/get_command_handler.rs b/src/cli/readarr/get_command_handler.rs index 65b8639b..225ce3a3 100644 --- a/src/cli/readarr/get_command_handler.rs +++ b/src/cli/readarr/get_command_handler.rs @@ -18,6 +18,8 @@ mod get_command_handler_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum ReadarrGetCommand { + #[command(about = "Get the shared settings for all indexers")] + AllIndexerSettings, #[command(about = "Get detailed information for the author with the given ID")] AuthorDetails { #[arg( @@ -71,6 +73,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrGetCommand> for ReadarrGetCommandH async fn handle(self) -> Result { let result = match self.command { + ReadarrGetCommand::AllIndexerSettings => { + let resp = self + .network + .handle_network_event(ReadarrEvent::GetAllIndexerSettings.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } ReadarrGetCommand::AuthorDetails { author_id } => { let resp = self .network diff --git a/src/cli/readarr/get_command_handler_tests.rs b/src/cli/readarr/get_command_handler_tests.rs index cc590648..6ec4733b 100644 --- a/src/cli/readarr/get_command_handler_tests.rs +++ b/src/cli/readarr/get_command_handler_tests.rs @@ -24,6 +24,14 @@ mod tests { use super::*; + #[test] + fn test_all_indexer_settings_has_no_arg_requirements() { + let result = + Cli::command().try_get_matches_from(["managarr", "readarr", "get", "all-indexer-settings"]); + + assert_ok!(&result); + } + #[test] fn test_author_details_requires_author_id() { let result = @@ -103,6 +111,34 @@ mod tests { network::{MockNetworkTrait, NetworkEvent, readarr_network::ReadarrEvent}, }; + #[tokio::test] + async fn test_handle_get_all_indexer_settings_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + ReadarrEvent::GetAllIndexerSettings.into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Readarr(ReadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let get_all_indexer_settings_command = ReadarrGetCommand::AllIndexerSettings; + + let result = ReadarrGetCommandHandler::with( + &app_arc, + get_all_indexer_settings_command, + &mut mock_network, + ) + .handle() + .await; + + assert_ok!(&result); + } + #[tokio::test] async fn test_handle_get_author_details_command() { let expected_author_id = 1; diff --git a/src/network/readarr_network/indexers/mod.rs b/src/network/readarr_network/indexers/mod.rs index 98afff73..db0bde54 100644 --- a/src/network/readarr_network/indexers/mod.rs +++ b/src/network/readarr_network/indexers/mod.rs @@ -1,5 +1,7 @@ use crate::models::servarr_data::modals::IndexerTestResultModalItem; -use crate::models::servarr_models::{EditIndexerParams, Indexer, IndexerTestResult}; +use crate::models::servarr_models::{ + EditIndexerParams, Indexer, IndexerSettings, IndexerTestResult, +}; use crate::models::stateful_table::StatefulTable; use crate::network::readarr_network::ReadarrEvent; use crate::network::{Network, RequestMethod}; @@ -34,6 +36,27 @@ impl Network<'_, '_> { .await } + pub(in crate::network::readarr_network) async fn get_all_readarr_indexer_settings( + &mut self, + ) -> Result { + info!("Fetching Readarr indexer settings"); + let event = ReadarrEvent::GetAllIndexerSettings; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), IndexerSettings>(request_props, |indexer_settings, mut app| { + if app.data.readarr_data.indexer_settings.is_none() { + app.data.readarr_data.indexer_settings = Some(indexer_settings); + } else { + debug!("Indexer Settings are being modified. Ignoring update..."); + } + }) + .await + } + pub(in crate::network::readarr_network) async fn edit_readarr_indexer( &mut self, mut edit_indexer_params: EditIndexerParams, diff --git a/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs b/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs index 153c4ac0..c9f5920d 100644 --- a/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs +++ b/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs @@ -7,8 +7,8 @@ mod tests { use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; use crate::network::readarr_network::ReadarrEvent; use crate::network::readarr_network::readarr_network_test_utils::test_utils::{ - INDEXER_JSON, INDEXER_TEST_RESULTS_JSON, indexer, indexer_test_results, stale_indexer, - tested_indexers, + INDEXER_JSON, INDEXER_SETTINGS_JSON, INDEXER_TEST_RESULTS_JSON, indexer, indexer_settings, + indexer_test_results, stale_indexer, stale_indexer_settings, tested_indexers, }; use bimap::BiMap; use mockito::Matcher; @@ -646,4 +646,72 @@ mod tests { .items ); } + + #[tokio::test] + async fn test_handle_get_all_readarr_indexer_settings_event() { + let (mock, app, _server) = MockServarrApi::get() + .returns(serde_json::from_str(INDEXER_SETTINGS_JSON).unwrap()) + .build_for(ReadarrEvent::GetAllIndexerSettings) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetAllIndexerSettings) + .await; + + mock.assert_async().await; + let ReadarrSerdeable::IndexerSettings(settings) = assert_ok!(result) else { + panic!("Expected IndexerSettings") + }; + assert_eq!(settings, indexer_settings()); + let app = app.lock().await; + assert_some_eq_x!(&app.data.readarr_data.indexer_settings, &indexer_settings()); + } + + #[tokio::test] + async fn test_handle_get_all_readarr_indexer_settings_event_does_not_overwrite_pending_edits() { + let (mock, app, _server) = MockServarrApi::get() + .returns(serde_json::from_str(INDEXER_SETTINGS_JSON).unwrap()) + .build_for(ReadarrEvent::GetAllIndexerSettings) + .await; + { + let mut app = app.lock().await; + app.server_tabs.set_index(3); + app.data.readarr_data.indexer_settings = Some(stale_indexer_settings()); + } + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetAllIndexerSettings) + .await; + + mock.assert_async().await; + assert_ok!(&result); + let app = app.lock().await; + assert_some_eq_x!( + &app.data.readarr_data.indexer_settings, + &stale_indexer_settings() + ); + } + + #[tokio::test] + async fn test_handle_get_all_readarr_indexer_settings_event_failure() { + let (mock, app, _server) = MockServarrApi::get() + .returns(serde_json::from_str(INDEXER_SETTINGS_JSON).unwrap()) + .status(500) + .build_for(ReadarrEvent::GetAllIndexerSettings) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetAllIndexerSettings) + .await; + + mock.assert_async().await; + assert_err!(result); + let app = app.lock().await; + assert_none!(&app.data.readarr_data.indexer_settings); + } } diff --git a/src/network/readarr_network/mod.rs b/src/network/readarr_network/mod.rs index a16ff907..551dd219 100644 --- a/src/network/readarr_network/mod.rs +++ b/src/network/readarr_network/mod.rs @@ -45,6 +45,7 @@ pub enum ReadarrEvent { DownloadRelease(ReleaseDownloadBody), EditAuthor(EditAuthorParams), EditIndexer(EditIndexerParams), + GetAllIndexerSettings, GetAuthorDetails(i64), GetAuthorHistory(i64), GetAuthorReleases(i64), @@ -112,6 +113,7 @@ impl NetworkResource for ReadarrEvent { | ReadarrEvent::UpdateAndScanAuthor(_) | ReadarrEvent::UpdateDownloads => "/command", ReadarrEvent::GetHostConfig | ReadarrEvent::GetSecurityConfig => "/config/host", + ReadarrEvent::GetAllIndexerSettings => "/config/indexer", ReadarrEvent::GetDiskSpace => "/diskspace", ReadarrEvent::GetBookEditions(_) => "/edition", ReadarrEvent::HealthCheck => "/health", @@ -207,6 +209,10 @@ impl Network<'_, '_> { .edit_readarr_indexer(edit_indexer_params) .await .map(ReadarrSerdeable::from), + ReadarrEvent::GetAllIndexerSettings => self + .get_all_readarr_indexer_settings() + .await + .map(ReadarrSerdeable::from), ReadarrEvent::GetAuthorDetails(author_id) => self .get_author_details(author_id) .await diff --git a/src/network/readarr_network/readarr_network_test_utils.rs b/src/network/readarr_network/readarr_network_test_utils.rs index 23537302..60086768 100644 --- a/src/network/readarr_network/readarr_network_test_utils.rs +++ b/src/network/readarr_network/readarr_network_test_utils.rs @@ -9,7 +9,9 @@ pub mod test_utils { ReadarrRelease, }; use crate::models::servarr_data::modals::IndexerTestResultModalItem; - use crate::models::servarr_models::{Indexer, IndexerField, Quality, QualityWrapper, RootFolder}; + use crate::models::servarr_models::{ + Indexer, IndexerField, IndexerSettings, Quality, QualityWrapper, RootFolder, + }; use chrono::DateTime; use serde_json::{Number, json}; @@ -521,4 +523,32 @@ pub mod test_utils { }, ] } + + pub const INDEXER_SETTINGS_JSON: &str = r#"{ + "id": 4, + "minimumAge": 17, + "retention": 43, + "maximumSize": 26500, + "rssSyncInterval": 35 + }"#; + + pub fn indexer_settings() -> IndexerSettings { + IndexerSettings { + id: 4, + minimum_age: 17, + retention: 43, + maximum_size: 26500, + rss_sync_interval: 35, + } + } + + pub fn stale_indexer_settings() -> IndexerSettings { + IndexerSettings { + id: 9, + minimum_age: 22, + retention: 58, + maximum_size: 31200, + rss_sync_interval: 90, + } + } } diff --git a/src/network/readarr_network/readarr_network_tests.rs b/src/network/readarr_network/readarr_network_tests.rs index 31db1641..2b6bfa1b 100644 --- a/src/network/readarr_network/readarr_network_tests.rs +++ b/src/network/readarr_network/readarr_network_tests.rs @@ -42,6 +42,7 @@ mod tests { #[case(ReadarrEvent::UpdateDownloads, "/command")] #[case(ReadarrEvent::GetHostConfig, "/config/host")] #[case(ReadarrEvent::GetSecurityConfig, "/config/host")] + #[case(ReadarrEvent::GetAllIndexerSettings, "/config/indexer")] #[case(ReadarrEvent::GetDiskSpace, "/diskspace")] #[case(ReadarrEvent::GetBookEditions(1), "/edition")] #[case(ReadarrEvent::HealthCheck, "/health")]