diff --git a/src/cli/radarr/edit_command_handler.rs b/src/cli/radarr/edit_command_handler.rs index ca731d3..0d206d8 100644 --- a/src/cli/radarr/edit_command_handler.rs +++ b/src/cli/radarr/edit_command_handler.rs @@ -9,9 +9,9 @@ use crate::{ cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command}, models::{ radarr_models::{ - EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, - MinimumAvailability, RadarrSerdeable, + EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable, }, + servarr_models::EditIndexerParams, Serdeable, }, network::{radarr_network::RadarrEvent, NetworkTrait}, diff --git a/src/cli/radarr/edit_command_handler_tests.rs b/src/cli/radarr/edit_command_handler_tests.rs index a960954..5fc2a2a 100644 --- a/src/cli/radarr/edit_command_handler_tests.rs +++ b/src/cli/radarr/edit_command_handler_tests.rs @@ -810,9 +810,10 @@ mod tests { }, models::{ radarr_models::{ - EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, - MinimumAvailability, RadarrSerdeable, + EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, + RadarrSerdeable, }, + servarr_models::EditIndexerParams, Serdeable, }, network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs index 057b5ab..8c33809 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs @@ -5,7 +5,7 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::KeyEventHandler; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use strum::IntoEnumIterator; @@ -14,7 +14,7 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; use crate::models::BlockSelectionState; @@ -69,7 +69,7 @@ mod tests { use std::sync::atomic::Ordering; use crate::app::App; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use pretty_assertions::assert_eq; use super::*; @@ -334,7 +334,7 @@ mod tests { use std::sync::atomic::Ordering; use crate::app::App; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::{ EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, }; @@ -759,7 +759,7 @@ mod tests { use rstest::rstest; use crate::app::App; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::{ servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, BlockSelectionState, }; @@ -1224,7 +1224,7 @@ mod tests { use super::*; use crate::app::App; use crate::event::Key; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use pretty_assertions::assert_eq; use rstest::rstest; @@ -1281,7 +1281,7 @@ mod tests { mod test_handle_key_char { use crate::app::App; - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; use crate::models::BlockSelectionState; use crate::network::radarr_network::RadarrEvent; diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index 50d646c..5fd3b1d 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -237,7 +237,7 @@ mod tests { } mod test_handle_submit { - use crate::models::servarr_data::radarr::modals::EditIndexerModal; + use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::{ RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, }; diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 6049e5f..a99974e 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -179,22 +179,6 @@ pub struct EditCollectionParams { pub search_on_add: Option, } -#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct EditIndexerParams { - pub indexer_id: i64, - pub name: Option, - pub enable_rss: Option, - pub enable_automatic_search: Option, - pub enable_interactive_search: Option, - pub url: Option, - pub api_key: Option, - pub seed_ratio: Option, - pub tags: Option>, - pub priority: Option, - pub clear_tags: bool, -} - #[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct EditMovieParams { diff --git a/src/models/servarr_data/modals.rs b/src/models/servarr_data/modals.rs index 84d8922..0105249 100644 --- a/src/models/servarr_data/modals.rs +++ b/src/models/servarr_data/modals.rs @@ -1,5 +1,17 @@ use crate::models::HorizontallyScrollableText; +#[derive(Default, Debug, PartialEq, Eq)] +pub struct EditIndexerModal { + pub name: HorizontallyScrollableText, + pub enable_rss: Option, + pub enable_automatic_search: Option, + pub enable_interactive_search: Option, + pub url: HorizontallyScrollableText, + pub api_key: HorizontallyScrollableText, + pub seed_ratio: HorizontallyScrollableText, + pub tags: HorizontallyScrollableText, +} + #[derive(Default, Clone, Eq, PartialEq, Debug)] pub struct IndexerTestResultModalItem { pub name: String, diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index 843d5ea..1b9c0d9 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -3,6 +3,7 @@ use strum::IntoEnumIterator; use crate::models::radarr_models::{ Collection, Credit, MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, RadarrRelease, }; +use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::RadarrData; use crate::models::servarr_models::{Indexer, RootFolder}; use crate::models::stateful_list::StatefulList; @@ -25,18 +26,6 @@ pub struct MovieDetailsModal { pub movie_releases: StatefulTable, } -#[derive(Default, Debug, PartialEq, Eq)] -pub struct EditIndexerModal { - pub name: HorizontallyScrollableText, - pub enable_rss: Option, - pub enable_automatic_search: Option, - pub enable_interactive_search: Option, - pub url: HorizontallyScrollableText, - pub api_key: HorizontallyScrollableText, - pub seed_ratio: HorizontallyScrollableText, - pub tags: HorizontallyScrollableText, -} - impl From<&RadarrData<'_>> for EditIndexerModal { fn from(radarr_data: &RadarrData<'_>) -> EditIndexerModal { let mut edit_indexer_modal = EditIndexerModal::default(); diff --git a/src/models/servarr_data/radarr/modals_tests.rs b/src/models/servarr_data/radarr/modals_tests.rs index 96e36ad..e19d8a9 100644 --- a/src/models/servarr_data/radarr/modals_tests.rs +++ b/src/models/servarr_data/radarr/modals_tests.rs @@ -16,8 +16,6 @@ mod test { #[rstest] fn test_edit_indexer_modal_from_radarr_data(#[values(true, false)] seed_ratio_present: bool) { - use crate::models::servarr_models::{Indexer, IndexerField}; - let mut radarr_data = RadarrData { tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), ..RadarrData::default() diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 207c2e2..dcae501 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::modals::{EditIndexerModal, IndexerTestResultModalItem}; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, MovieDetailsModal, + AddMovieModal, EditCollectionModal, 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/modals.rs b/src/models/servarr_data/sonarr/modals.rs index c5a128d..208c9a0 100644 --- a/src/models/servarr_data/sonarr/modals.rs +++ b/src/models/servarr_data/sonarr/modals.rs @@ -1,7 +1,8 @@ use strum::IntoEnumIterator; use crate::models::{ - servarr_models::RootFolder, + servarr_data::modals::EditIndexerModal, + servarr_models::{Indexer, RootFolder}, sonarr_models::{Episode, SeriesMonitor, SeriesType, SonarrHistoryItem, SonarrRelease}, stateful_list::StatefulList, stateful_table::StatefulTable, @@ -63,6 +64,84 @@ impl From<&SonarrData> for AddSeriesModal { } } +impl From<&SonarrData> for EditIndexerModal { + fn from(sonarr_data: &SonarrData) -> EditIndexerModal { + let mut edit_indexer_modal = EditIndexerModal::default(); + let Indexer { + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + tags, + fields, + .. + } = sonarr_data.indexers.current_selection(); + let seed_ratio_field_option = fields + .as_ref() + .unwrap() + .iter() + .find(|field| field.name.as_ref().unwrap() == "seedCriteria.seedRatio"); + let seed_ratio_value_option = if let Some(seed_ratio_field) = seed_ratio_field_option { + seed_ratio_field.value.clone() + } else { + None + }; + + edit_indexer_modal.name = name.clone().unwrap().into(); + edit_indexer_modal.enable_rss = Some(*enable_rss); + edit_indexer_modal.enable_automatic_search = Some(*enable_automatic_search); + edit_indexer_modal.enable_interactive_search = Some(*enable_interactive_search); + edit_indexer_modal.url = fields + .as_ref() + .unwrap() + .iter() + .find(|field| field.name.as_ref().unwrap() == "baseUrl") + .unwrap() + .value + .clone() + .unwrap() + .as_str() + .unwrap() + .into(); + edit_indexer_modal.api_key = fields + .as_ref() + .unwrap() + .iter() + .find(|field| field.name.as_ref().unwrap() == "apiKey") + .unwrap() + .value + .clone() + .unwrap() + .as_str() + .unwrap() + .into(); + + if seed_ratio_value_option.is_some() { + edit_indexer_modal.seed_ratio = seed_ratio_value_option + .unwrap() + .as_f64() + .unwrap() + .to_string() + .into(); + } + + edit_indexer_modal.tags = tags + .iter() + .map(|tag_id| { + sonarr_data + .tags_map + .get_by_left(&tag_id.as_i64().unwrap()) + .unwrap() + .clone() + }) + .collect::>() + .join(", ") + .into(); + + edit_indexer_modal + } +} + #[derive(Default)] pub struct EpisodeDetailsModal { pub episode_details: ScrollableText, diff --git a/src/models/servarr_data/sonarr/modals_tests.rs b/src/models/servarr_data/sonarr/modals_tests.rs index fc25384..0dfd63c 100644 --- a/src/models/servarr_data/sonarr/modals_tests.rs +++ b/src/models/servarr_data/sonarr/modals_tests.rs @@ -1,13 +1,19 @@ #[cfg(test)] mod tests { use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; + use rstest::rstest; use strum::IntoEnumIterator; + use crate::models::servarr_models::{Indexer, IndexerField}; use crate::models::{ servarr_data::sonarr::{modals::AddSeriesModal, sonarr_data::SonarrData}, servarr_models::RootFolder, sonarr_models::{SeriesMonitor, SeriesType}, }; + use serde_json::{Number, Value}; + + use crate::models::servarr_data::modals::EditIndexerModal; #[test] fn test_add_series_modal_from_sonarr_data() { @@ -55,4 +61,55 @@ mod tests { assert!(add_series_modal.tags.text.is_empty()); assert!(add_series_modal.use_season_folder); } + + #[rstest] + fn test_edit_indexer_modal_from_sonarr_data(#[values(true, false)] seed_ratio_present: bool) { + let mut sonarr_data = SonarrData { + tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), + ..SonarrData::default() + }; + let mut fields = vec![ + IndexerField { + name: Some("baseUrl".to_owned()), + value: Some(Value::String("https://test.com".to_owned())), + }, + IndexerField { + name: Some("apiKey".to_owned()), + value: Some(Value::String("1234".to_owned())), + }, + ]; + + if seed_ratio_present { + fields.push(IndexerField { + name: Some("seedCriteria.seedRatio".to_owned()), + value: Some(Value::from(1.2f64)), + }); + } + + let indexer = Indexer { + name: Some("Test".to_owned()), + enable_rss: true, + enable_automatic_search: true, + enable_interactive_search: true, + tags: vec![Number::from(1), Number::from(2)], + fields: Some(fields), + ..Indexer::default() + }; + sonarr_data.indexers.set_items(vec![indexer]); + + let edit_indexer_modal = EditIndexerModal::from(&sonarr_data); + + assert_str_eq!(edit_indexer_modal.name.text, "Test"); + assert_eq!(edit_indexer_modal.enable_rss, Some(true)); + assert_eq!(edit_indexer_modal.enable_automatic_search, Some(true)); + assert_eq!(edit_indexer_modal.enable_interactive_search, Some(true)); + assert_str_eq!(edit_indexer_modal.url.text, "https://test.com"); + assert_str_eq!(edit_indexer_modal.api_key.text, "1234"); + + if seed_ratio_present { + assert_str_eq!(edit_indexer_modal.seed_ratio.text, "1.2"); + } else { + assert!(edit_indexer_modal.seed_ratio.text.is_empty()); + } + } } diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index bd2962f..76fbd78 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Utc}; use strum::EnumIter; use crate::models::{ - servarr_data::modals::IndexerTestResultModalItem, + servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}, servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder}, sonarr_models::{ AddSeriesSearchResult, BlocklistItem, DownloadRecord, IndexerSettings, Season, Series, @@ -29,6 +29,7 @@ pub struct SonarrData { pub delete_series_files: bool, pub downloads: StatefulTable, pub disk_space_vec: Vec, + pub edit_indexer_modal: Option, pub edit_root_folder: Option, pub history: StatefulTable, pub indexers: StatefulTable, @@ -69,6 +70,7 @@ impl Default for SonarrData { downloads: StatefulTable::default(), delete_series_files: false, disk_space_vec: Vec::new(), + edit_indexer_modal: None, edit_root_folder: None, history: StatefulTable::default(), indexers: 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 a698b8f..30c7ee6 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -56,6 +56,7 @@ mod tests { assert!(!sonarr_data.delete_series_files); assert!(sonarr_data.downloads.is_empty()); assert!(sonarr_data.disk_space_vec.is_empty()); + assert!(sonarr_data.edit_indexer_modal.is_none()); assert!(sonarr_data.edit_root_folder.is_none()); assert!(sonarr_data.history.is_empty()); assert!(sonarr_data.indexers.is_empty()); diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs index 02134e0..4f1810b 100644 --- a/src/models/servarr_models.rs +++ b/src/models/servarr_models.rs @@ -89,6 +89,22 @@ pub struct DiskSpace { pub total_space: i64, } +#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct EditIndexerParams { + pub indexer_id: i64, + pub name: Option, + pub enable_rss: Option, + pub enable_automatic_search: Option, + pub enable_interactive_search: Option, + pub url: Option, + pub api_key: Option, + pub seed_ratio: Option, + pub tags: Option>, + pub priority: Option, + pub clear_tags: bool, +} + #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct HostConfig { diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index efb6823..0c5a8c3 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -9,18 +9,18 @@ use urlencoding::encode; use crate::models::radarr_models::{ AddMovieBody, AddMovieOptions, AddMovieSearchResult, BlocklistResponse, Collection, CollectionMovie, Credit, CreditType, DeleteMovieParams, DownloadRecord, DownloadsResponse, - EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, IndexerTestResult, - Movie, MovieCommandBody, MovieHistoryItem, RadarrRelease, RadarrReleaseDownloadBody, - RadarrSerdeable, RadarrTask, RadarrTaskName, SystemStatus, + EditCollectionParams, EditMovieParams, IndexerSettings, IndexerTestResult, Movie, + MovieCommandBody, MovieHistoryItem, RadarrRelease, RadarrReleaseDownloadBody, RadarrSerdeable, + RadarrTask, RadarrTaskName, SystemStatus, }; -use crate::models::servarr_data::modals::IndexerTestResultModalItem; +use crate::models::servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}; use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, MovieDetailsModal, + AddMovieModal, EditCollectionModal, EditMovieModal, MovieDetailsModal, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::{ - AddRootFolderBody, CommandBody, DiskSpace, HostConfig, Indexer, LogResponse, QualityProfile, - QueueEvent, RootFolder, SecurityConfig, Tag, Update, + AddRootFolderBody, CommandBody, DiskSpace, EditIndexerParams, HostConfig, Indexer, LogResponse, + QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update, }; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; @@ -188,9 +188,10 @@ impl<'a, 'b> Network<'a, 'b> { .edit_collection(params) .await .map(RadarrSerdeable::from), - RadarrEvent::EditIndexer(params) => { - self.edit_indexer(params).await.map(RadarrSerdeable::from) - } + RadarrEvent::EditIndexer(params) => self + .edit_radarr_indexer(params) + .await + .map(RadarrSerdeable::from), RadarrEvent::EditMovie(params) => self.edit_movie(params).await.map(RadarrSerdeable::from), RadarrEvent::GetAllIndexerSettings => self .get_all_radarr_indexer_settings() @@ -882,7 +883,10 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn edit_indexer(&mut self, edit_indexer_params: Option) -> Result<()> { + async fn edit_radarr_indexer( + &mut self, + edit_indexer_params: Option, + ) -> Result<()> { let detail_event = RadarrEvent::GetIndexers; let event = RadarrEvent::EditIndexer(None); let id = if let Some(ref params) = edit_indexer_params { diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index fc71755..c75ffd3 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -4053,7 +4053,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event() { + async fn test_handle_edit_radarr_indexer_event() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -4153,7 +4153,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details( + async fn test_handle_edit_radarr_indexer_event_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details( ) { let indexer_details_json = json!({ "enableRss": true, @@ -4255,7 +4255,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details( + async fn test_handle_edit_radarr_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details( ) { let indexer_details_json = json!({ "enableRss": true, @@ -4371,7 +4371,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event_uses_provided_parameters() { + async fn test_handle_edit_radarr_indexer_event_uses_provided_parameters() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -4464,7 +4464,8 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event_uses_provided_parameters_defaults_to_previous_values() { + async fn test_handle_edit_radarr_indexer_event_uses_provided_parameters_defaults_to_previous_values( + ) { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -4525,7 +4526,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_indexer_event_uses_provided_parameters_clears_tags_when_clear_tags_is_true( + async fn test_handle_edit_radarr_indexer_event_uses_provided_parameters_clears_tags_when_clear_tags_is_true( ) { let indexer_details_json = json!({ "enableRss": true, diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 27ba7fc..20cbecc 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -8,15 +8,15 @@ use crate::{ models::{ radarr_models::IndexerTestResult, servarr_data::{ - modals::IndexerTestResultModalItem, + modals::{EditIndexerModal, IndexerTestResultModalItem}, sonarr::{ modals::{AddSeriesModal, EpisodeDetailsModal, SeasonDetailsModal}, sonarr_data::ActiveSonarrBlock, }, }, servarr_models::{ - AddRootFolderBody, CommandBody, DiskSpace, HostConfig, Indexer, Language, LogResponse, - QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update, + AddRootFolderBody, CommandBody, DiskSpace, EditIndexerParams, HostConfig, Indexer, Language, + LogResponse, QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update, }, sonarr_models::{ AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, BlocklistResponse, @@ -51,6 +51,7 @@ pub enum SonarrEvent { DeleteTag(i64), DownloadRelease(SonarrReleaseDownloadBody), EditAllIndexerSettings(Option), + EditIndexer(Option), GetAllIndexerSettings, GetBlocklist, GetDownloads, @@ -106,7 +107,9 @@ impl NetworkResource for SonarrEvent { SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode", SonarrEvent::GetHistory(_) | SonarrEvent::GetEpisodeHistory(_) => "/history", SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host", - SonarrEvent::GetIndexers | SonarrEvent::DeleteIndexer(_) => "/indexer", + SonarrEvent::GetIndexers | SonarrEvent::DeleteIndexer(_) | SonarrEvent::EditIndexer(_) => { + "/indexer" + } SonarrEvent::GetLanguageProfiles => "/languageprofile", SonarrEvent::GetLogs(_) => "/log", SonarrEvent::GetDiskSpace => "/diskspace", @@ -204,6 +207,10 @@ impl<'a, 'b> Network<'a, 'b> { .edit_all_sonarr_indexer_settings(params) .await .map(SonarrSerdeable::from), + SonarrEvent::EditIndexer(params) => self + .edit_sonarr_indexer(params) + .await + .map(SonarrSerdeable::from), SonarrEvent::GetBlocklist => self.get_sonarr_blocklist().await.map(SonarrSerdeable::from), SonarrEvent::GetDownloads => self.get_sonarr_downloads().await.map(SonarrSerdeable::from), SonarrEvent::GetEpisodes(series_id) => self @@ -800,6 +807,261 @@ impl<'a, 'b> Network<'a, 'b> { resp } + async fn edit_sonarr_indexer( + &mut self, + edit_indexer_params: Option, + ) -> Result<()> { + let detail_event = SonarrEvent::GetIndexers; + let event = SonarrEvent::EditIndexer(None); + let id = if let Some(ref params) = edit_indexer_params { + params.indexer_id + } else { + self + .app + .lock() + .await + .data + .sonarr_data + .indexers + .current_selection() + .id + }; + info!("Updating Sonarr indexer with ID: {id}"); + + info!("Fetching indexer details for indexer with ID: {id}"); + + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_indexer_body, _| { + response = detailed_indexer_body.to_string() + }) + .await?; + + info!("Constructing edit indexer body"); + + let mut detailed_indexer_body: Value = serde_json::from_str(&response).unwrap(); + let priority = detailed_indexer_body["priority"] + .as_i64() + .expect("Unable to deserialize 'priority'"); + + let ( + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + url, + api_key, + seed_ratio, + tags, + priority, + ) = if let Some(params) = edit_indexer_params { + let seed_ratio_field_option = detailed_indexer_body["fields"] + .as_array() + .unwrap() + .iter() + .find(|field| field["name"] == "seedCriteria.seedRatio"); + let name = params.name.unwrap_or( + detailed_indexer_body["name"] + .as_str() + .expect("Unable to deserialize 'name'") + .to_owned(), + ); + let enable_rss = params.enable_rss.unwrap_or( + detailed_indexer_body["enableRss"] + .as_bool() + .expect("Unable to deserialize 'enableRss'"), + ); + let enable_automatic_search = params.enable_automatic_search.unwrap_or( + detailed_indexer_body["enableAutomaticSearch"] + .as_bool() + .expect("Unable to deserialize 'enableAutomaticSearch"), + ); + let enable_interactive_search = params.enable_interactive_search.unwrap_or( + detailed_indexer_body["enableInteractiveSearch"] + .as_bool() + .expect("Unable to deserialize 'enableInteractiveSearch'"), + ); + let url = params.url.unwrap_or( + detailed_indexer_body["fields"] + .as_array() + .expect("Unable to deserialize 'fields'") + .iter() + .find(|field| field["name"] == "baseUrl") + .expect("Field 'baseUrl' was not found in the 'fields' array") + .get("value") + .unwrap_or(&json!("")) + .as_str() + .expect("Unable to deserialize 'baseUrl value'") + .to_owned(), + ); + let api_key = params.api_key.unwrap_or( + detailed_indexer_body["fields"] + .as_array() + .expect("Unable to deserialize 'fields'") + .iter() + .find(|field| field["name"] == "apiKey") + .expect("Field 'apiKey' was not found in the 'fields' array") + .get("value") + .unwrap_or(&json!("")) + .as_str() + .expect("Unable to deserialize 'apiKey value'") + .to_owned(), + ); + let seed_ratio = params.seed_ratio.unwrap_or_else(|| { + if let Some(seed_ratio_field) = seed_ratio_field_option { + return seed_ratio_field + .get("value") + .unwrap_or(&json!("")) + .as_str() + .expect("Unable to deserialize 'seedCriteria.seedRatio value'") + .to_owned(); + } + + String::new() + }); + let tags = if params.clear_tags { + vec![] + } else { + params.tags.unwrap_or( + detailed_indexer_body["tags"] + .as_array() + .expect("Unable to deserialize 'tags'") + .iter() + .map(|item| item.as_i64().expect("Unable to deserialize tag ID")) + .collect(), + ) + }; + let priority = params.priority.unwrap_or(priority); + + ( + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + url, + api_key, + seed_ratio, + tags, + priority, + ) + } else { + let tags = self + .app + .lock() + .await + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + let tag_ids_vec = self.extract_and_add_sonarr_tag_ids_vec(tags).await; + let mut app = self.app.lock().await; + + let params = { + let EditIndexerModal { + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + url, + api_key, + seed_ratio, + .. + } = app.data.sonarr_data.edit_indexer_modal.as_ref().unwrap(); + + ( + name.text.clone(), + enable_rss.unwrap_or_default(), + enable_automatic_search.unwrap_or_default(), + enable_interactive_search.unwrap_or_default(), + url.text.clone(), + api_key.text.clone(), + seed_ratio.text.clone(), + tag_ids_vec, + priority, + ) + }; + + app.data.sonarr_data.edit_indexer_modal = None; + + params + }; + + *detailed_indexer_body.get_mut("name").unwrap() = json!(name); + *detailed_indexer_body.get_mut("priority").unwrap() = json!(priority); + *detailed_indexer_body.get_mut("enableRss").unwrap() = json!(enable_rss); + *detailed_indexer_body + .get_mut("enableAutomaticSearch") + .unwrap() = json!(enable_automatic_search); + *detailed_indexer_body + .get_mut("enableInteractiveSearch") + .unwrap() = json!(enable_interactive_search); + *detailed_indexer_body + .get_mut("fields") + .unwrap() + .as_array_mut() + .unwrap() + .iter_mut() + .find(|field| field["name"] == "baseUrl") + .unwrap() + .get_mut("value") + .unwrap() = json!(url); + *detailed_indexer_body + .get_mut("fields") + .unwrap() + .as_array_mut() + .unwrap() + .iter_mut() + .find(|field| field["name"] == "apiKey") + .unwrap() + .get_mut("value") + .unwrap() = json!(api_key); + *detailed_indexer_body.get_mut("tags").unwrap() = json!(tags); + let seed_ratio_field_option = detailed_indexer_body + .get_mut("fields") + .unwrap() + .as_array_mut() + .unwrap() + .iter_mut() + .find(|field| field["name"] == "seedCriteria.seedRatio"); + if let Some(seed_ratio_field) = seed_ratio_field_option { + seed_ratio_field + .as_object_mut() + .unwrap() + .insert("value".to_string(), json!(seed_ratio)); + } + + debug!("Edit indexer body: {detailed_indexer_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_indexer_body), + Some(format!("/{id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + async fn get_all_sonarr_indexer_settings(&mut self) -> Result { info!("Fetching Sonarr indexer settings"); let event = SonarrEvent::GetAllIndexerSettings; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 4952610..16ad396 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -22,14 +22,14 @@ mod test { use crate::app::{App, ServarrConfig}; use crate::models::radarr_models::IndexerTestResult; - use crate::models::servarr_data::modals::IndexerTestResultModalItem; + use crate::models::servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}; use crate::models::servarr_data::sonarr::modals::{ AddSeriesModal, EpisodeDetailsModal, SeasonDetailsModal, }; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_models::{ - DiskSpace, HostConfig, Indexer, IndexerField, Language, LogResponse, Quality, QualityProfile, - QualityWrapper, QueueEvent, RootFolder, SecurityConfig, Tag, Update, + DiskSpace, EditIndexerParams, HostConfig, Indexer, IndexerField, Language, LogResponse, + Quality, QualityProfile, QualityWrapper, QueueEvent, RootFolder, SecurityConfig, Tag, Update, }; use crate::models::sonarr_models::{ BlocklistItem, DeleteSeriesParams, DownloadRecord, DownloadsResponse, Episode, EpisodeFile, @@ -205,7 +205,12 @@ mod test { #[rstest] fn test_resource_indexer( - #[values(SonarrEvent::GetIndexers, SonarrEvent::DeleteIndexer(None))] event: SonarrEvent, + #[values( + SonarrEvent::GetIndexers, + SonarrEvent::DeleteIndexer(None), + SonarrEvent::EditIndexer(None) + )] + event: SonarrEvent, ) { assert_str_eq!(event.resource(), "/indexer"); } @@ -1126,6 +1131,565 @@ mod test { async_server.assert_async().await; } + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event() { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.2", + }, + ], + "tags": [1], + "id": 1 + }); + let expected_indexer_edit_body_json = json!({ + "enableRss": false, + "enableAutomaticSearch": false, + "enableInteractiveSearch": false, + "name": "Test Update", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://localhost:9696/1/", + }, + { + "name": "apiKey", + "value": "test1234", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.3", + }, + ], + "tags": [1, 2], + "id": 1 + }); + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let edit_indexer_modal = EditIndexerModal { + name: "Test Update".into(), + enable_rss: Some(false), + enable_automatic_search: Some(false), + enable_interactive_search: Some(false), + url: "https://localhost:9696/1/".into(), + api_key: "test1234".into(), + seed_ratio: "1.3".into(), + tags: "usenet, testing".into(), + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); + } + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(None)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + + let app = app_arc.lock().await; + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); + } + + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details( + ) { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + ], + "tags": [1], + "id": 1 + }); + let expected_indexer_edit_body_json = json!({ + "enableRss": false, + "enableAutomaticSearch": false, + "enableInteractiveSearch": false, + "name": "Test Update", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://localhost:9696/1/", + }, + { + "name": "apiKey", + "value": "test1234", + }, + ], + "tags": [1, 2], + "id": 1 + }); + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let edit_indexer_modal = EditIndexerModal { + name: "Test Update".into(), + enable_rss: Some(false), + enable_automatic_search: Some(false), + enable_interactive_search: Some(false), + url: "https://localhost:9696/1/".into(), + api_key: "test1234".into(), + seed_ratio: "1.3".into(), + tags: "usenet, testing".into(), + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + let mut indexer = indexer(); + indexer.fields = Some( + indexer + .fields + .unwrap() + .into_iter() + .filter(|field| field.name != Some("seedCriteria.seedRatio".to_string())) + .collect(), + ); + app.data.sonarr_data.indexers.set_items(vec![indexer]); + } + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(None)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + + let app = app_arc.lock().await; + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); + } + + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details( + ) { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + }, + ], + "tags": [1], + "id": 1 + }); + let expected_indexer_edit_body_json = json!({ + "enableRss": false, + "enableAutomaticSearch": false, + "enableInteractiveSearch": false, + "name": "Test Update", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://localhost:9696/1/", + }, + { + "name": "apiKey", + "value": "test1234", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.3", + }, + ], + "tags": [1, 2], + "id": 1 + }); + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let edit_indexer_modal = EditIndexerModal { + name: "Test Update".into(), + enable_rss: Some(false), + enable_automatic_search: Some(false), + enable_interactive_search: Some(false), + url: "https://localhost:9696/1/".into(), + api_key: "test1234".into(), + seed_ratio: "1.3".into(), + tags: "usenet, testing".into(), + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + let mut indexer = indexer(); + indexer.fields = Some( + indexer + .fields + .unwrap() + .into_iter() + .map(|mut field| { + if field.name == Some("seedCriteria.seedRatio".to_string()) { + field.value = None; + field + } else { + field + } + }) + .collect(), + ); + app.data.sonarr_data.indexers.set_items(vec![indexer]); + } + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(None)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + + let app = app_arc.lock().await; + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); + } + + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event_uses_provided_parameters() { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.2", + }, + ], + "tags": [1], + "id": 1 + }); + let expected_indexer_edit_body_json = json!({ + "enableRss": false, + "enableAutomaticSearch": false, + "enableInteractiveSearch": false, + "name": "Test Update", + "priority": 25, + "fields": [ + { + "name": "baseUrl", + "value": "https://localhost:9696/1/", + }, + { + "name": "apiKey", + "value": "test1234", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.3", + }, + ], + "tags": [1, 2], + "id": 1 + }); + let edit_indexer_params = EditIndexerParams { + indexer_id: 1, + name: Some("Test Update".to_owned()), + enable_rss: Some(false), + enable_automatic_search: Some(false), + enable_interactive_search: Some(false), + url: Some("https://localhost:9696/1/".to_owned()), + api_key: Some("test1234".to_owned()), + seed_ratio: Some("1.3".to_owned()), + tags: Some(vec![1, 2]), + priority: Some(25), + ..EditIndexerParams::default() + }; + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event_uses_provided_parameters_defaults_to_previous_values( + ) { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.2", + }, + ], + "tags": [1], + "id": 1 + }); + let edit_indexer_params = EditIndexerParams { + indexer_id: 1, + ..EditIndexerParams::default() + }; + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json.clone()), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(indexer_details_json)) + .create_async() + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_edit_sonarr_indexer_event_uses_provided_parameters_clears_tags_when_clear_tags_is_true( + ) { + let indexer_details_json = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.2", + }, + ], + "tags": [1, 2], + "id": 1 + }); + let expected_edit_indexer_body = json!({ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "name": "Test Indexer", + "priority": 1, + "fields": [ + { + "name": "baseUrl", + "value": "https://test.com", + }, + { + "name": "apiKey", + "value": "", + }, + { + "name": "seedCriteria.seedRatio", + "value": "1.2", + }, + ], + "tags": [], + "id": 1 + }); + let edit_indexer_params = EditIndexerParams { + indexer_id: 1, + clear_tags: true, + ..EditIndexerParams::default() + }; + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + SonarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!("/api/v3{}/1", SonarrEvent::EditIndexer(None).resource()).as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_edit_indexer_body)) + .create_async() + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + } + #[rstest] #[tokio::test] async fn test_handle_get_sonarr_blocklist_event(#[values(true, false)] use_custom_sorting: bool) {