From 6896fcc13465928066474e43d2926fbe8c3a5b9e Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 22 Nov 2024 17:35:36 -0700 Subject: [PATCH] feat(network): Support for testing all Sonarr indexers at once --- .../test_all_indexers_handler_tests.rs | 6 +- src/models/servarr_data/mod.rs | 1 + src/models/servarr_data/modals.rs | 8 ++ src/models/servarr_data/radarr/modals.rs | 7 -- src/models/servarr_data/radarr/radarr_data.rs | 4 +- src/models/servarr_data/sonarr/sonarr_data.rs | 3 + .../servarr_data/sonarr/sonarr_data_tests.rs | 1 + src/models/sonarr_models.rs | 3 + src/models/sonarr_models_tests.rs | 16 +++ src/network/radarr_network.rs | 13 ++- src/network/radarr_network_tests.rs | 2 +- src/network/sonarr_network.rs | 63 ++++++++++- src/network/sonarr_network_tests.rs | 100 ++++++++++++++++++ .../indexers/test_all_indexers_ui.rs | 2 +- 14 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 src/models/servarr_data/modals.rs diff --git a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs index 4ac81cc..708e22d 100644 --- a/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/test_all_indexers_handler_tests.rs @@ -5,7 +5,7 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler; use crate::handlers::KeyEventHandler; - use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::stateful_table::StatefulTable; use strum::IntoEnumIterator; @@ -14,7 +14,7 @@ mod tests { use pretty_assertions::assert_str_eq; use rstest::rstest; - use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::stateful_table::StatefulTable; use crate::simple_stateful_iterable_vec; @@ -112,7 +112,7 @@ mod tests { mod test_handle_home_end { use crate::extended_stateful_iterable_vec; - use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::stateful_table::StatefulTable; use pretty_assertions::assert_str_eq; diff --git a/src/models/servarr_data/mod.rs b/src/models/servarr_data/mod.rs index a4ec084..c82e844 100644 --- a/src/models/servarr_data/mod.rs +++ b/src/models/servarr_data/mod.rs @@ -1,2 +1,3 @@ +pub mod modals; pub mod radarr; pub mod sonarr; diff --git a/src/models/servarr_data/modals.rs b/src/models/servarr_data/modals.rs new file mode 100644 index 0000000..84d8922 --- /dev/null +++ b/src/models/servarr_data/modals.rs @@ -0,0 +1,8 @@ +use crate::models::HorizontallyScrollableText; + +#[derive(Default, Clone, Eq, PartialEq, Debug)] +pub struct IndexerTestResultModalItem { + pub name: String, + pub is_valid: bool, + pub validation_failures: HorizontallyScrollableText, +} diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index 3f73e3e..2d7c893 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -291,10 +291,3 @@ impl From<&RadarrData<'_>> for EditCollectionModal { edit_collection_modal } } - -#[derive(Default, Clone, Eq, PartialEq, Debug)] -pub struct IndexerTestResultModalItem { - pub name: String, - pub is_valid: bool, - pub validation_failures: HorizontallyScrollableText, -} diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 483a1a0..207c2e2 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -9,9 +9,9 @@ use crate::models::radarr_models::{ AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DownloadRecord, IndexerSettings, Movie, RadarrTask, }; +use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, - MovieDetailsModal, + AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, MovieDetailsModal, }; use crate::models::servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder}; use crate::models::stateful_list::StatefulList; diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 26d3c37..6d44336 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Utc}; use strum::EnumIter; use crate::models::{ + servarr_data::modals::IndexerTestResultModalItem, servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder}, sonarr_models::{ BlocklistItem, DownloadRecord, IndexerSettings, Season, Series, SonarrHistoryItem, SonarrTask, @@ -26,6 +27,7 @@ pub struct SonarrData { pub history: StatefulTable, pub indexers: StatefulTable, pub indexer_settings: Option, + pub indexer_test_all_results: Option>, pub indexer_test_error: Option, pub logs: StatefulList, pub quality_profile_map: BiMap, @@ -53,6 +55,7 @@ impl Default for SonarrData { indexers: StatefulTable::default(), indexer_settings: None, indexer_test_error: None, + indexer_test_all_results: None, logs: StatefulList::default(), quality_profile_map: BiMap::new(), queued_events: StatefulTable::default(), diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index f7e1c70..b17150a 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -42,6 +42,7 @@ mod tests { assert!(sonarr_data.indexers.is_empty()); assert!(sonarr_data.indexer_settings.is_none()); assert!(sonarr_data.indexer_test_error.is_none()); + assert!(sonarr_data.indexer_test_all_results.is_none()); assert!(sonarr_data.logs.is_empty()); assert!(sonarr_data.quality_profile_map.is_empty()); assert!(sonarr_data.queued_events.is_empty()); diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 8973709..b215a79 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -10,6 +10,7 @@ use strum::EnumIter; use crate::serde_enum_from; use super::{ + radarr_models::IndexerTestResult, servarr_models::{ DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, Release, RootFolder, SecurityConfig, Tag, Update, @@ -437,6 +438,7 @@ pub enum SonarrSerdeable { HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), + IndexerTestResults(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), @@ -477,6 +479,7 @@ serde_enum_from!( HostConfig(HostConfig), IndexerSettings(IndexerSettings), Indexers(Vec), + IndexerTestResults(Vec), LogResponse(LogResponse), QualityProfiles(Vec), QueueEvents(Vec), diff --git a/src/models/sonarr_models_tests.rs b/src/models/sonarr_models_tests.rs index 1893ac6..a56af3d 100644 --- a/src/models/sonarr_models_tests.rs +++ b/src/models/sonarr_models_tests.rs @@ -4,6 +4,7 @@ mod tests { use serde_json::json; use crate::models::{ + radarr_models::IndexerTestResult, servarr_models::{ DiskSpace, HostConfig, Indexer, Log, LogResponse, QualityProfile, QueueEvent, Release, RootFolder, SecurityConfig, Tag, Update, @@ -438,4 +439,19 @@ mod tests { assert_eq!(sonarr_serdeable, SonarrSerdeable::Updates(updates)); } + + #[test] + fn test_sonarr_serdeable_from_indexer_test_results() { + let indexer_test_results = vec![IndexerTestResult { + id: 1, + ..IndexerTestResult::default() + }]; + + let sonarr_serdeable: SonarrSerdeable = indexer_test_results.clone().into(); + + assert_eq!( + sonarr_serdeable, + SonarrSerdeable::IndexerTestResults(indexer_test_results) + ); + } } diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index f38bdd8..dca19e9 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -12,9 +12,9 @@ use crate::models::radarr_models::{ EditIndexerParams, EditMovieParams, IndexerSettings, IndexerTestResult, Movie, MovieCommandBody, MovieHistoryItem, RadarrSerdeable, RadarrTask, RadarrTaskName, ReleaseDownloadBody, SystemStatus, }; +use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, - MovieDetailsModal, + AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, MovieDetailsModal, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::{ @@ -259,7 +259,10 @@ impl<'a, 'b> Network<'a, 'b> { .test_radarr_indexer(indexer_id) .await .map(RadarrSerdeable::from), - RadarrEvent::TestAllIndexers => self.test_all_indexers().await.map(RadarrSerdeable::from), + RadarrEvent::TestAllIndexers => self + .test_all_radarr_indexers() + .await + .map(RadarrSerdeable::from), RadarrEvent::TriggerAutomaticSearch(movie_id) => self .trigger_automatic_search(movie_id) .await @@ -2095,8 +2098,8 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn test_all_indexers(&mut self) -> Result> { - info!("Testing all indexers"); + async fn test_all_radarr_indexers(&mut self) -> Result> { + info!("Testing all Radarr indexers"); let event = RadarrEvent::TestAllIndexers; let mut request_props = self diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 1f090f2..50e1234 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -1065,7 +1065,7 @@ mod test { } #[tokio::test] - async fn test_handle_test_all_indexers_event() { + async fn test_handle_test_all_radarr_indexers_event() { let indexers = vec![ Indexer { id: 1, diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index ff44911..270f700 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -5,9 +5,13 @@ use serde_json::{json, Value}; use crate::{ models::{ - servarr_data::sonarr::{ - modals::{EpisodeDetailsModal, SeasonDetailsModal}, - sonarr_data::ActiveSonarrBlock, + radarr_models::IndexerTestResult, + servarr_data::{ + modals::IndexerTestResultModalItem, + sonarr::{ + modals::{EpisodeDetailsModal, SeasonDetailsModal}, + sonarr_data::ActiveSonarrBlock, + }, }, servarr_models::{ AddRootFolderBody, CommandBody, DiskSpace, HostConfig, Indexer, LogResponse, QualityProfile, @@ -68,6 +72,7 @@ pub enum SonarrEvent { MarkHistoryItemAsFailed(i64), StartTask(Option), TestIndexer(Option), + TestAllIndexers, } impl NetworkResource for SonarrEvent { @@ -100,6 +105,7 @@ impl NetworkResource for SonarrEvent { SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", SonarrEvent::StartTask(_) => "/command", SonarrEvent::TestIndexer(_) => "/indexer/test", + SonarrEvent::TestAllIndexers => "/indexer/testall", } } } @@ -230,6 +236,10 @@ impl<'a, 'b> Network<'a, 'b> { .test_sonarr_indexer(indexer_id) .await .map(SonarrSerdeable::from), + SonarrEvent::TestAllIndexers => self + .test_all_sonarr_indexers() + .await + .map(SonarrSerdeable::from), } } @@ -1376,6 +1386,53 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn test_all_sonarr_indexers(&mut self) -> Result> { + info!("Testing all Sonarr indexers"); + let event = SonarrEvent::TestAllIndexers; + + let mut request_props = self + .request_props_from(event, RequestMethod::Post, None, None, None) + .await; + request_props.ignore_status_code = true; + + self + .handle_request::<(), Vec>(request_props, |test_results, mut app| { + let mut test_all_indexer_results = StatefulTable::default(); + let indexers = app.data.sonarr_data.indexers.items.clone(); + let modal_test_results = test_results + .iter() + .map(|result| { + let name = indexers + .iter() + .filter(|&indexer| indexer.id == result.id) + .map(|indexer| indexer.name.clone()) + .nth(0) + .unwrap_or_default(); + let validation_failures = result + .validation_failures + .iter() + .map(|failure| { + format!( + "Failure for field '{}': {}", + failure.property_name, failure.error_message + ) + }) + .collect::>() + .join(", "); + + IndexerTestResultModalItem { + name: name.unwrap_or_default(), + is_valid: result.is_valid, + validation_failures: validation_failures.into(), + } + }) + .collect(); + test_all_indexer_results.set_items(modal_test_results); + app.data.sonarr_data.indexer_test_all_results = Some(test_all_indexer_results); + }) + .await + } + async fn extract_series_id(&mut self, series_id: Option) -> (i64, String) { let series_id = if let Some(id) = series_id { id diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index b6b2025..b7b67e0 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -15,6 +15,8 @@ mod test { use tokio_util::sync::CancellationToken; use crate::app::App; + use crate::models::radarr_models::IndexerTestResult; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal}; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_models::{ @@ -225,6 +227,7 @@ mod test { #[case(SonarrEvent::GetUpdates, "/update")] #[case(SonarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")] #[case(SonarrEvent::TestIndexer(None), "/indexer/test")] + #[case(SonarrEvent::TestAllIndexers, "/indexer/testall")] fn test_resource(#[case] event: SonarrEvent, #[case] expected_uri: String) { assert_str_eq!(event.resource(), expected_uri); } @@ -4128,6 +4131,103 @@ mod test { } } + #[tokio::test] + async fn test_handle_test_all_sonarr_indexers_event() { + let indexers = vec![ + Indexer { + id: 1, + name: Some("Test 1".to_owned()), + ..Indexer::default() + }, + Indexer { + id: 2, + name: Some("Test 2".to_owned()), + ..Indexer::default() + }, + ]; + let indexer_test_results_modal_items = vec![ + IndexerTestResultModalItem { + name: "Test 1".to_owned(), + is_valid: true, + validation_failures: HorizontallyScrollableText::default(), + }, + IndexerTestResultModalItem { + name: "Test 2".to_owned(), + is_valid: false, + validation_failures: "Failure for field 'test field 1': test error message, Failure for field 'test field 2': test error message 2".into(), + }, + ]; + let response_json = json!([ + { + "id": 1, + "isValid": true, + "validationFailures": [] + }, + { + "id": 2, + "isValid": false, + "validationFailures": [ + { + "propertyName": "test field 1", + "errorMessage": "test error message", + "severity": "error" + }, + { + "propertyName": "test field 2", + "errorMessage": "test error message 2", + "severity": "error" + }, + ] + }]); + let response: Vec = serde_json::from_value(response_json.clone()).unwrap(); + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Post, + None, + Some(response_json), + Some(400), + SonarrEvent::TestAllIndexers, + None, + None, + ) + .await; + app_arc + .lock() + .await + .data + .sonarr_data + .indexers + .set_items(indexers); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::IndexerTestResults(results) = network + .handle_sonarr_event(SonarrEvent::TestAllIndexers) + .await + .unwrap() + { + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .sonarr_data + .indexer_test_all_results + .is_some()); + assert_eq!( + app_arc + .lock() + .await + .data + .sonarr_data + .indexer_test_all_results + .as_ref() + .unwrap() + .items, + indexer_test_results_modal_items + ); + assert_eq!(results, response); + } + } + #[tokio::test] async fn test_extract_series_id() { let app_arc = Arc::new(Mutex::new(App::default())); diff --git a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs index e1a1b59..1b36a45 100644 --- a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs +++ b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs @@ -1,6 +1,6 @@ use crate::app::context_clues::{build_context_clue_string, BARE_POPUP_CONTEXT_CLUES}; use crate::app::App; -use crate::models::servarr_data::radarr::modals::IndexerTestResultModalItem; +use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::Route; use crate::ui::radarr_ui::indexers::draw_indexers;