diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index 103fa60..91a6fe1 100755 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -4,6 +4,9 @@ set -e echo "Running pre-push hook:" +echo "Executing: cargo fmt" +cargo fmt + echo "Executing: make lint" make lint diff --git a/.cargo-husky/hooks/pre-push b/.cargo-husky/hooks/pre-push index 103fa60..1160efc 100755 --- a/.cargo-husky/hooks/pre-push +++ b/.cargo-husky/hooks/pre-push @@ -4,6 +4,9 @@ set -e echo "Running pre-push hook:" +echo "Executing: cargo fmt --check" +cargo fmt --check + echo "Executing: make lint" make lint diff --git a/src/app/radarr/mod.rs b/src/app/radarr/mod.rs index 173b2c4..575a33b 100644 --- a/src/app/radarr/mod.rs +++ b/src/app/radarr/mod.rs @@ -77,7 +77,9 @@ impl<'a> App<'a> { } ActiveRadarrBlock::TestIndexer => { self - .dispatch_network_event(RadarrEvent::TestIndexer(None).into()) + .dispatch_network_event( + RadarrEvent::TestIndexer(self.extract_radarr_indexer_id().await).into(), + ) .await; } ActiveRadarrBlock::TestAllIndexers => { @@ -93,7 +95,7 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetQueuedEvents.into()) .await; self - .dispatch_network_event(RadarrEvent::GetLogs(None).into()) + .dispatch_network_event(RadarrEvent::GetLogs(500).into()) .await; } ActiveRadarrBlock::SystemUpdates => { @@ -103,17 +105,23 @@ impl<'a> App<'a> { } ActiveRadarrBlock::AddMovieSearchResults => { self - .dispatch_network_event(RadarrEvent::SearchNewMovie(None).into()) + .dispatch_network_event( + RadarrEvent::SearchNewMovie(self.extract_movie_search_query().await).into(), + ) .await; } ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => { self - .dispatch_network_event(RadarrEvent::GetMovieDetails(None).into()) + .dispatch_network_event( + RadarrEvent::GetMovieDetails(self.extract_movie_id().await).into(), + ) .await; } ActiveRadarrBlock::MovieHistory => { self - .dispatch_network_event(RadarrEvent::GetMovieHistory(None).into()) + .dispatch_network_event( + RadarrEvent::GetMovieHistory(self.extract_movie_id().await).into(), + ) .await; } ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => { @@ -123,7 +131,9 @@ impl<'a> App<'a> { || movie_details_modal.movie_crew.items.is_empty() => { self - .dispatch_network_event(RadarrEvent::GetMovieCredits(None).into()) + .dispatch_network_event( + RadarrEvent::GetMovieCredits(self.extract_movie_id().await).into(), + ) .await; } _ => (), @@ -132,7 +142,7 @@ impl<'a> App<'a> { ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() { Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => { self - .dispatch_network_event(RadarrEvent::GetReleases(None).into()) + .dispatch_network_event(RadarrEvent::GetReleases(self.extract_movie_id().await).into()) .await; } _ => (), @@ -219,4 +229,29 @@ impl<'a> App<'a> { .collection_movies .set_items(collection_movies); } + + async fn extract_movie_id(&self) -> i64 { + self.data.radarr_data.movies.current_selection().clone().id + } + + async fn extract_movie_search_query(&self) -> String { + self + .data + .radarr_data + .add_movie_search + .as_ref() + .expect("Add movie search is empty") + .text + .clone() + } + + async fn extract_radarr_indexer_id(&self) -> i64 { + self + .data + .radarr_data + .indexers + .current_selection() + .clone() + .id + } } diff --git a/src/app/radarr/radarr_tests.rs b/src/app/radarr/radarr_tests.rs index 45648d7..d1d1d2c 100644 --- a/src/app/radarr/radarr_tests.rs +++ b/src/app/radarr/radarr_tests.rs @@ -1,724 +1,794 @@ #[cfg(test)] mod tests { - mod radarr_tests { - use pretty_assertions::assert_eq; - use tokio::sync::mpsc; - - use crate::app::radarr::ActiveRadarrBlock; - use crate::app::App; - use crate::models::radarr_models::{Collection, CollectionMovie, Credit, RadarrRelease}; - use crate::models::servarr_data::radarr::modals::MovieDetailsModal; - - use crate::network::radarr_network::RadarrEvent; - use crate::network::NetworkEvent; - - #[tokio::test] - async fn test_dispatch_by_blocklist_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Blocklist) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetBlocklist.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_collections_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Collections) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetCollections.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovies.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_collection_details_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) - .await; - - assert!(!app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - assert_eq!(app.tick_count, 0); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_dispatch_by_collection_details_block_with_add_movie() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None)); - - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::AddMovie(None).into() - ); - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - assert_eq!(app.tick_count, 0); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_dispatch_by_downloads_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Downloads) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_root_folders_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::RootFolders) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movies_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Movies) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovies.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_indexers_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::Indexers) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetIndexers.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_all_indexer_settings_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::AllIndexerSettingsPrompt) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetAllIndexerSettings.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_test_indexer_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::TestIndexer) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::TestIndexer(None).into() - ); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_test_all_indexers_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::TestAllIndexers) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::TestAllIndexers.into() - ); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_system_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::System) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTasks.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQueuedEvents.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetLogs(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_system_updates_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::SystemUpdates) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetUpdates.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_add_movie_search_results_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::AddMovieSearchResults) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::SearchNewMovie(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movie_details_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieDetails) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieDetails(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_file_info_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::FileInfo) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieDetails(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_movie_history_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieHistory) - .await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieHistory(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_movie_cast_non_empty() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - let mut movie_details_modal = MovieDetailsModal::default(); - movie_details_modal - .movie_cast - .set_items(vec![Credit::default()]); - app.data.radarr_data.movie_details_modal = Some(movie_details_modal); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_movie_crew_non_empty() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - let mut movie_details_modal = MovieDetailsModal::default(); - movie_details_modal - .movie_crew - .set_items(vec![Credit::default()]); - app.data.radarr_data.movie_details_modal = Some(movie_details_modal); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(app.is_loading); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetMovieCredits(None).into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_cast_crew_blocks_cast_and_crew_non_empty() { - let mut app = App::default(); - - for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { - let mut movie_details_modal = MovieDetailsModal::default(); - movie_details_modal - .movie_cast - .set_items(vec![Credit::default()]); - movie_details_modal - .movie_crew - .set_items(vec![Credit::default()]); - app.data.radarr_data.movie_details_modal = Some(movie_details_modal); - - app.dispatch_by_radarr_block(active_radarr_block).await; - - assert!(!app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block() { - let (mut app, mut sync_network_rx) = construct_app_unit(); + use pretty_assertions::assert_eq; + use tokio::sync::mpsc; + + use crate::app::radarr::ActiveRadarrBlock; + use crate::app::App; + use crate::models::radarr_models::{ + AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, Movie, RadarrRelease, + }; + use crate::models::servarr_data::radarr::modals::MovieDetailsModal; + use crate::models::servarr_models::Indexer; + use crate::network::radarr_network::RadarrEvent; + use crate::network::NetworkEvent; + + #[tokio::test] + async fn test_dispatch_by_blocklist_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Blocklist) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetBlocklist.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_collections_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Collections) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetCollections.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovies.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_collection_details_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) + .await; + + assert!(!app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + assert_eq!(app.tick_count, 0); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_dispatch_by_collection_details_block_with_add_movie() { + let add_movie_body = AddMovieBody { + tmdb_id: 1234, + title: "Test".to_owned(), + root_folder_path: "/nfs2".to_owned(), + minimum_availability: "announced".to_owned(), + monitored: true, + quality_profile_id: 2222, + tags: vec![1, 2], + tag_input_string: None, + add_options: AddMovieOptions { + monitor: "movieOnly".to_owned(), + search_for_movie: true, + }, + }; + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::AddMovie(add_movie_body.clone())); + + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::CollectionDetails) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::AddMovie(add_movie_body).into() + ); + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + assert_eq!(app.tick_count, 0); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_dispatch_by_downloads_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Downloads) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_root_folders_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::RootFolders) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movies_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Movies) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovies.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_indexers_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::Indexers) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetIndexers.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_all_indexer_settings_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::AllIndexerSettingsPrompt) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetAllIndexerSettings.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_test_indexer_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.indexers.set_items(vec![Indexer { + id: 1, + ..Indexer::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::TestIndexer) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::TestIndexer(1).into() + ); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_test_all_indexers_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::TestAllIndexers) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::TestAllIndexers.into() + ); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_system_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::System) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTasks.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQueuedEvents.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetLogs(500).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_system_updates_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::SystemUpdates) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetUpdates.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_add_movie_search_results_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.add_movie_search = Some("test".into()); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::AddMovieSearchResults) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::SearchNewMovie("test".into()).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movie_details_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieDetails) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieDetails(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_file_info_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::FileInfo) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieDetails(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_movie_history_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::MovieHistory) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieHistory(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; + app.dispatch_by_radarr_block(active_radarr_block).await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetReleases(None).into() + RadarrEvent::GetMovieCredits(1).into() ); assert!(!app.data.radarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block_movie_releases_non_empty() { - let mut app = App::default(); - let mut movie_details_modal = MovieDetailsModal::default(); - movie_details_modal - .movie_releases - .set_items(vec![RadarrRelease::default()]); - app.data.radarr_data.movie_details_modal = Some(movie_details_modal); - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; - - assert!(!app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_dispatch_by_manual_search_block_is_loading() { - let mut app = App { - is_loading: true, - ..App::default() - }; - - app - .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) - .await; - - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!(app.tick_count, 0); - } - - #[tokio::test] - async fn test_check_for_radarr_prompt_action_no_prompt_confirm() { - let mut app = App::default(); - app.data.radarr_data.prompt_confirm = false; - - app.check_for_radarr_prompt_action().await; - - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.should_refresh); - } - - #[tokio::test] - async fn test_check_for_radarr_prompt_action() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::GetStatus); - - app.check_for_radarr_prompt_action().await; - - assert!(!app.data.radarr_data.prompt_confirm); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert!(app.should_refresh); - assert_eq!(app.data.radarr_data.prompt_confirm_action, None); - } - - #[tokio::test] - async fn test_radarr_refresh_metadata() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_routing = true; - - app.refresh_radarr_metadata().await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDiskSpace.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert!(app.is_loading); - } - - #[tokio::test] - async fn test_radarr_on_tick_first_render() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_first_render = true; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDiskSpace.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetStatus.into() - ); - assert!(app.is_loading); - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.is_first_render); - } - - #[tokio::test] - async fn test_radarr_on_tick_routing() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_routing = true; - app.should_refresh = true; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_radarr_on_tick_routing_while_long_request_is_running_should_cancel_request() { - let (mut app, _) = construct_app_unit(); - app.is_routing = true; - app.should_refresh = false; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert!(app.cancellation_token.is_cancelled()); - } - - #[tokio::test] - async fn test_radarr_on_tick_should_refresh() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.should_refresh = true; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.should_refresh); - assert!(!app.data.radarr_data.prompt_confirm); - } - - #[tokio::test] - async fn test_radarr_on_tick_should_refresh_does_not_cancel_prompt_requests() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.is_loading = true; - app.is_routing = true; - app.should_refresh = true; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - assert!(app.should_refresh); - assert!(!app.data.radarr_data.prompt_confirm); - assert!(!app.cancellation_token.is_cancelled()); - } - - #[tokio::test] - async fn test_radarr_on_tick_network_tick_frequency() { - let (mut app, mut sync_network_rx) = construct_app_unit(); - app.tick_count = 2; - app.tick_until_poll = 2; - - app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; - - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetQualityProfiles.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetTags.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetRootFolders.into() - ); - assert_eq!( - sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetDownloads.into() - ); - assert!(app.is_loading); - } - - #[tokio::test] - async fn test_populate_movie_collection_table_unfiltered() { - let mut app = App::default(); - app.data.radarr_data.collections.set_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app.populate_movie_collection_table().await; - - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - } - - #[tokio::test] - async fn test_populate_movie_collection_table_filtered() { - let mut app = App::default(); - app - .data - .radarr_data - .collections - .set_filtered_items(vec![Collection { - movies: Some(vec![CollectionMovie::default()]), - ..Collection::default() - }]); - - app.populate_movie_collection_table().await; - - assert!(!app.data.radarr_data.collection_movies.items.is_empty()); - } - - fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { - let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); - let mut app = App { - network_tx: Some(sync_network_tx), - tick_count: 1, - is_first_render: false, - ..App::default() - }; - app.data.radarr_data.prompt_confirm = true; - - (app, sync_network_rx) - } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_movie_cast_non_empty() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + let mut movie_details_modal = MovieDetailsModal::default(); + movie_details_modal + .movie_cast + .set_items(vec![Credit::default()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieCredits(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_movie_crew_non_empty() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + let mut movie_details_modal = MovieDetailsModal::default(); + movie_details_modal + .movie_crew + .set_items(vec![Credit::default()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetMovieCredits(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_cast_crew_blocks_cast_and_crew_non_empty() { + let mut app = App::default(); + + for active_radarr_block in &[ActiveRadarrBlock::Cast, ActiveRadarrBlock::Crew] { + let mut movie_details_modal = MovieDetailsModal::default(); + movie_details_modal + .movie_cast + .set_items(vec![Credit::default()]); + movie_details_modal + .movie_crew + .set_items(vec![Credit::default()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + + app.dispatch_by_radarr_block(active_radarr_block).await; + + assert!(!app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(app.is_loading); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetReleases(1).into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block_movie_releases_non_empty() { + let mut app = App::default(); + let mut movie_details_modal = MovieDetailsModal::default(); + movie_details_modal + .movie_releases + .set_items(vec![RadarrRelease::default()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(!app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_dispatch_by_manual_search_block_is_loading() { + let mut app = App { + is_loading: true, + ..App::default() + }; + + app + .dispatch_by_radarr_block(&ActiveRadarrBlock::ManualSearch) + .await; + + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + + #[tokio::test] + async fn test_check_for_radarr_prompt_action_no_prompt_confirm() { + let mut app = App::default(); + app.data.radarr_data.prompt_confirm = false; + + app.check_for_radarr_prompt_action().await; + + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.should_refresh); + } + + #[tokio::test] + async fn test_check_for_radarr_prompt_action() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::GetStatus); + + app.check_for_radarr_prompt_action().await; + + assert!(!app.data.radarr_data.prompt_confirm); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert!(app.should_refresh); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + } + + #[tokio::test] + async fn test_radarr_refresh_metadata() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_routing = true; + + app.refresh_radarr_metadata().await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDiskSpace.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert!(app.is_loading); + } + + #[tokio::test] + async fn test_radarr_on_tick_first_render() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_first_render = true; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDiskSpace.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetStatus.into() + ); + assert!(app.is_loading); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.is_first_render); + } + + #[tokio::test] + async fn test_radarr_on_tick_routing() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_routing = true; + app.should_refresh = true; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_radarr_on_tick_routing_while_long_request_is_running_should_cancel_request() { + let (mut app, _) = construct_app_unit(); + app.is_routing = true; + app.should_refresh = false; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert!(app.cancellation_token.is_cancelled()); + } + + #[tokio::test] + async fn test_radarr_on_tick_should_refresh() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.should_refresh = true; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.should_refresh); + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[tokio::test] + async fn test_radarr_on_tick_should_refresh_does_not_cancel_prompt_requests() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.is_loading = true; + app.is_routing = true; + app.should_refresh = true; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + assert!(app.should_refresh); + assert!(!app.data.radarr_data.prompt_confirm); + assert!(!app.cancellation_token.is_cancelled()); + } + + #[tokio::test] + async fn test_radarr_on_tick_network_tick_frequency() { + let (mut app, mut sync_network_rx) = construct_app_unit(); + app.tick_count = 2; + app.tick_until_poll = 2; + + app.radarr_on_tick(ActiveRadarrBlock::Downloads).await; + + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetQualityProfiles.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetTags.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetRootFolders.into() + ); + assert_eq!( + sync_network_rx.recv().await.unwrap(), + RadarrEvent::GetDownloads.into() + ); + assert!(app.is_loading); + } + + #[tokio::test] + async fn test_populate_movie_collection_table_unfiltered() { + let mut app = App::default(); + app.data.radarr_data.collections.set_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app.populate_movie_collection_table().await; + + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + } + + #[tokio::test] + async fn test_populate_movie_collection_table_filtered() { + let mut app = App::default(); + app + .data + .radarr_data + .collections + .set_filtered_items(vec![Collection { + movies: Some(vec![CollectionMovie::default()]), + ..Collection::default() + }]); + + app.populate_movie_collection_table().await; + + assert!(!app.data.radarr_data.collection_movies.items.is_empty()); + } + + #[tokio::test] + async fn test_extract_movie_id() { + let mut app = App::default(); + app.data.radarr_data.movies.set_items(vec![Movie { + id: 1, + ..Movie::default() + }]); + + assert_eq!(app.extract_movie_id().await, 1); + } + + #[tokio::test] + async fn test_extract_radarr_indexer_id() { + let mut app = App::default(); + app.data.radarr_data.indexers.set_items(vec![Indexer { + id: 1, + ..Indexer::default() + }]); + + assert_eq!(app.extract_radarr_indexer_id().await, 1); + } + + fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { + let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); + let mut app = App { + network_tx: Some(sync_network_tx), + tick_count: 1, + is_first_render: false, + ..App::default() + }; + app.data.radarr_data.prompt_confirm = true; + + (app, sync_network_rx) } } diff --git a/src/app/sonarr/mod.rs b/src/app/sonarr/mod.rs index 3d2bb82..471055f 100644 --- a/src/app/sonarr/mod.rs +++ b/src/app/sonarr/mod.rs @@ -38,30 +38,42 @@ impl<'a> App<'a> { } ActiveSonarrBlock::SeriesHistory => { self - .dispatch_network_event(SonarrEvent::GetSeriesHistory(None).into()) + .dispatch_network_event( + SonarrEvent::GetSeriesHistory(self.extract_series_id().await).into(), + ) .await; } ActiveSonarrBlock::SeasonDetails => { self - .dispatch_network_event(SonarrEvent::GetEpisodes(None).into()) + .dispatch_network_event(SonarrEvent::GetEpisodes(self.extract_series_id().await).into()) .await; self - .dispatch_network_event(SonarrEvent::GetEpisodeFiles(None).into()) + .dispatch_network_event( + SonarrEvent::GetEpisodeFiles(self.extract_series_id().await).into(), + ) .await; self .dispatch_network_event(SonarrEvent::GetDownloads.into()) .await; } ActiveSonarrBlock::SeasonHistory => { - self - .dispatch_network_event(SonarrEvent::GetSeasonHistory(None).into()) - .await; + if !self.data.sonarr_data.seasons.is_empty() { + self + .dispatch_network_event( + SonarrEvent::GetSeasonHistory(self.extract_series_id_season_number_tuple().await) + .into(), + ) + .await; + } } ActiveSonarrBlock::ManualSeasonSearch => { match self.data.sonarr_data.season_details_modal.as_ref() { Some(season_details_modal) if season_details_modal.season_releases.is_empty() => { self - .dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into()) + .dispatch_network_event( + SonarrEvent::GetSeasonReleases(self.extract_series_id_season_number_tuple().await) + .into(), + ) .await; } _ => (), @@ -69,12 +81,16 @@ impl<'a> App<'a> { } ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => { self - .dispatch_network_event(SonarrEvent::GetEpisodeDetails(None).into()) + .dispatch_network_event( + SonarrEvent::GetEpisodeDetails(self.extract_episode_id().await).into(), + ) .await; } ActiveSonarrBlock::EpisodeHistory => { self - .dispatch_network_event(SonarrEvent::GetEpisodeHistory(None).into()) + .dispatch_network_event( + SonarrEvent::GetEpisodeHistory(self.extract_episode_id().await).into(), + ) .await; } ActiveSonarrBlock::ManualEpisodeSearch => { @@ -82,7 +98,9 @@ impl<'a> App<'a> { if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() { if episode_details_modal.episode_releases.is_empty() { self - .dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into()) + .dispatch_network_event( + SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(), + ) .await; } } @@ -103,7 +121,7 @@ impl<'a> App<'a> { } ActiveSonarrBlock::History => { self - .dispatch_network_event(SonarrEvent::GetHistory(None).into()) + .dispatch_network_event(SonarrEvent::GetHistory(500).into()) .await; } ActiveSonarrBlock::RootFolders => { @@ -126,7 +144,9 @@ impl<'a> App<'a> { } ActiveSonarrBlock::TestIndexer => { self - .dispatch_network_event(SonarrEvent::TestIndexer(None).into()) + .dispatch_network_event( + SonarrEvent::TestIndexer(self.extract_sonarr_indexer_id().await).into(), + ) .await; } ActiveSonarrBlock::TestAllIndexers => { @@ -142,12 +162,14 @@ impl<'a> App<'a> { .dispatch_network_event(SonarrEvent::GetQueuedEvents.into()) .await; self - .dispatch_network_event(SonarrEvent::GetLogs(None).into()) + .dispatch_network_event(SonarrEvent::GetLogs(500).into()) .await; } ActiveSonarrBlock::AddSeriesSearchResults => { self - .dispatch_network_event(SonarrEvent::SearchNewSeries(None).into()) + .dispatch_network_event( + SonarrEvent::SearchNewSeries(self.extract_add_new_series_search_query().await).into(), + ) .await; } ActiveSonarrBlock::SystemUpdates => { @@ -242,4 +264,46 @@ impl<'a> App<'a> { .collect(); self.data.sonarr_data.seasons.set_items(seasons); } + + async fn extract_episode_id(&self) -> i64 { + self + .data + .sonarr_data + .season_details_modal + .as_ref() + .expect("Season details have not been loaded") + .episodes + .current_selection() + .id + } + + async fn extract_series_id(&self) -> i64 { + self.data.sonarr_data.series.current_selection().id + } + + async fn extract_series_id_season_number_tuple(&self) -> (i64, i64) { + let series_id = self.data.sonarr_data.series.current_selection().id; + let season_number = self + .data + .sonarr_data + .seasons + .current_selection() + .season_number; + (series_id, season_number) + } + + async fn extract_add_new_series_search_query(&self) -> String { + self + .data + .sonarr_data + .add_series_search + .as_ref() + .expect("Add series search is empty") + .text + .clone() + } + + async fn extract_sonarr_indexer_id(&self) -> i64 { + self.data.sonarr_data.indexers.current_selection().id + } } diff --git a/src/app/sonarr/sonarr_tests.rs b/src/app/sonarr/sonarr_tests.rs index 8237f7a..e2657e3 100644 --- a/src/app/sonarr/sonarr_tests.rs +++ b/src/app/sonarr/sonarr_tests.rs @@ -4,6 +4,9 @@ mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use tokio::sync::mpsc; + use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; + use crate::models::servarr_models::Indexer; + use crate::models::sonarr_models::Episode; use crate::{ app::App, models::{ @@ -40,6 +43,10 @@ mod tests { #[tokio::test] async fn test_dispatch_by_series_history_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory) @@ -48,7 +55,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetSeriesHistory(None).into() + SonarrEvent::GetSeriesHistory(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -80,6 +87,10 @@ mod tests { #[tokio::test] async fn test_dispatch_by_season_details_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails) @@ -88,11 +99,11 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodes(None).into() + SonarrEvent::GetEpisodes(1).into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodeFiles(None).into() + SonarrEvent::GetEpisodeFiles(1).into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), @@ -105,6 +116,14 @@ mod tests { #[tokio::test] async fn test_dispatch_by_season_history_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); + app.data.sonarr_data.seasons.set_items(vec![Season { + season_number: 1, + ..Season::default() + }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory) @@ -113,16 +132,41 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetSeasonHistory(None).into() + SonarrEvent::GetSeasonHistory((1, 1)).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } + #[tokio::test] + async fn test_dispatch_by_season_history_block_no_op_when_seasons_table_is_empty() { + let (mut app, _) = construct_app_unit(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); + + app + .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory) + .await; + + assert!(!app.is_loading); + assert!(!app.data.sonarr_data.prompt_confirm); + assert_eq!(app.tick_count, 0); + } + #[tokio::test] async fn test_dispatch_by_manual_season_search_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default()); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); + app.data.sonarr_data.seasons.set_items(vec![Season { + season_number: 1, + ..Season::default() + }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch) @@ -131,7 +175,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetSeasonReleases(None).into() + SonarrEvent::GetSeasonReleases((1, 1)).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -174,6 +218,7 @@ mod tests { #[tokio::test] async fn test_dispatch_by_episode_details_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data = create_test_sonarr_data(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails) @@ -182,7 +227,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodeDetails(None).into() + SonarrEvent::GetEpisodeDetails(0).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -191,6 +236,7 @@ mod tests { #[tokio::test] async fn test_dispatch_by_episode_file_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data = create_test_sonarr_data(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile) @@ -199,7 +245,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodeDetails(None).into() + SonarrEvent::GetEpisodeDetails(0).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -208,6 +254,12 @@ mod tests { #[tokio::test] async fn test_dispatch_by_episode_history_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + let mut season_details_modal = SeasonDetailsModal::default(); + season_details_modal.episodes.set_items(vec![Episode { + id: 1, + ..Episode::default() + }]); + app.data.sonarr_data.season_details_modal = Some(season_details_modal); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory) @@ -216,7 +268,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodeHistory(None).into() + SonarrEvent::GetEpisodeHistory(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -225,10 +277,14 @@ mod tests { #[tokio::test] async fn test_dispatch_by_manual_episode_search_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); - let season_details_modal = SeasonDetailsModal { + let mut season_details_modal = SeasonDetailsModal { episode_details_modal: Some(EpisodeDetailsModal::default()), ..SeasonDetailsModal::default() }; + season_details_modal.episodes.set_items(vec![Episode { + id: 1, + ..Episode::default() + }]); app.data.sonarr_data.season_details_modal = Some(season_details_modal); app @@ -238,7 +294,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetEpisodeReleases(None).into() + SonarrEvent::GetEpisodeReleases(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -293,7 +349,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetHistory(None).into() + SonarrEvent::GetHistory(500).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -403,6 +459,10 @@ mod tests { #[tokio::test] async fn test_dispatch_by_test_indexer_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.indexers.set_items(vec![Indexer { + id: 1, + ..Indexer::default() + }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer) @@ -411,7 +471,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::TestIndexer(None).into() + SonarrEvent::TestIndexer(1).into() ); assert_eq!(app.tick_count, 0); } @@ -451,7 +511,7 @@ mod tests { ); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::GetLogs(None).into() + SonarrEvent::GetLogs(500).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -477,6 +537,7 @@ mod tests { #[tokio::test] async fn test_dispatch_by_add_series_search_results_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); + app.data.sonarr_data.add_series_search = Some("test search".into()); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults) @@ -485,7 +546,7 @@ mod tests { assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), - SonarrEvent::SearchNewSeries(None).into() + SonarrEvent::SearchNewSeries("test search".into()).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); @@ -727,6 +788,83 @@ mod tests { ); } + #[tokio::test] + async fn test_extract_episode_id() { + let mut app = App::default(); + let mut season_details_modal = SeasonDetailsModal::default(); + season_details_modal.episodes.set_items(vec![Episode { + id: 1, + ..Episode::default() + }]); + app.data.sonarr_data.season_details_modal = Some(season_details_modal); + + assert_eq!(app.extract_episode_id().await, 1); + } + + #[tokio::test] + #[should_panic(expected = "Season details have not been loaded")] + async fn test_extract_episode_id_requires_season_details_modal_to_be_some() { + let app = App::default(); + + assert_eq!(app.extract_episode_id().await, 0); + } + + #[tokio::test] + async fn test_extract_series_id() { + let mut app = App::default(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); + + assert_eq!(app.extract_series_id().await, 1); + } + + #[tokio::test] + async fn test_extract_series_id_season_number_tuple() { + let mut app = App::default(); + app.data.sonarr_data.series.set_items(vec![Series { + id: 1, + ..Series::default() + }]); + app.data.sonarr_data.seasons.set_items(vec![Season { + season_number: 1, + ..Season::default() + }]); + + assert_eq!(app.extract_series_id_season_number_tuple().await, (1, 1)); + } + + #[tokio::test] + async fn test_extract_add_new_series_search_query() { + let mut app = App::default(); + app.data.sonarr_data.add_series_search = Some("test search".into()); + + assert_str_eq!( + app.extract_add_new_series_search_query().await, + "test search" + ); + } + + #[tokio::test] + #[should_panic(expected = "Add series search is empty")] + async fn test_extract_add_new_series_search_query_panics_when_the_query_is_not_set() { + let app = App::default(); + + app.extract_add_new_series_search_query().await; + } + + #[tokio::test] + async fn test_extract_sonarr_indexer_id() { + let mut app = App::default(); + app.data.sonarr_data.indexers.set_items(vec![Indexer { + id: 1, + ..Indexer::default() + }]); + + assert_eq!(app.extract_sonarr_indexer_id().await, 1); + } + fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); let mut app = App { diff --git a/src/cli/radarr/add_command_handler.rs b/src/cli/radarr/add_command_handler.rs index 0c35a7e..73cd851 100644 --- a/src/cli/radarr/add_command_handler.rs +++ b/src/cli/radarr/add_command_handler.rs @@ -4,6 +4,8 @@ use anyhow::Result; use clap::{arg, command, ArgAction, Subcommand}; use tokio::sync::Mutex; +use super::RadarrCommand; +use crate::models::servarr_models::AddRootFolderBody; use crate::{ app::App, cli::{CliCommandHandler, Command}, @@ -11,8 +13,6 @@ use crate::{ network::{radarr_network::RadarrEvent, NetworkTrait}, }; -use super::RadarrCommand; - #[cfg(test)] #[path = "add_command_handler_tests.rs"] mod add_command_handler_tests; @@ -125,6 +125,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan minimum_availability: minimum_availability.to_string(), monitored: !disable_monitoring, tags, + tag_input_string: None, add_options: AddMovieOptions { monitor: monitor.to_string(), search_for_movie: !no_search_for_movie, @@ -132,14 +133,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan }; let resp = self .network - .handle_network_event(RadarrEvent::AddMovie(Some(body)).into()) + .handle_network_event(RadarrEvent::AddMovie(body).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrAddCommand::RootFolder { root_folder_path } => { + let add_root_folder_body = AddRootFolderBody { + path: root_folder_path, + }; let resp = self .network - .handle_network_event(RadarrEvent::AddRootFolder(Some(root_folder_path)).into()) + .handle_network_event(RadarrEvent::AddRootFolder(add_root_folder_body).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/add_command_handler_tests.rs b/src/cli/radarr/add_command_handler_tests.rs index 754abd2..edec9b6 100644 --- a/src/cli/radarr/add_command_handler_tests.rs +++ b/src/cli/radarr/add_command_handler_tests.rs @@ -368,6 +368,7 @@ mod tests { use super::*; use mockall::predicate::eq; + use crate::models::servarr_models::AddRootFolderBody; use serde_json::json; use tokio::sync::Mutex; @@ -381,6 +382,7 @@ mod tests { minimum_availability: "released".to_owned(), monitored: false, tags: vec![1, 2], + tag_input_string: None, add_options: AddMovieOptions { monitor: "movieAndCollection".to_owned(), search_for_movie: false, @@ -390,7 +392,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::AddMovie(Some(expected_add_movie_body)).into(), + RadarrEvent::AddMovie(expected_add_movie_body).into(), )) .times(1) .returning(|_| { @@ -420,11 +422,14 @@ mod tests { #[tokio::test] async fn test_handle_add_root_folder_command() { let expected_root_folder_path = "/nfs/test".to_owned(); + let expected_add_root_folder_body = AddRootFolderBody { + path: expected_root_folder_path.clone(), + }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), + RadarrEvent::AddRootFolder(expected_add_root_folder_body).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/delete_command_handler.rs b/src/cli/radarr/delete_command_handler.rs index 2f10c57..9fdb83c 100644 --- a/src/cli/radarr/delete_command_handler.rs +++ b/src/cli/radarr/delete_command_handler.rs @@ -89,21 +89,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => { let resp = self .network - .handle_network_event(RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) + .handle_network_event(RadarrEvent::DeleteBlocklistItem(blocklist_item_id).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrDeleteCommand::Download { download_id } => { let resp = self .network - .handle_network_event(RadarrEvent::DeleteDownload(Some(download_id)).into()) + .handle_network_event(RadarrEvent::DeleteDownload(download_id).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrDeleteCommand::Indexer { indexer_id } => { let resp = self .network - .handle_network_event(RadarrEvent::DeleteIndexer(Some(indexer_id)).into()) + .handle_network_event(RadarrEvent::DeleteIndexer(indexer_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -119,14 +119,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm }; let resp = self .network - .handle_network_event(RadarrEvent::DeleteMovie(Some(delete_movie_params)).into()) + .handle_network_event(RadarrEvent::DeleteMovie(delete_movie_params).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrDeleteCommand::RootFolder { root_folder_id } => { let resp = self .network - .handle_network_event(RadarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) + .handle_network_event(RadarrEvent::DeleteRootFolder(root_folder_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/delete_command_handler_tests.rs b/src/cli/radarr/delete_command_handler_tests.rs index d8971f7..ad1580e 100644 --- a/src/cli/radarr/delete_command_handler_tests.rs +++ b/src/cli/radarr/delete_command_handler_tests.rs @@ -268,7 +268,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), + RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(), )) .times(1) .returning(|_| { @@ -299,7 +299,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteDownload(Some(expected_download_id)).into(), + RadarrEvent::DeleteDownload(expected_download_id).into(), )) .times(1) .returning(|_| { @@ -325,7 +325,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), + RadarrEvent::DeleteIndexer(expected_indexer_id).into(), )) .times(1) .returning(|_| { @@ -355,7 +355,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteMovie(Some(expected_delete_movie_params)).into(), + RadarrEvent::DeleteMovie(expected_delete_movie_params).into(), )) .times(1) .returning(|_| { @@ -385,7 +385,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), + RadarrEvent::DeleteRootFolder(expected_root_folder_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/edit_command_handler.rs b/src/cli/radarr/edit_command_handler.rs index 0d206d8..fb48fbd 100644 --- a/src/cli/radarr/edit_command_handler.rs +++ b/src/cli/radarr/edit_command_handler.rs @@ -390,7 +390,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH }; self .network - .handle_network_event(RadarrEvent::EditAllIndexerSettings(Some(params)).into()) + .handle_network_event(RadarrEvent::EditAllIndexerSettings(params).into()) .await?; "All indexer settings updated".to_owned() } else { @@ -420,7 +420,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH }; self .network - .handle_network_event(RadarrEvent::EditCollection(Some(edit_collection_params)).into()) + .handle_network_event(RadarrEvent::EditCollection(edit_collection_params).into()) .await?; "Collection updated".to_owned() } @@ -455,13 +455,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH api_key, seed_ratio, tags: tag, + tag_input_string: None, priority, clear_tags, }; self .network - .handle_network_event(RadarrEvent::EditIndexer(Some(edit_indexer_params)).into()) + .handle_network_event(RadarrEvent::EditIndexer(edit_indexer_params).into()) .await?; "Indexer updated".to_owned() } @@ -483,12 +484,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH quality_profile_id, root_folder_path, tags: tag, + tag_input_string: None, clear_tags, }; self .network - .handle_network_event(RadarrEvent::EditMovie(Some(edit_movie_params)).into()) + .handle_network_event(RadarrEvent::EditMovie(edit_movie_params).into()) .await?; "Movie Updated".to_owned() } diff --git a/src/cli/radarr/edit_command_handler_tests.rs b/src/cli/radarr/edit_command_handler_tests.rs index 5fc2a2a..850bcb5 100644 --- a/src/cli/radarr/edit_command_handler_tests.rs +++ b/src/cli/radarr/edit_command_handler_tests.rs @@ -857,7 +857,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -928,7 +928,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -1000,7 +1000,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -1047,7 +1047,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), + RadarrEvent::EditCollection(expected_edit_collection_params).into(), )) .times(1) .returning(|_| { @@ -1089,7 +1089,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), + RadarrEvent::EditCollection(expected_edit_collection_params).into(), )) .times(1) .returning(|_| { @@ -1131,7 +1131,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), + RadarrEvent::EditCollection(expected_edit_collection_params).into(), )) .times(1) .returning(|_| { @@ -1171,6 +1171,7 @@ mod tests { api_key: Some("testKey".to_owned()), seed_ratio: Some("1.2".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, priority: Some(25), clear_tags: false, }; @@ -1178,7 +1179,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), + RadarrEvent::EditIndexer(expected_edit_indexer_params).into(), )) .times(1) .returning(|_| { @@ -1224,6 +1225,7 @@ mod tests { api_key: Some("testKey".to_owned()), seed_ratio: Some("1.2".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, priority: Some(25), clear_tags: false, }; @@ -1231,7 +1233,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), + RadarrEvent::EditIndexer(expected_edit_indexer_params).into(), )) .times(1) .returning(|_| { @@ -1277,6 +1279,7 @@ mod tests { api_key: Some("testKey".to_owned()), seed_ratio: Some("1.2".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, priority: Some(25), clear_tags: false, }; @@ -1284,7 +1287,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), + RadarrEvent::EditIndexer(expected_edit_indexer_params).into(), )) .times(1) .returning(|_| { @@ -1327,13 +1330,14 @@ mod tests { quality_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), + RadarrEvent::EditMovie(expected_edit_movie_params).into(), )) .times(1) .returning(|_| { @@ -1369,13 +1373,14 @@ mod tests { quality_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), + RadarrEvent::EditMovie(expected_edit_movie_params).into(), )) .times(1) .returning(|_| { @@ -1411,13 +1416,14 @@ mod tests { quality_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), + RadarrEvent::EditMovie(expected_edit_movie_params).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/get_command_handler.rs b/src/cli/radarr/get_command_handler.rs index 4fe2830..08016ea 100644 --- a/src/cli/radarr/get_command_handler.rs +++ b/src/cli/radarr/get_command_handler.rs @@ -90,14 +90,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan RadarrGetCommand::MovieDetails { movie_id } => { let resp = self .network - .handle_network_event(RadarrEvent::GetMovieDetails(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::GetMovieDetails(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrGetCommand::MovieHistory { movie_id } => { let resp = self .network - .handle_network_event(RadarrEvent::GetMovieHistory(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::GetMovieHistory(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/get_command_handler_tests.rs b/src/cli/radarr/get_command_handler_tests.rs index e70db37..7742c07 100644 --- a/src/cli/radarr/get_command_handler_tests.rs +++ b/src/cli/radarr/get_command_handler_tests.rs @@ -182,7 +182,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetMovieDetails(Some(expected_movie_id)).into(), + RadarrEvent::GetMovieDetails(expected_movie_id).into(), )) .times(1) .returning(|_| { @@ -208,7 +208,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetMovieHistory(Some(expected_movie_id)).into(), + RadarrEvent::GetMovieHistory(expected_movie_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/list_command_handler.rs b/src/cli/radarr/list_command_handler.rs index b769033..3e89945 100644 --- a/src/cli/radarr/list_command_handler.rs +++ b/src/cli/radarr/list_command_handler.rs @@ -131,7 +131,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH } => { let logs = self .network - .handle_network_event(RadarrEvent::GetLogs(Some(events)).into()) + .handle_network_event(RadarrEvent::GetLogs(events).into()) .await?; if output_in_log_format { @@ -152,7 +152,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH RadarrListCommand::MovieCredits { movie_id } => { let resp = self .network - .handle_network_event(RadarrEvent::GetMovieCredits(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::GetMovieCredits(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/list_command_handler_tests.rs b/src/cli/radarr/list_command_handler_tests.rs index ca9aa0f..36f030d 100644 --- a/src/cli/radarr/list_command_handler_tests.rs +++ b/src/cli/radarr/list_command_handler_tests.rs @@ -163,7 +163,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), + RadarrEvent::GetMovieCredits(expected_movie_id).into(), )) .times(1) .returning(|_| { @@ -189,7 +189,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetLogs(Some(expected_events)).into(), + RadarrEvent::GetLogs(expected_events).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/mod.rs b/src/cli/radarr/mod.rs index 6789380..2641691 100644 --- a/src/cli/radarr/mod.rs +++ b/src/cli/radarr/mod.rs @@ -209,7 +209,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, ' }; let resp = self .network - .handle_network_event(RadarrEvent::DownloadRelease(Some(params)).into()) + .handle_network_event(RadarrEvent::DownloadRelease(params).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -217,28 +217,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, ' println!("Searching for releases. This may take a minute..."); let resp = self .network - .handle_network_event(RadarrEvent::GetReleases(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::GetReleases(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrCommand::SearchNewMovie { query } => { let resp = self .network - .handle_network_event(RadarrEvent::SearchNewMovie(Some(query)).into()) + .handle_network_event(RadarrEvent::SearchNewMovie(query).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrCommand::StartTask { task_name } => { let resp = self .network - .handle_network_event(RadarrEvent::StartTask(Some(task_name)).into()) + .handle_network_event(RadarrEvent::StartTask(task_name).into()) .await?; serde_json::to_string_pretty(&resp)? } RadarrCommand::TestIndexer { indexer_id } => { let resp = self .network - .handle_network_event(RadarrEvent::TestIndexer(Some(indexer_id)).into()) + .handle_network_event(RadarrEvent::TestIndexer(indexer_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -253,7 +253,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, ' RadarrCommand::TriggerAutomaticSearch { movie_id } => { let resp = self .network - .handle_network_event(RadarrEvent::TriggerAutomaticSearch(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::TriggerAutomaticSearch(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/radarr_command_tests.rs b/src/cli/radarr/radarr_command_tests.rs index 5fd14a1..11a41b6 100644 --- a/src/cli/radarr/radarr_command_tests.rs +++ b/src/cli/radarr/radarr_command_tests.rs @@ -293,9 +293,9 @@ mod tests { ))) }); let app_arc = Arc::new(Mutex::new(App::default())); - let claer_blocklist_command = RadarrCommand::ClearBlocklist; + let clear_blocklist_command = RadarrCommand::ClearBlocklist; - let result = RadarrCliHandler::with(&app_arc, claer_blocklist_command, &mut mock_network) + let result = RadarrCliHandler::with(&app_arc, clear_blocklist_command, &mut mock_network) .handle() .await; @@ -313,7 +313,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DownloadRelease(Some(expected_release_download_body)).into(), + RadarrEvent::DownloadRelease(expected_release_download_body).into(), )) .times(1) .returning(|_| { @@ -342,7 +342,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetReleases(Some(expected_movie_id)).into(), + RadarrEvent::GetReleases(expected_movie_id).into(), )) .times(1) .returning(|_| { @@ -367,7 +367,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::SearchNewMovie(Some(expected_search_query)).into(), + RadarrEvent::SearchNewMovie(expected_search_query).into(), )) .times(1) .returning(|_| { @@ -394,7 +394,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::StartTask(Some(expected_task_name)).into(), + RadarrEvent::StartTask(expected_task_name).into(), )) .times(1) .returning(|_| { @@ -421,7 +421,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::TestIndexer(Some(expected_indexer_id)).into(), + RadarrEvent::TestIndexer(expected_indexer_id).into(), )) .times(1) .returning(|_| { @@ -468,7 +468,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::TriggerAutomaticSearch(Some(expected_movie_id)).into(), + RadarrEvent::TriggerAutomaticSearch(expected_movie_id).into(), )) .times(1) .returning(|_| { @@ -524,7 +524,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), + RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(), )) .times(1) .returning(|_| { @@ -584,7 +584,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -654,7 +654,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), + RadarrEvent::GetMovieCredits(expected_movie_id).into(), )) .times(1) .returning(|_| { @@ -680,7 +680,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), + RadarrEvent::UpdateAndScan(expected_movie_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/radarr/refresh_command_handler.rs b/src/cli/radarr/refresh_command_handler.rs index f329249..d871be4 100644 --- a/src/cli/radarr/refresh_command_handler.rs +++ b/src/cli/radarr/refresh_command_handler.rs @@ -88,7 +88,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrRefreshCommand> RadarrRefreshCommand::Movie { movie_id } => { let resp = self .network - .handle_network_event(RadarrEvent::UpdateAndScan(Some(movie_id)).into()) + .handle_network_event(RadarrEvent::UpdateAndScan(movie_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/radarr/refresh_command_handler_tests.rs b/src/cli/radarr/refresh_command_handler_tests.rs index 3c43830..25fe01c 100644 --- a/src/cli/radarr/refresh_command_handler_tests.rs +++ b/src/cli/radarr/refresh_command_handler_tests.rs @@ -112,7 +112,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), + RadarrEvent::UpdateAndScan(expected_movie_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/add_command_handler.rs b/src/cli/sonarr/add_command_handler.rs index c842d0d..9bf0dc6 100644 --- a/src/cli/sonarr/add_command_handler.rs +++ b/src/cli/sonarr/add_command_handler.rs @@ -4,6 +4,8 @@ use anyhow::Result; use clap::{ArgAction, Subcommand}; use tokio::sync::Mutex; +use super::SonarrCommand; +use crate::models::servarr_models::AddRootFolderBody; use crate::{ app::App, cli::{CliCommandHandler, Command}, @@ -11,8 +13,6 @@ use crate::{ network::{sonarr_network::SonarrEvent, NetworkTrait}, }; -use super::SonarrCommand; - #[cfg(test)] #[path = "add_command_handler_tests.rs"] mod add_command_handler_tests; @@ -140,6 +140,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan series_type: series_type.to_string(), season_folder: !disable_season_folders, tags, + tag_input_string: None, add_options: AddSeriesOptions { monitor: monitor.to_string(), search_for_cutoff_unmet_episodes: !no_search_for_series, @@ -148,14 +149,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan }; let resp = self .network - .handle_network_event(SonarrEvent::AddSeries(Some(body)).into()) + .handle_network_event(SonarrEvent::AddSeries(body).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrAddCommand::RootFolder { root_folder_path } => { + let add_root_folder_body = AddRootFolderBody { + path: root_folder_path, + }; let resp = self .network - .handle_network_event(SonarrEvent::AddRootFolder(Some(root_folder_path)).into()) + .handle_network_event(SonarrEvent::AddRootFolder(add_root_folder_body).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/add_command_handler_tests.rs b/src/cli/sonarr/add_command_handler_tests.rs index 8018b9e..862d6f7 100644 --- a/src/cli/sonarr/add_command_handler_tests.rs +++ b/src/cli/sonarr/add_command_handler_tests.rs @@ -469,17 +469,21 @@ mod tests { use super::*; use mockall::predicate::eq; + use crate::models::servarr_models::AddRootFolderBody; use serde_json::json; use tokio::sync::Mutex; #[tokio::test] async fn test_handle_add_root_folder_command() { let expected_root_folder_path = "/nfs/test".to_owned(); + let expected_add_root_folder_body = AddRootFolderBody { + path: expected_root_folder_path.clone(), + }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), + SonarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(), )) .times(1) .returning(|_| { @@ -511,6 +515,7 @@ mod tests { series_type: "anime".to_owned(), monitored: false, tags: vec![1, 2], + tag_input_string: None, season_folder: false, add_options: AddSeriesOptions { monitor: "future".to_owned(), @@ -522,7 +527,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::AddSeries(Some(expected_add_series_body)).into(), + SonarrEvent::AddSeries(expected_add_series_body).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/delete_command_handler.rs b/src/cli/sonarr/delete_command_handler.rs index 1bf03b2..a497305 100644 --- a/src/cli/sonarr/delete_command_handler.rs +++ b/src/cli/sonarr/delete_command_handler.rs @@ -94,35 +94,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => { let resp = self .network - .handle_network_event(SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) + .handle_network_event(SonarrEvent::DeleteBlocklistItem(blocklist_item_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrDeleteCommand::Download { download_id } => { let resp = self .network - .handle_network_event(SonarrEvent::DeleteDownload(Some(download_id)).into()) + .handle_network_event(SonarrEvent::DeleteDownload(download_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrDeleteCommand::EpisodeFile { episode_file_id } => { let resp = self .network - .handle_network_event(SonarrEvent::DeleteEpisodeFile(Some(episode_file_id)).into()) + .handle_network_event(SonarrEvent::DeleteEpisodeFile(episode_file_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrDeleteCommand::Indexer { indexer_id } => { let resp = self .network - .handle_network_event(SonarrEvent::DeleteIndexer(Some(indexer_id)).into()) + .handle_network_event(SonarrEvent::DeleteIndexer(indexer_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrDeleteCommand::RootFolder { root_folder_id } => { let resp = self .network - .handle_network_event(SonarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) + .handle_network_event(SonarrEvent::DeleteRootFolder(root_folder_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -138,7 +138,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm }; let resp = self .network - .handle_network_event(SonarrEvent::DeleteSeries(Some(delete_series_params)).into()) + .handle_network_event(SonarrEvent::DeleteSeries(delete_series_params).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/delete_command_handler_tests.rs b/src/cli/sonarr/delete_command_handler_tests.rs index 9813e3a..a0d32e2 100644 --- a/src/cli/sonarr/delete_command_handler_tests.rs +++ b/src/cli/sonarr/delete_command_handler_tests.rs @@ -301,7 +301,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), + SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(), )) .times(1) .returning(|_| { @@ -332,7 +332,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteDownload(Some(expected_download_id)).into(), + SonarrEvent::DeleteDownload(expected_download_id).into(), )) .times(1) .returning(|_| { @@ -351,6 +351,32 @@ mod tests { assert!(result.is_ok()); } + #[tokio::test] + async fn test_handle_delete_episode_file_command() { + let expected_episode_file_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + SonarrEvent::DeleteEpisodeFile(expected_episode_file_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Sonarr(SonarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::default())); + let delete_episode_file_command = SonarrDeleteCommand::EpisodeFile { episode_file_id: 1 }; + + let result = + SonarrDeleteCommandHandler::with(&app_arc, delete_episode_file_command, &mut mock_network) + .handle() + .await; + + assert!(result.is_ok()); + } + #[tokio::test] async fn test_handle_delete_indexer_command() { let expected_indexer_id = 1; @@ -358,7 +384,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), + SonarrEvent::DeleteIndexer(expected_indexer_id).into(), )) .times(1) .returning(|_| { @@ -384,7 +410,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), + SonarrEvent::DeleteRootFolder(expected_root_folder_id).into(), )) .times(1) .returning(|_| { @@ -414,7 +440,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteSeries(Some(expected_delete_series_params)).into(), + SonarrEvent::DeleteSeries(expected_delete_series_params).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/edit_command_handler.rs b/src/cli/sonarr/edit_command_handler.rs index bd879c3..7d06313 100644 --- a/src/cli/sonarr/edit_command_handler.rs +++ b/src/cli/sonarr/edit_command_handler.rs @@ -274,7 +274,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH }; self .network - .handle_network_event(SonarrEvent::EditAllIndexerSettings(Some(params)).into()) + .handle_network_event(SonarrEvent::EditAllIndexerSettings(params).into()) .await?; "All indexer settings updated".to_owned() } else { @@ -312,13 +312,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH api_key, seed_ratio, tags: tag, + tag_input_string: None, priority, clear_tags, }; self .network - .handle_network_event(SonarrEvent::EditIndexer(Some(edit_indexer_params)).into()) + .handle_network_event(SonarrEvent::EditIndexer(edit_indexer_params).into()) .await?; "Indexer updated".to_owned() } @@ -347,12 +348,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH language_profile_id, root_folder_path, tags: tag, + tag_input_string: None, clear_tags, }; self .network - .handle_network_event(SonarrEvent::EditSeries(Some(edit_series_params)).into()) + .handle_network_event(SonarrEvent::EditSeries(edit_series_params).into()) .await?; "Series Updated".to_owned() } diff --git a/src/cli/sonarr/edit_command_handler_tests.rs b/src/cli/sonarr/edit_command_handler_tests.rs index eaef63f..c388ed8 100644 --- a/src/cli/sonarr/edit_command_handler_tests.rs +++ b/src/cli/sonarr/edit_command_handler_tests.rs @@ -650,7 +650,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -689,6 +689,7 @@ mod tests { api_key: Some("testKey".to_owned()), seed_ratio: Some("1.2".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, priority: Some(25), clear_tags: false, }; @@ -696,7 +697,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), + SonarrEvent::EditIndexer(expected_edit_indexer_params).into(), )) .times(1) .returning(|_| { @@ -741,13 +742,14 @@ mod tests { language_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), + SonarrEvent::EditSeries(expected_edit_series_params).into(), )) .times(1) .returning(|_| { @@ -788,13 +790,14 @@ mod tests { language_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), + SonarrEvent::EditSeries(expected_edit_series_params).into(), )) .times(1) .returning(|_| { @@ -835,13 +838,14 @@ mod tests { language_profile_id: Some(1), root_folder_path: Some("/nfs/test".to_owned()), tags: Some(vec![1, 2]), + tag_input_string: None, clear_tags: false, }; let mut mock_network = MockNetworkTrait::new(); mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), + SonarrEvent::EditSeries(expected_edit_series_params).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/get_command_handler.rs b/src/cli/sonarr/get_command_handler.rs index 37e3101..c6b9ffe 100644 --- a/src/cli/sonarr/get_command_handler.rs +++ b/src/cli/sonarr/get_command_handler.rs @@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan SonarrGetCommand::EpisodeDetails { episode_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetEpisodeDetails(Some(episode_id)).into()) + .handle_network_event(SonarrEvent::GetEpisodeDetails(episode_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -104,7 +104,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan SonarrGetCommand::SeriesDetails { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetSeriesDetails(Some(series_id)).into()) + .handle_network_event(SonarrEvent::GetSeriesDetails(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/get_command_handler_tests.rs b/src/cli/sonarr/get_command_handler_tests.rs index 12a6225..ad53be8 100644 --- a/src/cli/sonarr/get_command_handler_tests.rs +++ b/src/cli/sonarr/get_command_handler_tests.rs @@ -160,7 +160,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodeDetails(Some(expected_episode_id)).into(), + SonarrEvent::GetEpisodeDetails(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -232,7 +232,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetSeriesDetails(Some(expected_series_id)).into(), + SonarrEvent::GetSeriesDetails(expected_series_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/list_command_handler.rs b/src/cli/sonarr/list_command_handler.rs index 1603308..fe7c268 100644 --- a/src/cli/sonarr/list_command_handler.rs +++ b/src/cli/sonarr/list_command_handler.rs @@ -163,28 +163,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH SonarrListCommand::Episodes { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetEpisodes(Some(series_id)).into()) + .handle_network_event(SonarrEvent::GetEpisodes(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrListCommand::EpisodeFiles { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetEpisodeFiles(Some(series_id)).into()) + .handle_network_event(SonarrEvent::GetEpisodeFiles(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrListCommand::EpisodeHistory { episode_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetEpisodeHistory(Some(episode_id)).into()) + .handle_network_event(SonarrEvent::GetEpisodeHistory(episode_id).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrListCommand::History { events: items } => { let resp = self .network - .handle_network_event(SonarrEvent::GetHistory(Some(items)).into()) + .handle_network_event(SonarrEvent::GetHistory(items).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -208,7 +208,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH } => { let logs = self .network - .handle_network_event(SonarrEvent::GetLogs(Some(events)).into()) + .handle_network_event(SonarrEvent::GetLogs(events).into()) .await?; if output_in_log_format { @@ -246,9 +246,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH } => { let resp = self .network - .handle_network_event( - SonarrEvent::GetSeasonHistory(Some((series_id, season_number))).into(), - ) + .handle_network_event(SonarrEvent::GetSeasonHistory((series_id, season_number)).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -262,7 +260,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH SonarrListCommand::SeriesHistory { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::GetSeriesHistory(Some(series_id)).into()) + .handle_network_event(SonarrEvent::GetSeriesHistory(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/list_command_handler_tests.rs b/src/cli/sonarr/list_command_handler_tests.rs index c333193..866489a 100644 --- a/src/cli/sonarr/list_command_handler_tests.rs +++ b/src/cli/sonarr/list_command_handler_tests.rs @@ -329,7 +329,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodes(Some(expected_series_id)).into(), + SonarrEvent::GetEpisodes(expected_series_id).into(), )) .times(1) .returning(|_| { @@ -355,7 +355,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodeFiles(Some(expected_series_id)).into(), + SonarrEvent::GetEpisodeFiles(expected_series_id).into(), )) .times(1) .returning(|_| { @@ -381,7 +381,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetHistory(Some(expected_events)).into(), + SonarrEvent::GetHistory(expected_events).into(), )) .times(1) .returning(|_| { @@ -407,7 +407,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetLogs(Some(expected_events)).into(), + SonarrEvent::GetLogs(expected_events).into(), )) .times(1) .returning(|_| { @@ -435,7 +435,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetSeriesHistory(Some(expected_series_id)).into(), + SonarrEvent::GetSeriesHistory(expected_series_id).into(), )) .times(1) .returning(|_| { @@ -461,7 +461,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodeHistory(Some(expected_episode_id)).into(), + SonarrEvent::GetEpisodeHistory(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -488,7 +488,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetSeasonHistory(Some((expected_series_id, expected_season_number))).into(), + SonarrEvent::GetSeasonHistory((expected_series_id, expected_season_number)).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/manual_search_command_handler.rs b/src/cli/sonarr/manual_search_command_handler.rs index e8e1ca3..b2972bf 100644 --- a/src/cli/sonarr/manual_search_command_handler.rs +++ b/src/cli/sonarr/manual_search_command_handler.rs @@ -75,7 +75,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand> println!("Searching for episode releases. This may take a minute..."); let resp = self .network - .handle_network_event(SonarrEvent::GetEpisodeReleases(Some(episode_id)).into()) + .handle_network_event(SonarrEvent::GetEpisodeReleases(episode_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -86,9 +86,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand> println!("Searching for season releases. This may take a minute..."); let resp = self .network - .handle_network_event( - SonarrEvent::GetSeasonReleases(Some((series_id, season_number))).into(), - ) + .handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/manual_search_command_handler_tests.rs b/src/cli/sonarr/manual_search_command_handler_tests.rs index 53b26ad..7141e01 100644 --- a/src/cli/sonarr/manual_search_command_handler_tests.rs +++ b/src/cli/sonarr/manual_search_command_handler_tests.rs @@ -130,7 +130,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), + SonarrEvent::GetEpisodeReleases(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -160,7 +160,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetSeasonReleases(Some((expected_series_id, expected_season_number))).into(), + SonarrEvent::GetSeasonReleases((expected_series_id, expected_season_number)).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/mod.rs b/src/cli/sonarr/mod.rs index 3a19110..b19e837 100644 --- a/src/cli/sonarr/mod.rs +++ b/src/cli/sonarr/mod.rs @@ -245,21 +245,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' SonarrCommand::SearchNewSeries { query } => { let resp = self .network - .handle_network_event(SonarrEvent::SearchNewSeries(Some(query)).into()) + .handle_network_event(SonarrEvent::SearchNewSeries(query).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrCommand::StartTask { task_name } => { let resp = self .network - .handle_network_event(SonarrEvent::StartTask(Some(task_name)).into()) + .handle_network_event(SonarrEvent::StartTask(task_name).into()) .await?; serde_json::to_string_pretty(&resp)? } SonarrCommand::TestIndexer { indexer_id } => { let resp = self .network - .handle_network_event(SonarrEvent::TestIndexer(Some(indexer_id)).into()) + .handle_network_event(SonarrEvent::TestIndexer(indexer_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -274,7 +274,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' SonarrCommand::ToggleEpisodeMonitoring { episode_id } => { let resp = self .network - .handle_network_event(SonarrEvent::ToggleEpisodeMonitoring(Some(episode_id)).into()) + .handle_network_event(SonarrEvent::ToggleEpisodeMonitoring(episode_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -285,7 +285,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, ' let resp = self .network .handle_network_event( - SonarrEvent::ToggleSeasonMonitoring(Some((series_id, season_number))).into(), + SonarrEvent::ToggleSeasonMonitoring((series_id, season_number)).into(), ) .await?; serde_json::to_string_pretty(&resp)? diff --git a/src/cli/sonarr/refresh_command_handler.rs b/src/cli/sonarr/refresh_command_handler.rs index 418862f..9daa179 100644 --- a/src/cli/sonarr/refresh_command_handler.rs +++ b/src/cli/sonarr/refresh_command_handler.rs @@ -71,7 +71,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrRefreshCommand> SonarrRefreshCommand::Series { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::UpdateAndScanSeries(Some(series_id)).into()) + .handle_network_event(SonarrEvent::UpdateAndScanSeries(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/refresh_command_handler_tests.rs b/src/cli/sonarr/refresh_command_handler_tests.rs index ce133d2..38e4057 100644 --- a/src/cli/sonarr/refresh_command_handler_tests.rs +++ b/src/cli/sonarr/refresh_command_handler_tests.rs @@ -119,7 +119,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), + SonarrEvent::UpdateAndScanSeries(expected_series_id).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/sonarr_command_tests.rs b/src/cli/sonarr/sonarr_command_tests.rs index 7599274..7078d08 100644 --- a/src/cli/sonarr/sonarr_command_tests.rs +++ b/src/cli/sonarr/sonarr_command_tests.rs @@ -346,7 +346,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), + SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(), )) .times(1) .returning(|_| { @@ -434,7 +434,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), + SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(), )) .times(1) .returning(|_| { @@ -470,7 +470,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), + SonarrEvent::GetEpisodeReleases(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -498,7 +498,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), + SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -571,7 +571,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), + SonarrEvent::UpdateAndScanSeries(expected_series_id).into(), )) .times(1) .returning(|_| { @@ -597,7 +597,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::SearchNewSeries(Some(expected_search_query)).into(), + SonarrEvent::SearchNewSeries(expected_search_query).into(), )) .times(1) .returning(|_| { @@ -624,7 +624,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::StartTask(Some(expected_task_name)).into(), + SonarrEvent::StartTask(expected_task_name).into(), )) .times(1) .returning(|_| { @@ -651,7 +651,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::TestIndexer(Some(expected_indexer_id)).into(), + SonarrEvent::TestIndexer(expected_indexer_id).into(), )) .times(1) .returning(|_| { @@ -698,7 +698,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::ToggleEpisodeMonitoring(Some(expected_episode_id)).into(), + SonarrEvent::ToggleEpisodeMonitoring(expected_episode_id).into(), )) .times(1) .returning(|_| { @@ -729,8 +729,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::ToggleSeasonMonitoring(Some((expected_series_id, expected_season_number))) - .into(), + SonarrEvent::ToggleSeasonMonitoring((expected_series_id, expected_season_number)).into(), )) .times(1) .returning(|_| { diff --git a/src/cli/sonarr/trigger_automatic_search_command_handler.rs b/src/cli/sonarr/trigger_automatic_search_command_handler.rs index e87a5a5..27853f3 100644 --- a/src/cli/sonarr/trigger_automatic_search_command_handler.rs +++ b/src/cli/sonarr/trigger_automatic_search_command_handler.rs @@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand> SonarrTriggerAutomaticSearchCommand::Series { series_id } => { let resp = self .network - .handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(Some(series_id)).into()) + .handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(series_id).into()) .await?; serde_json::to_string_pretty(&resp)? } @@ -94,7 +94,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand> let resp = self .network .handle_network_event( - SonarrEvent::TriggerAutomaticSeasonSearch(Some((series_id, season_number))).into(), + SonarrEvent::TriggerAutomaticSeasonSearch((series_id, season_number)).into(), ) .await?; serde_json::to_string_pretty(&resp)? @@ -102,7 +102,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand> SonarrTriggerAutomaticSearchCommand::Episode { episode_id } => { let resp = self .network - .handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(Some(episode_id)).into()) + .handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id).into()) .await?; serde_json::to_string_pretty(&resp)? } diff --git a/src/cli/sonarr/trigger_automatic_search_command_handler_tests.rs b/src/cli/sonarr/trigger_automatic_search_command_handler_tests.rs index 03c4057..336ca6f 100644 --- a/src/cli/sonarr/trigger_automatic_search_command_handler_tests.rs +++ b/src/cli/sonarr/trigger_automatic_search_command_handler_tests.rs @@ -166,7 +166,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::TriggerAutomaticSeriesSearch(Some(expected_series_id)).into(), + SonarrEvent::TriggerAutomaticSeriesSearch(expected_series_id).into(), )) .times(1) .returning(|_| { @@ -197,11 +197,8 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::TriggerAutomaticSeasonSearch(Some(( - expected_series_id, - expected_season_number, - ))) - .into(), + SonarrEvent::TriggerAutomaticSeasonSearch((expected_series_id, expected_season_number)) + .into(), )) .times(1) .returning(|_| { @@ -233,7 +230,7 @@ mod tests { mock_network .expect_handle_network_event() .with(eq::( - SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), + SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(), )) .times(1) .returning(|_| { diff --git a/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs b/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs index 2a44f7f..17d6388 100644 --- a/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs +++ b/src/handlers/radarr_handlers/blocklist/blocklist_handler_tests.rs @@ -160,7 +160,7 @@ mod tests { #[case( ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::DeleteBlocklistItemPrompt, - RadarrEvent::DeleteBlocklistItem(None) + RadarrEvent::DeleteBlocklistItem(3) )] #[case( ActiveRadarrBlock::Blocklist, @@ -361,7 +361,7 @@ mod tests { #[case( ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::DeleteBlocklistItemPrompt, - RadarrEvent::DeleteBlocklistItem(None) + RadarrEvent::DeleteBlocklistItem(3) )] #[case( ActiveRadarrBlock::Blocklist, @@ -541,6 +541,22 @@ mod tests { }) } + #[test] + fn test_extract_blocklist_item_id() { + let mut app = App::default(); + app.data.radarr_data.blocklist.set_items(blocklist_vec()); + + let blocklist_item_id = BlocklistHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::Blocklist, + None, + ) + .extract_blocklist_item_id(); + + assert_eq!(blocklist_item_id, 3); + } + #[test] fn test_blocklist_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/blocklist/mod.rs b/src/handlers/radarr_handlers/blocklist/mod.rs index 2f23683..f1fc57a 100644 --- a/src/handlers/radarr_handlers/blocklist/mod.rs +++ b/src/handlers/radarr_handlers/blocklist/mod.rs @@ -28,6 +28,10 @@ impl<'a, 'b> BlocklistHandler<'a, 'b> { self.app.data.radarr_data.blocklist, BlocklistItem ); + + fn extract_blocklist_item_id(&self) -> i64 { + self.app.data.radarr_data.blocklist.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, 'b> { @@ -98,8 +102,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, match self.active_radarr_block { ActiveRadarrBlock::DeleteBlocklistItemPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::DeleteBlocklistItem(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem( + self.extract_blocklist_item_id(), + )); } self.app.pop_navigation_stack(); @@ -151,8 +156,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a, ActiveRadarrBlock::DeleteBlocklistItemPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::DeleteBlocklistItem(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem( + self.extract_blocklist_item_id(), + )); self.app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/collections/edit_collection_handler.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs index f29f8fd..e33d3c0 100644 --- a/src/handlers/radarr_handlers/collections/edit_collection_handler.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler.rs @@ -2,6 +2,8 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::radarr_models::EditCollectionParams; +use crate::models::servarr_data::radarr::modals::EditCollectionModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; @@ -18,6 +20,51 @@ pub(super) struct EditCollectionHandler<'a, 'b> { context: Option, } +impl<'a, 'b> EditCollectionHandler<'a, 'b> { + fn build_edit_collection_params(&mut self) -> EditCollectionParams { + let collection_id = self.app.data.radarr_data.collections.current_selection().id; + let EditCollectionModal { + path, + search_on_add, + minimum_availability_list, + monitored, + quality_profile_list, + } = self + .app + .data + .radarr_data + .edit_collection_modal + .as_ref() + .unwrap(); + let quality_profile = quality_profile_list.current_selection(); + let quality_profile_id = *self + .app + .data + .radarr_data + .quality_profile_map + .iter() + .filter(|(_, value)| *value == quality_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + + let root_folder_path: String = path.text.clone(); + let monitored = monitored.unwrap_or_default(); + let search_on_add = search_on_add.unwrap_or_default(); + let minimum_availability = *minimum_availability_list.current_selection(); + self.app.data.radarr_data.edit_collection_modal = None; + + EditCollectionParams { + collection_id, + monitored: Some(monitored), + minimum_availability: Some(minimum_availability), + quality_profile_id: Some(quality_profile_id), + root_folder_path: Some(root_folder_path), + search_on_add: Some(search_on_add), + } + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { EDIT_COLLECTION_BLOCKS.contains(&active_block) @@ -190,8 +237,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::EditCollectionConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::EditCollection(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection( + self.build_edit_collection_params(), + )); self.app.should_refresh = true; } @@ -310,7 +358,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle && key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection( + self.build_edit_collection_params(), + )); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs index d8f9f5b..cb741dc 100644 --- a/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use bimap::BiMap; use pretty_assertions::assert_str_eq; use strum::IntoEnumIterator; @@ -7,8 +8,9 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::collection; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::MinimumAvailability; + use crate::models::radarr_models::{Collection, EditCollectionParams, MinimumAvailability}; use crate::models::servarr_data::radarr::modals::EditCollectionModal; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, @@ -519,7 +521,34 @@ mod tests { #[test] fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() { let mut app = App::default(); - app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); + let mut edit_collection_modal = EditCollectionModal { + path: "/nfs/Test Path".into(), + monitored: Some(false), + search_on_add: Some(false), + ..EditCollectionModal::default() + }; + edit_collection_modal + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_collection_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal); + app.data.radarr_data.collections.set_items(vec![Collection { + monitored: false, + search_on_add: false, + ..collection() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_collection_params = EditCollectionParams { + collection_id: 123, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + search_on_add: Some(false), + }; app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.data.radarr_data.prompt_confirm = true; @@ -545,9 +574,10 @@ mod tests { ); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditCollection(None)) + Some(RadarrEvent::EditCollection(expected_edit_collection_params)) ); assert!(app.should_refresh); + assert!(app.data.radarr_data.edit_collection_modal.is_none()); } #[test] @@ -919,7 +949,34 @@ mod tests { #[test] fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() { let mut app = App::default(); - app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); + let mut edit_collection_modal = EditCollectionModal { + path: "/nfs/Test Path".into(), + monitored: Some(false), + search_on_add: Some(false), + ..EditCollectionModal::default() + }; + edit_collection_modal + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_collection_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal); + app.data.radarr_data.collections.set_items(vec![Collection { + monitored: false, + search_on_add: false, + ..collection() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_collection_params = EditCollectionParams { + collection_id: 123, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + search_on_add: Some(false), + }; app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.data.radarr_data.selected_block = @@ -944,9 +1001,10 @@ mod tests { ); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditCollection(None)) + Some(RadarrEvent::EditCollection(expected_edit_collection_params)) ); assert!(app.should_refresh); + assert!(app.data.radarr_data.edit_collection_modal.is_none()); } } @@ -961,6 +1019,50 @@ mod tests { }); } + #[test] + fn test_build_edit_collection_params() { + let mut app = App::default(); + let mut edit_collection_modal = EditCollectionModal { + path: "/nfs/Test Path".into(), + monitored: Some(false), + search_on_add: Some(false), + ..EditCollectionModal::default() + }; + edit_collection_modal + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_collection_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal); + app.data.radarr_data.collections.set_items(vec![Collection { + monitored: false, + search_on_add: false, + ..collection() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_collection_params = EditCollectionParams { + collection_id: 123, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + search_on_add: Some(false), + }; + + let edit_collection_params = EditCollectionHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::EditCollectionPrompt, + None, + ) + .build_edit_collection_params(); + + assert_eq!(edit_collection_params, expected_edit_collection_params); + assert!(app.data.radarr_data.edit_collection_modal.is_none()); + } + #[test] fn test_edit_collection_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs index e5e75bc..0323916 100644 --- a/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs +++ b/src/handlers/radarr_handlers/downloads/downloads_handler_tests.rs @@ -1,11 +1,13 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::downloads::DownloadsHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::download_record; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::DownloadRecord; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; @@ -137,7 +139,7 @@ mod tests { #[case( ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt, - RadarrEvent::DeleteDownload(None) + RadarrEvent::DeleteDownload(1) )] #[case( ActiveRadarrBlock::Downloads, @@ -154,7 +156,7 @@ mod tests { .data .radarr_data .downloads - .set_items(vec![DownloadRecord::default()]); + .set_items(vec![download_record()]); app.data.radarr_data.prompt_confirm = true; app.push_navigation_stack(base_route.into()); app.push_navigation_stack(prompt_block.into()); @@ -336,7 +338,7 @@ mod tests { #[case( ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt, - RadarrEvent::DeleteDownload(None) + RadarrEvent::DeleteDownload(1) )] #[case( ActiveRadarrBlock::Downloads, @@ -353,7 +355,7 @@ mod tests { .data .radarr_data .downloads - .set_items(vec![DownloadRecord::default()]); + .set_items(vec![download_record()]); app.push_navigation_stack(base_route.into()); app.push_navigation_stack(prompt_block.into()); @@ -385,6 +387,26 @@ mod tests { }) } + #[test] + fn test_extract_download_id() { + let mut app = App::default(); + app + .data + .radarr_data + .downloads + .set_items(vec![download_record()]); + + let download_id = DownloadsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::Downloads, + None, + ) + .extract_download_id(); + + assert_eq!(download_id, 1); + } + #[test] fn test_downloads_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/downloads/mod.rs b/src/handlers/radarr_handlers/downloads/mod.rs index a09966f..8a5246a 100644 --- a/src/handlers/radarr_handlers/downloads/mod.rs +++ b/src/handlers/radarr_handlers/downloads/mod.rs @@ -27,6 +27,10 @@ impl<'a, 'b> DownloadsHandler<'a, 'b> { self.app.data.radarr_data.downloads, DownloadRecord ); + + fn extract_download_id(&self) -> i64 { + self.app.data.radarr_data.downloads.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, 'b> { @@ -95,7 +99,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, match self.active_radarr_block { ActiveRadarrBlock::DeleteDownloadPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::DeleteDownload(self.extract_download_id())); } self.app.pop_navigation_stack(); @@ -138,7 +143,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a, ActiveRadarrBlock::DeleteDownloadPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteDownload(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::DeleteDownload(self.extract_download_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs index 7c0f936..b197c4b 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler.rs @@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; +use crate::models::servarr_models::EditIndexerParams; use crate::network::radarr_network::RadarrEvent; use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; @@ -17,6 +19,61 @@ pub(super) struct EditIndexerHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> EditIndexerHandler<'a, 'b> { + fn build_edit_indexer_params(&mut self) -> EditIndexerParams { + let indexer_id = self.app.data.radarr_data.indexers.current_selection().id; + let tags = self + .app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + + let params = { + let EditIndexerModal { + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + url, + api_key, + seed_ratio, + priority, + .. + } = self + .app + .data + .radarr_data + .edit_indexer_modal + .as_ref() + .unwrap(); + + EditIndexerParams { + indexer_id, + name: Some(name.text.clone()), + enable_rss: Some(enable_rss.unwrap_or_default()), + enable_automatic_search: Some(enable_automatic_search.unwrap_or_default()), + enable_interactive_search: Some(enable_interactive_search.unwrap_or_default()), + url: Some(url.text.clone()), + api_key: Some(api_key.text.clone()), + seed_ratio: Some(seed_ratio.text.clone()), + tags: None, + tag_input_string: Some(tags), + priority: Some(*priority), + clear_tags: false, + } + }; + + self.app.data.radarr_data.edit_indexer_modal = None; + + params + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { EDIT_INDEXER_BLOCKS.contains(&active_block) @@ -297,12 +354,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' let selected_block = self.app.data.radarr_data.selected_block.get_active_block(); match selected_block { ActiveRadarrBlock::EditIndexerConfirmPrompt => { - let radarr_data = &mut self.app.data.radarr_data; - if radarr_data.prompt_confirm { - radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None)); + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params())); self.app.should_refresh = true; } else { - radarr_data.edit_indexer_modal = None; + self.app.data.radarr_data.edit_indexer_modal = None; } self.app.pop_navigation_stack(); @@ -464,7 +521,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<' && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); 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 9325c65..cd912c4 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_handler_tests.rs @@ -4,9 +4,12 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; + use crate::models::servarr_models::EditIndexerParams; + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; mod test_handle_scroll_up_and_down { @@ -896,7 +899,32 @@ mod tests { .radarr_data .selected_block .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); - app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + 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(), + priority: 25, + }; + app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.radarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(25), + ..EditIndexerParams::default() + }; app.data.radarr_data.prompt_confirm = true; EditIndexerHandler::with( @@ -908,11 +936,11 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); - assert!(app.data.radarr_data.edit_indexer_modal.is_some()); + assert!(app.data.radarr_data.edit_indexer_modal.is_none()); assert!(app.should_refresh); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditIndexer(None)) + Some(RadarrEvent::EditIndexer(expected_edit_indexer_params)) ); } @@ -1408,7 +1436,7 @@ mod tests { use crate::models::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; use crate::models::BlockSelectionState; use crate::network::radarr_network::RadarrEvent; - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use super::*; @@ -1709,7 +1737,32 @@ mod tests { .radarr_data .selected_block .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); - app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + 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(), + priority: 25, + }; + app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.radarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(25), + ..EditIndexerParams::default() + }; EditIndexerHandler::with( DEFAULT_KEYBINDINGS.confirm.key, @@ -1720,11 +1773,11 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); - assert!(app.data.radarr_data.edit_indexer_modal.is_some()); + assert!(app.data.radarr_data.edit_indexer_modal.is_none()); assert!(app.should_refresh); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditIndexer(None)) + Some(RadarrEvent::EditIndexer(expected_edit_indexer_params)) ); } } @@ -1740,6 +1793,48 @@ mod tests { }) } + #[test] + fn test_build_edit_indexer_params() { + let mut app = App::default(); + 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(), + priority: 25, + }; + app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.radarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(25), + ..EditIndexerParams::default() + }; + + let edit_indexer_params = EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::EditIndexerPrompt, + None, + ) + .build_edit_indexer_params(); + + assert_eq!(edit_indexer_params, expected_edit_indexer_params); + assert!(app.data.radarr_data.edit_indexer_modal.is_none()); + } + #[test] fn test_edit_indexer_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs index 046e7b5..b8d73ef 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::radarr_models::IndexerSettings; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, }; @@ -19,6 +20,14 @@ pub(super) struct IndexerSettingsHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> IndexerSettingsHandler<'a, 'b> { + fn build_edit_indexer_settings_body(&mut self) -> IndexerSettings { + let indexer_settings = self.app.data.radarr_data.indexer_settings.clone().unwrap(); + self.app.data.radarr_data.indexer_settings = None; + indexer_settings + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { INDEXER_SETTINGS_BLOCKS.contains(&active_block) @@ -166,12 +175,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl ActiveRadarrBlock::AllIndexerSettingsPrompt => { match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::IndexerSettingsConfirmPrompt => { - let radarr_data = &mut self.app.data.radarr_data; - if radarr_data.prompt_confirm { - radarr_data.prompt_confirm_action = Some(RadarrEvent::EditAllIndexerSettings(None)); + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some( + RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()), + ); self.app.should_refresh = true; } else { - radarr_data.indexer_settings = None; + self.app.data.radarr_data.indexer_settings = None; } self.app.pop_navigation_stack(); @@ -258,8 +268,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::EditAllIndexerSettings(None)); + self.app.data.radarr_data.prompt_confirm_action = Some( + RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()), + ); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs index 8d3bddf..df09b83 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs @@ -1,11 +1,13 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer_settings; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::IndexerSettings; use crate::models::servarr_data::radarr::radarr_data::{ @@ -472,7 +474,7 @@ mod tests { .radarr_data .selected_block .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); - app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.radarr_data.indexer_settings = Some(indexer_settings()); app.data.radarr_data.prompt_confirm = true; IndexerSettingsHandler::with( @@ -486,9 +488,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditAllIndexerSettings(None)) + Some(RadarrEvent::EditAllIndexerSettings(indexer_settings())) ); - assert!(app.data.radarr_data.indexer_settings.is_some()); + assert!(app.data.radarr_data.indexer_settings.is_none()); assert!(app.should_refresh); } @@ -858,7 +860,7 @@ mod tests { } mod test_handle_key_char { - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use crate::{ models::{ @@ -937,7 +939,7 @@ mod tests { .radarr_data .selected_block .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); - app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.radarr_data.indexer_settings = Some(indexer_settings()); IndexerSettingsHandler::with( DEFAULT_KEYBINDINGS.confirm.key, @@ -950,9 +952,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditAllIndexerSettings(None)) + Some(RadarrEvent::EditAllIndexerSettings(indexer_settings())) ); - assert!(app.data.radarr_data.indexer_settings.is_some()); + assert!(app.data.radarr_data.indexer_settings.is_none()); assert!(app.should_refresh); } } @@ -968,6 +970,23 @@ mod tests { }) } + #[test] + fn test_build_edit_indexer_settings_body() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(indexer_settings()); + + let body = IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::AllIndexerSettingsPrompt, + None, + ) + .build_edit_indexer_settings_body(); + + assert_eq!(body, indexer_settings()); + assert!(app.data.radarr_data.indexer_settings.is_none()); + } + #[test] fn test_edit_indexer_settings_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index 5f070a7..2097e35 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use rstest::rstest; use strum::IntoEnumIterator; @@ -7,6 +8,7 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::indexers::IndexersHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, @@ -123,17 +125,17 @@ mod tests { } mod test_handle_submit { + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer; 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, }; use crate::models::servarr_models::{Indexer, IndexerField}; + use crate::network::radarr_network::RadarrEvent; use bimap::BiMap; use pretty_assertions::assert_eq; use serde_json::{Number, Value}; - use crate::network::radarr_network::RadarrEvent; - use super::*; const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; @@ -239,11 +241,7 @@ mod tests { #[test] fn test_delete_indexer_prompt_confirm_submit() { let mut app = App::default(); - app - .data - .radarr_data - .indexers - .set_items(vec![Indexer::default()]); + app.data.radarr_data.indexers.set_items(vec![indexer()]); app.data.radarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); @@ -259,7 +257,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteIndexer(None)) + Some(RadarrEvent::DeleteIndexer(1)) ); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); } @@ -348,13 +346,13 @@ mod tests { mod test_handle_key_char { use pretty_assertions::assert_eq; + use super::*; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer; use crate::{ models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, network::radarr_network::RadarrEvent, }; - use super::*; - #[test] fn test_refresh_indexers_key() { let mut app = App::default(); @@ -542,11 +540,7 @@ mod tests { #[test] fn test_delete_indexer_prompt_confirm() { let mut app = App::default(); - app - .data - .radarr_data - .indexers - .set_items(vec![Indexer::default()]); + app.data.radarr_data.indexers.set_items(vec![indexer()]); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); @@ -561,7 +555,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteIndexer(None)) + Some(RadarrEvent::DeleteIndexer(1)) ); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); } @@ -639,6 +633,22 @@ mod tests { }) } + #[test] + fn test_extract_indexer_id() { + let mut app = App::default(); + app.data.radarr_data.indexers.set_items(vec![indexer()]); + + let indexer_id = IndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::Indexers, + None, + ) + .extract_indexer_id(); + + assert_eq!(indexer_id, 1); + } + #[test] fn test_indexers_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index 282e80a..7d64f53 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -33,6 +33,10 @@ pub(super) struct IndexersHandler<'a, 'b> { impl<'a, 'b> IndexersHandler<'a, 'b> { handle_table_events!(self, indexers, self.app.data.radarr_data.indexers, Indexer); + + fn extract_indexer_id(&self) -> i64 { + self.app.data.radarr_data.indexers.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> { @@ -115,9 +119,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, fn handle_submit(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::DeleteIndexerPrompt => { + let indexer_id = self.extract_indexer_id(); let radarr_data = &mut self.app.data.radarr_data; if radarr_data.prompt_confirm { - radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(None)); + radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(indexer_id)); } self.app.pop_navigation_stack(); @@ -189,7 +194,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, ActiveRadarrBlock::DeleteIndexerPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/library/add_movie_handler.rs b/src/handlers/radarr_handlers/library/add_movie_handler.rs index c6c11d5..586b5f9 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler.rs @@ -1,10 +1,14 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; -use crate::models::radarr_models::AddMovieSearchResult; +use crate::models::radarr_models::{ + AddMovieBody, AddMovieOptions, AddMovieSearchResult, CollectionMovie, +}; +use crate::models::servarr_data::radarr::modals::AddMovieModal; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, }; +use crate::models::stateful_table::StatefulTable; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; @@ -30,9 +34,95 @@ impl<'a, 'b> AddMovieHandler<'a, 'b> { .radarr_data .add_searched_movies .as_mut() - .unwrap(), + .unwrap_or(&mut StatefulTable::default()), AddMovieSearchResult ); + + fn build_add_movie_body(&mut self) -> AddMovieBody { + let tags = self + .app + .data + .radarr_data + .add_movie_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + let AddMovieModal { + root_folder_list, + monitor_list, + minimum_availability_list, + quality_profile_list, + .. + } = self.app.data.radarr_data.add_movie_modal.as_ref().unwrap(); + let (tmdb_id, title) = if let Some(context) = self.context { + if context == ActiveRadarrBlock::CollectionDetails { + let CollectionMovie { tmdb_id, title, .. } = self + .app + .data + .radarr_data + .collection_movies + .current_selection() + .clone(); + (tmdb_id, title.text) + } else { + let AddMovieSearchResult { tmdb_id, title, .. } = self + .app + .data + .radarr_data + .add_searched_movies + .as_ref() + .unwrap() + .current_selection() + .clone(); + (tmdb_id, title.text) + } + } else { + let AddMovieSearchResult { tmdb_id, title, .. } = self + .app + .data + .radarr_data + .add_searched_movies + .as_ref() + .unwrap() + .current_selection() + .clone(); + (tmdb_id, title.text) + }; + let quality_profile = quality_profile_list.current_selection(); + let quality_profile_id = *self + .app + .data + .radarr_data + .quality_profile_map + .iter() + .filter(|(_, value)| *value == quality_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + + let path = root_folder_list.current_selection().path.clone(); + let monitor = monitor_list.current_selection().to_string(); + let minimum_availability = minimum_availability_list.current_selection().to_string(); + + self.app.data.radarr_data.add_movie_modal = None; + + AddMovieBody { + tmdb_id, + title, + root_folder_path: path, + minimum_availability, + monitored: true, + quality_profile_id, + tags: Vec::new(), + tag_input_string: Some(tags), + add_options: AddMovieOptions { + monitor, + search_for_movie: true, + }, + } + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> { @@ -361,7 +451,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::AddMovieConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::AddMovie(self.build_add_movie_body())); } self.app.pop_navigation_stack(); @@ -461,7 +552,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, && key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::AddMovie(self.build_add_movie_body())); self.app.pop_navigation_stack(); } } diff --git a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs index ebd98b5..0075b84 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs @@ -1,17 +1,24 @@ #[cfg(test)] mod tests { + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::add_movie_search_result; + use crate::models::stateful_table::StatefulTable; use pretty_assertions::assert_str_eq; + use rstest::rstest; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::add_movie_body; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::collection_movie; use crate::handlers::KeyEventHandler; use crate::models::radarr_models::{AddMovieSearchResult, MinimumAvailability, MovieMonitor}; + use crate::models::servarr_data::radarr::modals::AddMovieModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS}; use crate::models::servarr_models::RootFolder; use crate::models::HorizontallyScrollableText; + use bimap::BiMap; mod test_handle_scroll_up_and_down { use pretty_assertions::assert_eq; @@ -758,6 +765,9 @@ mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::{ + add_movie_body, add_movie_search_result, collection_movie, + }; use crate::models::radarr_models::Movie; use crate::models::servarr_data::radarr::modals::AddMovieModal; use crate::models::servarr_data::radarr::radarr_data::ADD_MOVIE_SELECTION_BLOCKS; @@ -969,8 +979,10 @@ mod tests { assert_eq!(app.data.radarr_data.prompt_confirm_action, None); } - #[test] - fn test_add_movie_confirm_prompt_prompt_confirmation_submit() { + #[rstest] + fn test_add_movie_confirm_prompt_prompt_confirmation_submit( + #[values(true, false)] movie_details_context: bool, + ) { let mut app = App::default(); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); @@ -982,21 +994,67 @@ mod tests { .radarr_data .selected_block .set_index(0, ADD_MOVIE_SELECTION_BLOCKS.len() - 1); + let mut add_movie_modal = AddMovieModal { + tags: "usenet, testing".into(), + ..AddMovieModal::default() + }; + add_movie_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_movie_modal.root_folder_list.state.select(Some(1)); + add_movie_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_movie_modal + .monitor_list + .set_items(Vec::from_iter(MovieMonitor::iter())); + add_movie_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.add_movie_modal = Some(add_movie_modal); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + let context = if movie_details_context { + app + .data + .radarr_data + .collection_movies + .set_items(vec![collection_movie()]); + Some(ActiveRadarrBlock::CollectionDetails) + } else { + let mut add_searched_movies = StatefulTable::default(); + add_searched_movies.set_items(vec![add_movie_search_result()]); + app.data.radarr_data.add_searched_movies = Some(add_searched_movies); + None + }; AddMovieHandler::with( SUBMIT_KEY, &mut app, ActiveRadarrBlock::AddMoviePrompt, - None, + context, ) .handle(); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::AddMovie(None)) + Some(RadarrEvent::AddMovie(add_movie_body())) ); - assert!(app.data.radarr_data.add_movie_modal.is_some()); + assert!(app.data.radarr_data.add_movie_modal.is_none()); } #[rstest] @@ -1266,10 +1324,18 @@ mod tests { } mod test_handle_key_char { + use bimap::BiMap; + use pretty_assertions::assert_eq; + use rstest::rstest; + use super::*; use crate::{ + handlers::radarr_handlers::radarr_handler_test_utils::utils::{ + add_movie_body, add_movie_search_result, collection_movie, + }, models::{ servarr_data::radarr::{modals::AddMovieModal, radarr_data::ADD_MOVIE_SELECTION_BLOCKS}, + stateful_table::StatefulTable, BlockSelectionState, }, network::radarr_network::RadarrEvent, @@ -1368,8 +1434,10 @@ mod tests { ); } - #[test] - fn test_add_movie_confirm_prompt_prompt_confirmation_confirm() { + #[rstest] + fn test_add_movie_confirm_prompt_prompt_confirmation_confirm( + #[values(true, false)] movie_details_context: bool, + ) { let mut app = App::default(); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); @@ -1380,21 +1448,67 @@ mod tests { .radarr_data .selected_block .set_index(0, ADD_MOVIE_SELECTION_BLOCKS.len() - 1); + let mut add_movie_modal = AddMovieModal { + tags: "usenet, testing".into(), + ..AddMovieModal::default() + }; + add_movie_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_movie_modal.root_folder_list.state.select(Some(1)); + add_movie_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_movie_modal + .monitor_list + .set_items(Vec::from_iter(MovieMonitor::iter())); + add_movie_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.add_movie_modal = Some(add_movie_modal); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + let context = if movie_details_context { + app + .data + .radarr_data + .collection_movies + .set_items(vec![collection_movie()]); + Some(ActiveRadarrBlock::CollectionDetails) + } else { + let mut add_searched_movies = StatefulTable::default(); + add_searched_movies.set_items(vec![add_movie_search_result()]); + app.data.radarr_data.add_searched_movies = Some(add_searched_movies); + None + }; AddMovieHandler::with( DEFAULT_KEYBINDINGS.confirm.key, &mut app, ActiveRadarrBlock::AddMoviePrompt, - None, + context, ) .handle(); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::AddMovie(None)) + Some(RadarrEvent::AddMovie(add_movie_body())) ); - assert!(app.data.radarr_data.add_movie_modal.is_some()); + assert!(app.data.radarr_data.add_movie_modal.is_none()); } } @@ -1409,6 +1523,80 @@ mod tests { }); } + #[test] + fn test_add_movie_search_no_panic_on_none_search_result() { + let mut app = App::default(); + app.data.radarr_data.add_searched_movies = None; + + AddMovieHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::AddMovieSearchResults, + None, + ) + .handle(); + } + + #[rstest] + fn test_build_add_movie_body(#[values(true, false)] movie_details_context: bool) { + let mut app = App::default(); + let mut add_movie_modal = AddMovieModal { + tags: "usenet, testing".into(), + ..AddMovieModal::default() + }; + add_movie_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_movie_modal.root_folder_list.state.select(Some(1)); + add_movie_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_movie_modal + .monitor_list + .set_items(Vec::from_iter(MovieMonitor::iter())); + add_movie_modal + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.add_movie_modal = Some(add_movie_modal); + app.data.radarr_data.quality_profile_map = BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + let context = if movie_details_context { + app + .data + .radarr_data + .collection_movies + .set_items(vec![collection_movie()]); + Some(ActiveRadarrBlock::CollectionDetails) + } else { + let mut add_searched_movies = StatefulTable::default(); + add_searched_movies.set_items(vec![add_movie_search_result()]); + app.data.radarr_data.add_searched_movies = Some(add_searched_movies); + None + }; + + let actual_add_movie_body = AddMovieHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveRadarrBlock::AddMoviePrompt, + context, + ) + .build_add_movie_body(); + + assert_eq!(actual_add_movie_body, add_movie_body()); + } + #[test] fn test_add_movie_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/library/delete_movie_handler.rs b/src/handlers/radarr_handlers/library/delete_movie_handler.rs index 6e78552..be44f50 100644 --- a/src/handlers/radarr_handlers/library/delete_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/delete_movie_handler.rs @@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::radarr_models::DeleteMovieParams; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::network::radarr_network::RadarrEvent; @@ -16,6 +17,21 @@ pub(super) struct DeleteMovieHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> DeleteMovieHandler<'a, 'b> { + fn build_delete_movie_params(&mut self) -> DeleteMovieParams { + let id = self.app.data.radarr_data.movies.current_selection().id; + let delete_movie_files = self.app.data.radarr_data.delete_movie_files; + let add_list_exclusion = self.app.data.radarr_data.add_list_exclusion; + self.app.data.radarr_data.reset_delete_movie_preferences(); + + DeleteMovieParams { + id, + delete_movie_files, + add_list_exclusion, + } + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { DELETE_MOVIE_BLOCKS.contains(&active_block) @@ -72,7 +88,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<' match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::DeleteMovieConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::DeleteMovie(self.build_delete_movie_params())); self.app.should_refresh = true; } else { self.app.data.radarr_data.reset_delete_movie_preferences(); @@ -108,7 +125,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<' && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::DeleteMovie(self.build_delete_movie_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs index 214aa46..b267aba 100644 --- a/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/delete_movie_handler_tests.rs @@ -1,12 +1,15 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::movie; use crate::handlers::KeyEventHandler; + use crate::models::radarr_models::DeleteMovieParams; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; mod test_handle_scroll_up_and_down { @@ -119,8 +122,14 @@ mod tests { #[test] fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() { let mut app = App::default(); + let expected_delete_movie_params = DeleteMovieParams { + id: 1, + delete_movie_files: true, + add_list_exclusion: true, + }; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + app.data.radarr_data.movies.set_items(vec![movie()]); app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.add_list_exclusion = true; @@ -142,12 +151,12 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteMovie(None)) + Some(RadarrEvent::DeleteMovie(expected_delete_movie_params)) ); assert!(app.should_refresh); assert!(app.data.radarr_data.prompt_confirm); - assert!(app.data.radarr_data.delete_movie_files); - assert!(app.data.radarr_data.add_list_exclusion); + assert!(!app.data.radarr_data.delete_movie_files); + assert!(!app.data.radarr_data.add_list_exclusion); } #[test] @@ -212,6 +221,7 @@ mod tests { mod test_handle_esc { use super::*; + use pretty_assertions::assert_eq; use rstest::rstest; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; @@ -248,14 +258,21 @@ mod tests { }, network::radarr_network::RadarrEvent, }; + use pretty_assertions::assert_eq; use super::*; #[test] fn test_delete_movie_confirm_prompt_prompt_confirm() { let mut app = App::default(); + let expected_delete_movie_params = DeleteMovieParams { + id: 1, + delete_movie_files: true, + add_list_exclusion: true, + }; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); + app.data.radarr_data.movies.set_items(vec![movie()]); app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS); @@ -276,12 +293,12 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteMovie(None)) + Some(RadarrEvent::DeleteMovie(expected_delete_movie_params)) ); assert!(app.should_refresh); assert!(app.data.radarr_data.prompt_confirm); - assert!(app.data.radarr_data.delete_movie_files); - assert!(app.data.radarr_data.add_list_exclusion); + assert!(!app.data.radarr_data.delete_movie_files); + assert!(!app.data.radarr_data.add_list_exclusion); } } @@ -296,6 +313,31 @@ mod tests { }); } + #[test] + fn test_build_delete_movie_params() { + let mut app = App::default(); + app.data.radarr_data.movies.set_items(vec![movie()]); + app.data.radarr_data.delete_movie_files = true; + app.data.radarr_data.add_list_exclusion = true; + let expected_delete_movie_params = DeleteMovieParams { + id: 1, + delete_movie_files: true, + add_list_exclusion: true, + }; + + let delete_movie_params = DeleteMovieHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::DeleteMoviePrompt, + None, + ) + .build_delete_movie_params(); + + assert_eq!(delete_movie_params, expected_delete_movie_params); + assert!(!app.data.radarr_data.delete_movie_files); + assert!(!app.data.radarr_data.add_list_exclusion); + } + #[test] fn test_delete_movie_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/library/edit_movie_handler.rs b/src/handlers/radarr_handlers/library/edit_movie_handler.rs index c11e908..47ce109 100644 --- a/src/handlers/radarr_handlers/library/edit_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/edit_movie_handler.rs @@ -2,6 +2,8 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::radarr_models::EditMovieParams; +use crate::models::servarr_data::radarr::modals::EditMovieModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; @@ -18,6 +20,57 @@ pub(super) struct EditMovieHandler<'a, 'b> { context: Option, } +impl<'a, 'b> EditMovieHandler<'a, 'b> { + fn build_edit_movie_params(&mut self) -> EditMovieParams { + let movie_id = self.app.data.radarr_data.movies.current_selection().id; + let tags = self + .app + .data + .radarr_data + .edit_movie_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + let params = { + let EditMovieModal { + monitored, + path, + minimum_availability_list, + quality_profile_list, + .. + } = self.app.data.radarr_data.edit_movie_modal.as_ref().unwrap(); + let quality_profile = quality_profile_list.current_selection(); + let quality_profile_id = *self + .app + .data + .radarr_data + .quality_profile_map + .iter() + .filter(|(_, value)| *value == quality_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + + EditMovieParams { + movie_id, + monitored: *monitored, + minimum_availability: Some(*minimum_availability_list.current_selection()), + quality_profile_id: Some(quality_profile_id), + root_folder_path: Some(path.text.clone()), + tags: None, + tag_input_string: Some(tags), + clear_tags: false, + } + }; + + self.app.data.radarr_data.edit_movie_modal = None; + + params + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { EDIT_MOVIE_BLOCKS.contains(&active_block) @@ -222,7 +275,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::EditMovieConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::EditMovie(self.build_edit_movie_params())); self.app.should_refresh = true; } @@ -333,7 +387,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, && key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditMovie(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::EditMovie(self.build_edit_movie_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs index 8835d13..1e3bf48 100644 --- a/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/edit_movie_handler_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use bimap::BiMap; use pretty_assertions::assert_str_eq; use strum::IntoEnumIterator; @@ -7,8 +8,9 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::movie; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::MinimumAvailability; + use crate::models::radarr_models::{EditMovieParams, MinimumAvailability, Movie}; use crate::models::servarr_data::radarr::modals::EditMovieModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; @@ -641,7 +643,34 @@ mod tests { #[test] fn test_edit_movie_confirm_prompt_prompt_confirmation_submit() { let mut app = App::default(); - app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); + let mut edit_movie = EditMovieModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + ..EditMovieModal::default() + }; + edit_movie + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_movie + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_movie_modal = Some(edit_movie); + app.data.radarr_data.movies.set_items(vec![Movie { + monitored: false, + ..movie() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_movie_params = EditMovieParams { + movie_id: 1, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".into()), + ..EditMovieParams::default() + }; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.data.radarr_data.prompt_confirm = true; @@ -663,9 +692,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditMovie(None)) + Some(RadarrEvent::EditMovie(expected_edit_movie_params)) ); - assert!(app.data.radarr_data.edit_movie_modal.is_some()); + assert!(app.data.radarr_data.edit_movie_modal.is_none()); assert!(app.should_refresh); } @@ -1053,7 +1082,34 @@ mod tests { #[test] fn test_edit_movie_confirm_prompt_prompt_confirm() { let mut app = App::default(); - app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); + let mut edit_movie = EditMovieModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + ..EditMovieModal::default() + }; + edit_movie + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_movie + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_movie_modal = Some(edit_movie); + app.data.radarr_data.movies.set_items(vec![Movie { + monitored: false, + ..movie() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_movie_params = EditMovieParams { + movie_id: 1, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".into()), + ..EditMovieParams::default() + }; app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); @@ -1074,9 +1130,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::EditMovie(None)) + Some(RadarrEvent::EditMovie(expected_edit_movie_params)) ); - assert!(app.data.radarr_data.edit_movie_modal.is_some()); + assert!(app.data.radarr_data.edit_movie_modal.is_none()); assert!(app.should_refresh); } } @@ -1092,6 +1148,50 @@ mod tests { }); } + #[test] + fn test_build_edit_movie_params() { + let mut app = App::default(); + let mut edit_movie = EditMovieModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + ..EditMovieModal::default() + }; + edit_movie + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_movie + .minimum_availability_list + .set_items(Vec::from_iter(MinimumAvailability::iter())); + app.data.radarr_data.edit_movie_modal = Some(edit_movie); + app.data.radarr_data.movies.set_items(vec![Movie { + monitored: false, + ..movie() + }]); + app.data.radarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + let expected_edit_movie_params = EditMovieParams { + movie_id: 1, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".into()), + ..EditMovieParams::default() + }; + + let edit_movie_params = EditMovieHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::EditMoviePrompt, + None, + ) + .build_edit_movie_params(); + + assert_eq!(edit_movie_params, expected_edit_movie_params); + assert!(app.data.radarr_data.edit_movie_modal.is_none()); + } + #[test] fn test_edit_movie_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/library/movie_details_handler.rs b/src/handlers/radarr_handlers/library/movie_details_handler.rs index a8d61ac..a379952 100644 --- a/src/handlers/radarr_handlers/library/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/library/movie_details_handler.rs @@ -6,7 +6,9 @@ use crate::event::Key; use crate::handle_table_events; use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; -use crate::models::radarr_models::{Credit, MovieHistoryItem, RadarrRelease}; +use crate::models::radarr_models::{ + Credit, MovieHistoryItem, RadarrRelease, RadarrReleaseDownloadBody, +}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS, }; @@ -79,6 +81,35 @@ impl<'a, 'b> MovieDetailsHandler<'a, 'b> { .movie_crew, Credit ); + + fn build_radarr_release_download_body(&self) -> RadarrReleaseDownloadBody { + let movie_id = self.app.data.radarr_data.movies.current_selection().id; + let (guid, indexer_id) = { + let RadarrRelease { + guid, indexer_id, .. + } = self + .app + .data + .radarr_data + .movie_details_modal + .as_ref() + .unwrap() + .movie_releases + .current_selection(); + + (guid.clone(), *indexer_id) + }; + + RadarrReleaseDownloadBody { + guid, + indexer_id, + movie_id, + } + } + + fn extract_movie_id(&self) -> i64 { + self.app.data.radarr_data.movies.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<'a, 'b> { @@ -240,14 +271,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler< ActiveRadarrBlock::AutomaticallySearchMoviePrompt => { if self.app.data.radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::TriggerAutomaticSearch(None)); + Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id())); } self.app.pop_navigation_stack(); } ActiveRadarrBlock::UpdateAndScanPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::UpdateAndScan(self.extract_movie_id())); } self.app.pop_navigation_stack(); @@ -259,8 +291,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler< } ActiveRadarrBlock::ManualSearchConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::DownloadRelease(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease( + self.build_radarr_release_download_body(), + )); } self.app.pop_navigation_stack(); @@ -333,19 +366,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler< { self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::TriggerAutomaticSearch(None)); + Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id())); self.app.pop_navigation_stack(); } ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAndScan(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::UpdateAndScan(self.extract_movie_id())); self.app.pop_navigation_stack(); } ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease( + self.build_radarr_release_download_body(), + )); self.app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs b/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs index 4f7e0ab..66f321d 100644 --- a/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/movie_details_handler_tests.rs @@ -2,7 +2,7 @@ mod tests { use std::cmp::Ordering; - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; use serde_json::Number; use strum::IntoEnumIterator; @@ -13,9 +13,10 @@ mod tests { use crate::handlers::radarr_handlers::library::movie_details_handler::{ releases_sorting_options, MovieDetailsHandler, }; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::{movie, release}; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::RadarrRelease; use crate::models::radarr_models::{Credit, MovieHistoryItem}; + use crate::models::radarr_models::{RadarrRelease, RadarrReleaseDownloadBody}; use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::models::servarr_models::{Language, Quality, QualityWrapper}; @@ -130,6 +131,7 @@ mod tests { mod test_handle_home_end { use crate::models::servarr_data::radarr::modals::MovieDetailsModal; + use pretty_assertions::assert_eq; use super::*; @@ -359,25 +361,31 @@ mod tests { #[rstest] #[case( ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - RadarrEvent::TriggerAutomaticSearch(None) - )] - #[case( - ActiveRadarrBlock::UpdateAndScanPrompt, - RadarrEvent::UpdateAndScan(None) + RadarrEvent::TriggerAutomaticSearch(1) )] + #[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan(1))] #[case( ActiveRadarrBlock::ManualSearchConfirmPrompt, - RadarrEvent::DownloadRelease(None) + RadarrEvent::DownloadRelease(RadarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + movie_id: 1, + }) )] fn test_movie_info_prompt_confirm_submit( #[case] prompt_block: ActiveRadarrBlock, #[case] expected_action: RadarrEvent, ) { let mut app = App::default(); - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal { + let mut movie_details_modal = MovieDetailsModal { movie_details: ScrollableText::with_string("test".to_owned()), ..MovieDetailsModal::default() - }); + }; + movie_details_modal + .movie_releases + .set_items(vec![release()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + app.data.radarr_data.movies.set_items(vec![movie()]); app.data.radarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); app.push_navigation_stack(prompt_block.into()); @@ -771,25 +779,32 @@ mod tests { #[rstest] #[case( ActiveRadarrBlock::AutomaticallySearchMoviePrompt, - RadarrEvent::TriggerAutomaticSearch(None) - )] - #[case( - ActiveRadarrBlock::UpdateAndScanPrompt, - RadarrEvent::UpdateAndScan(None) + RadarrEvent::TriggerAutomaticSearch(1) )] + #[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan(1))] #[case( ActiveRadarrBlock::ManualSearchConfirmPrompt, - RadarrEvent::DownloadRelease(None) + RadarrEvent::DownloadRelease(RadarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + movie_id: 1, + }) )] fn test_movie_info_prompt_confirm( #[case] prompt_block: ActiveRadarrBlock, #[case] expected_action: RadarrEvent, ) { let mut app = App::default(); - app.data.radarr_data.movie_details_modal = Some(MovieDetailsModal { + let mut movie_details_modal = MovieDetailsModal { movie_details: ScrollableText::with_string("test".to_owned()), ..MovieDetailsModal::default() - }); + }; + movie_details_modal + .movie_releases + .set_items(vec![release()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + app.data.radarr_data.movies.set_items(vec![movie()]); + app.data.radarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); app.push_navigation_stack(prompt_block.into()); @@ -813,6 +828,48 @@ mod tests { } } + #[test] + fn test_build_radarr_release_download_body() { + let mut app = App::default(); + let mut movie_details_modal = MovieDetailsModal::default(); + movie_details_modal + .movie_releases + .set_items(vec![release()]); + app.data.radarr_data.movie_details_modal = Some(movie_details_modal); + app.data.radarr_data.movies.set_items(vec![movie()]); + let expected_body = RadarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + movie_id: 1, + }; + + let body = MovieDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::ManualSearchConfirmPrompt, + None, + ) + .build_radarr_release_download_body(); + + assert_eq!(body, expected_body); + } + + #[test] + fn test_extract_movie_id() { + let mut app = App::default(); + app.data.radarr_data.movies.set_items(vec![movie()]); + + let movie_id = MovieDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::AutomaticallySearchMoviePrompt, + None, + ) + .extract_movie_id(); + + assert_eq!(movie_id, 1); + } + #[test] fn test_releases_sorting_options_source() { let expected_cmp_fn: fn(&RadarrRelease, &RadarrRelease) -> Ordering = diff --git a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs index b56730e..5c18dee 100644 --- a/src/handlers/radarr_handlers/radarr_handler_test_utils.rs +++ b/src/handlers/radarr_handlers/radarr_handler_test_utils.rs @@ -1,6 +1,18 @@ #[cfg(test)] #[macro_use] -mod utils { +pub(in crate::handlers::radarr_handlers) mod utils { + use crate::models::radarr_models::{ + AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie, + DownloadRecord, IndexerSettings, MediaInfo, MinimumAvailability, Movie, MovieCollection, + MovieFile, RadarrRelease, Rating, RatingsList, + }; + use crate::models::servarr_models::{ + Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder, + }; + use crate::models::HorizontallyScrollableText; + use chrono::DateTime; + use serde_json::{json, Number}; + #[macro_export] macro_rules! test_edit_movie_key { ($handler:ident, $block:expr, $context:expr) => { @@ -228,4 +240,245 @@ mod utils { ); }; } + + pub fn language() -> Language { + Language { + id: 1, + name: "English".to_owned(), + } + } + + pub fn genres() -> Vec { + vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()] + } + + pub fn rating() -> Rating { + Rating { + value: Number::from_f64(9.9).unwrap(), + } + } + + pub fn ratings_list() -> RatingsList { + RatingsList { + imdb: Some(rating()), + tmdb: Some(rating()), + rotten_tomatoes: Some(rating()), + } + } + + pub fn media_info() -> MediaInfo { + MediaInfo { + audio_bitrate: 0, + audio_channels: Number::from_f64(7.1).unwrap(), + audio_codec: Some("AAC".to_owned()), + audio_languages: Some("eng".to_owned()), + audio_stream_count: 1, + video_bit_depth: 10, + video_bitrate: 0, + video_codec: "x265".to_owned(), + video_fps: Number::from_f64(23.976).unwrap(), + resolution: "1920x804".to_owned(), + run_time: "2:00:00".to_owned(), + scan_type: "Progressive".to_owned(), + } + } + + pub fn movie_file() -> MovieFile { + MovieFile { + relative_path: "Test.mkv".to_owned(), + path: "/nfs/movies/Test.mkv".to_owned(), + date_added: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()), + media_info: Some(media_info()), + } + } + + pub fn collection_movie() -> CollectionMovie { + CollectionMovie { + title: "Test".to_owned().into(), + overview: "Collection blah blah blah".to_owned(), + year: 2023, + runtime: 120, + tmdb_id: 1234, + genres: genres(), + ratings: ratings_list(), + } + } + + pub fn collection() -> Collection { + Collection { + id: 123, + title: "Test Collection".to_owned().into(), + root_folder_path: Some("/nfs/movies".to_owned()), + search_on_add: true, + monitored: true, + minimum_availability: MinimumAvailability::Released, + overview: Some("Collection blah blah blah".to_owned()), + quality_profile_id: 2222, + movies: Some(vec![collection_movie()]), + } + } + + pub fn movie() -> Movie { + Movie { + id: 1, + title: "Test".to_owned().into(), + original_language: language(), + size_on_disk: 3543348019, + status: "Downloaded".to_owned(), + overview: "Blah blah blah".to_owned(), + path: "/nfs/movies".to_owned(), + studio: "21st Century Alex".to_owned(), + genres: genres(), + year: 2023, + monitored: true, + has_file: true, + runtime: 120, + tmdb_id: 1234, + quality_profile_id: 2222, + minimum_availability: MinimumAvailability::Announced, + certification: Some("R".to_owned()), + tags: vec![Number::from(1)], + ratings: ratings_list(), + movie_file: Some(movie_file()), + collection: Some(movie_collection()), + } + } + + pub fn movie_collection() -> MovieCollection { + MovieCollection { + title: Some("Test Collection".to_owned()), + } + } + + pub fn rejections() -> Vec { + vec![ + "Unknown quality profile".to_owned(), + "Release is already mapped".to_owned(), + ] + } + + pub fn quality() -> Quality { + Quality { + name: "HD - 1080p".to_owned(), + } + } + + pub fn quality_wrapper() -> QualityWrapper { + QualityWrapper { quality: quality() } + } + + pub fn release() -> RadarrRelease { + RadarrRelease { + guid: "1234".to_owned(), + protocol: "torrent".to_owned(), + age: 1, + title: HorizontallyScrollableText::from("Test Release"), + indexer: "kickass torrents".to_owned(), + indexer_id: 2, + size: 1234, + rejected: true, + rejections: Some(rejections()), + seeders: Some(Number::from(2)), + leechers: Some(Number::from(1)), + languages: Some(vec![language()]), + quality: quality_wrapper(), + } + } + + pub fn add_movie_search_result() -> AddMovieSearchResult { + AddMovieSearchResult { + tmdb_id: 1234, + title: HorizontallyScrollableText::from("Test"), + original_language: language(), + status: "released".to_owned(), + overview: "New movie blah blah blah".to_owned(), + genres: genres(), + year: 2023, + runtime: 120, + ratings: ratings_list(), + } + } + + pub fn download_record() -> DownloadRecord { + DownloadRecord { + title: "Test Download Title".to_owned(), + status: "downloading".to_owned(), + id: 1, + movie_id: 1, + size: 3543348019, + sizeleft: 1771674009, + output_path: Some(HorizontallyScrollableText::from("/nfs/movies/Test")), + indexer: "kickass torrents".to_owned(), + download_client: "transmission".to_owned(), + } + } + + pub fn root_folder() -> RootFolder { + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + } + } + + pub fn indexer() -> Indexer { + Indexer { + enable_rss: true, + enable_automatic_search: true, + enable_interactive_search: true, + supports_rss: true, + supports_search: true, + protocol: "torrent".to_owned(), + priority: 25, + download_client_id: 0, + name: Some("Test Indexer".to_owned()), + implementation_name: Some("Torznab".to_owned()), + implementation: Some("Torznab".to_owned()), + config_contract: Some("TorznabSettings".to_owned()), + tags: vec![Number::from(1)], + id: 1, + fields: Some(vec![ + IndexerField { + name: Some("baseUrl".to_owned()), + value: Some(json!("https://test.com")), + }, + IndexerField { + name: Some("apiKey".to_owned()), + value: Some(json!("")), + }, + IndexerField { + name: Some("seedCriteria.seedRatio".to_owned()), + value: Some(json!("1.2")), + }, + ]), + } + } + + pub fn indexer_settings() -> IndexerSettings { + IndexerSettings { + rss_sync_interval: 60, + allow_hardcoded_subs: true, + id: 1, + ..IndexerSettings::default() + } + } + + pub fn add_movie_body() -> AddMovieBody { + AddMovieBody { + tmdb_id: 1234, + title: "Test".to_owned(), + root_folder_path: "/nfs2".to_owned(), + minimum_availability: "announced".to_owned(), + monitored: true, + quality_profile_id: 2222, + tags: Vec::new(), + tag_input_string: Some("usenet, testing".into()), + add_options: AddMovieOptions { + monitor: "movieOnly".to_owned(), + search_for_movie: true, + }, + } + } } diff --git a/src/handlers/radarr_handlers/root_folders/mod.rs b/src/handlers/radarr_handlers/root_folders/mod.rs index 6ffc3f0..6e8aa97 100644 --- a/src/handlers/radarr_handlers/root_folders/mod.rs +++ b/src/handlers/radarr_handlers/root_folders/mod.rs @@ -5,7 +5,7 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; -use crate::models::servarr_models::RootFolder; +use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::HorizontallyScrollableText; use crate::network::radarr_network::RadarrEvent; use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; @@ -28,6 +28,32 @@ impl<'a, 'b> RootFoldersHandler<'a, 'b> { self.app.data.radarr_data.root_folders, RootFolder ); + + fn build_add_root_folder_body(&mut self) -> AddRootFolderBody { + let path = self + .app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .text + .clone(); + + self.app.data.radarr_data.edit_root_folder = None; + + AddRootFolderBody { path } + } + + fn extract_root_folder_id(&mut self) -> i64 { + self + .app + .data + .radarr_data + .root_folders + .current_selection() + .id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'a, 'b> { @@ -124,7 +150,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' ActiveRadarrBlock::DeleteRootFolderPrompt => { if self.app.data.radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::DeleteRootFolder(None)); + Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id())); } self.app.pop_navigation_stack(); @@ -140,7 +166,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' .text .is_empty() => { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder(None)); + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder( + self.build_add_root_folder_body(), + )); self.app.data.radarr_data.prompt_confirm = true; self.app.should_ignore_quit_key = false; self.app.pop_navigation_stack(); @@ -192,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm_action = - Some(RadarrEvent::DeleteRootFolder(None)); + Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs index 420204a..984c243 100644 --- a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs +++ b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs @@ -1,14 +1,16 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; + use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::root_folder; use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; - use crate::models::servarr_models::RootFolder; + use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::HorizontallyScrollableText; mod test_handle_home_end { @@ -250,6 +252,9 @@ mod tests { #[test] fn test_add_root_folder_prompt_confirm_submit() { let mut app = App::default(); + let expected_add_root_folder_body = AddRootFolderBody { + path: "Test".to_owned(), + }; app .data .radarr_data @@ -273,7 +278,7 @@ mod tests { assert!(!app.should_ignore_quit_key); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::AddRootFolder(None)) + Some(RadarrEvent::AddRootFolder(expected_add_root_folder_body)) ); assert_eq!( app.get_current_route(), @@ -314,7 +319,7 @@ mod tests { .data .radarr_data .root_folders - .set_items(vec![RootFolder::default()]); + .set_items(vec![root_folder()]); app.data.radarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); @@ -330,7 +335,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteRootFolder(None)) + Some(RadarrEvent::DeleteRootFolder(1)) ); assert_eq!( app.get_current_route(), @@ -604,7 +609,7 @@ mod tests { .data .radarr_data .root_folders - .set_items(vec![RootFolder::default()]); + .set_items(vec![root_folder()]); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); @@ -619,7 +624,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::DeleteRootFolder(None)) + Some(RadarrEvent::DeleteRootFolder(1)) ); assert_eq!( app.get_current_route(), @@ -639,6 +644,46 @@ mod tests { }) } + #[test] + fn test_build_add_root_folder_body() { + let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); + let expected_add_root_folder_body = AddRootFolderBody { + path: "/nfs/test".to_owned(), + }; + + let actual_add_root_folder_body = RootFoldersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::RootFolders, + None, + ) + .build_add_root_folder_body(); + + assert_eq!(actual_add_root_folder_body, expected_add_root_folder_body); + assert!(app.data.radarr_data.edit_root_folder.is_none()); + } + + #[test] + fn test_extract_root_folder_id() { + let mut app = App::default(); + app + .data + .radarr_data + .root_folders + .set_items(vec![root_folder()]); + + let root_folder_id = RootFoldersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::RootFolders, + None, + ) + .extract_root_folder_id(); + + assert_eq!(root_folder_id, 1); + } + #[test] fn test_root_folders_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/radarr_handlers/system/system_details_handler.rs b/src/handlers/radarr_handlers/system/system_details_handler.rs index d6f19dd..4052635 100644 --- a/src/handlers/radarr_handlers/system/system_details_handler.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler.rs @@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::radarr_models::RadarrTaskName; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::stateful_list::StatefulList; use crate::models::Scrollable; @@ -18,6 +19,18 @@ pub(super) struct SystemDetailsHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> SystemDetailsHandler<'a, 'b> { + fn extract_task_name(&self) -> RadarrTaskName { + self + .app + .data + .radarr_data + .tasks + .current_selection() + .task_name + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler<'a, 'b> { fn accepts(active_block: ActiveRadarrBlock) -> bool { SYSTEM_DETAILS_BLOCKS.contains(&active_block) @@ -137,7 +150,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler } ActiveRadarrBlock::SystemTaskStartConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::StartTask(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::StartTask(self.extract_task_name())); } self.app.pop_navigation_stack(); @@ -174,7 +188,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.radarr_data.prompt_confirm = true; - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::StartTask(None)); + self.app.data.radarr_data.prompt_confirm_action = + Some(RadarrEvent::StartTask(self.extract_task_name())); self.app.pop_navigation_stack(); } } diff --git a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs index 34c97da..e12a610 100644 --- a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; @@ -8,7 +8,7 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::RadarrTask; + use crate::models::radarr_models::{RadarrTask, RadarrTaskName}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS, }; @@ -16,6 +16,7 @@ mod tests { use crate::models::{HorizontallyScrollableText, ScrollableText}; mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; use rstest::rstest; use crate::models::{HorizontallyScrollableText, ScrollableText}; @@ -236,6 +237,7 @@ mod tests { mod test_handle_home_end { use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; + use pretty_assertions::assert_eq; use super::*; @@ -676,6 +678,10 @@ mod tests { let mut app = App::default(); app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned()); app.data.radarr_data.prompt_confirm = true; + app.data.radarr_data.tasks.set_items(vec![RadarrTask { + task_name: RadarrTaskName::default(), + ..RadarrTask::default() + }]); app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into()); @@ -690,7 +696,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::StartTask(None)) + Some(RadarrEvent::StartTask(RadarrTaskName::default())) ); assert_eq!( app.get_current_route(), @@ -831,6 +837,7 @@ mod tests { } mod test_handle_key_char { + use pretty_assertions::assert_eq; use rstest::rstest; use crate::network::radarr_network::RadarrEvent; @@ -894,6 +901,10 @@ mod tests { fn test_system_tasks_start_task_prompt_confirm() { let mut app = App::default(); app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app.data.radarr_data.tasks.set_items(vec![RadarrTask { + task_name: RadarrTaskName::default(), + ..RadarrTask::default() + }]); app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into()); @@ -908,7 +919,7 @@ mod tests { assert!(app.data.radarr_data.prompt_confirm); assert_eq!( app.data.radarr_data.prompt_confirm_action, - Some(RadarrEvent::StartTask(None)) + Some(RadarrEvent::StartTask(RadarrTaskName::default())) ); assert_eq!( app.get_current_route(), @@ -928,6 +939,25 @@ mod tests { }) } + #[test] + fn test_extract_task_name() { + let mut app = App::default(); + app.data.radarr_data.tasks.set_items(vec![RadarrTask { + task_name: RadarrTaskName::default(), + ..RadarrTask::default() + }]); + + let task_name = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveRadarrBlock::SystemTasks, + None, + ) + .extract_task_name(); + + assert_eq!(task_name, RadarrTaskName::default()); + } + #[test] fn test_system_details_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/blocklist/blocklist_handler_tests.rs b/src/handlers/sonarr_handlers/blocklist/blocklist_handler_tests.rs index f8bcd9a..7e1db51 100644 --- a/src/handlers/sonarr_handlers/blocklist/blocklist_handler_tests.rs +++ b/src/handlers/sonarr_handlers/blocklist/blocklist_handler_tests.rs @@ -161,7 +161,7 @@ mod tests { #[case( ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::DeleteBlocklistItemPrompt, - SonarrEvent::DeleteBlocklistItem(None) + SonarrEvent::DeleteBlocklistItem(3) )] #[case( ActiveSonarrBlock::Blocklist, @@ -361,7 +361,7 @@ mod tests { #[case( ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::DeleteBlocklistItemPrompt, - SonarrEvent::DeleteBlocklistItem(None) + SonarrEvent::DeleteBlocklistItem(3) )] #[case( ActiveSonarrBlock::Blocklist, @@ -513,6 +513,22 @@ mod tests { }) } + #[test] + fn test_extract_blocklist_item_id() { + let mut app = App::default(); + app.data.sonarr_data.blocklist.set_items(blocklist_vec()); + + let blocklist_item_id = BlocklistHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Blocklist, + None, + ) + .extract_blocklist_item_id(); + + assert_eq!(blocklist_item_id, 3); + } + #[test] fn test_blocklist_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/blocklist/mod.rs b/src/handlers/sonarr_handlers/blocklist/mod.rs index 9843558..e36b061 100644 --- a/src/handlers/sonarr_handlers/blocklist/mod.rs +++ b/src/handlers/sonarr_handlers/blocklist/mod.rs @@ -28,6 +28,10 @@ impl<'a, 'b> BlocklistHandler<'a, 'b> { self.app.data.sonarr_data.blocklist, BlocklistItem ); + + fn extract_blocklist_item_id(&self) -> i64 { + self.app.data.sonarr_data.blocklist.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a, 'b> { @@ -98,8 +102,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a, match self.active_sonarr_block { ActiveSonarrBlock::DeleteBlocklistItemPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteBlocklistItem(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem( + self.extract_blocklist_item_id(), + )); } self.app.pop_navigation_stack(); @@ -151,8 +156,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a, ActiveSonarrBlock::DeleteBlocklistItemPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteBlocklistItem(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem( + self.extract_blocklist_item_id(), + )); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/downloads/downloads_handler_tests.rs b/src/handlers/sonarr_handlers/downloads/downloads_handler_tests.rs index dd2ac0f..7c39053 100644 --- a/src/handlers/sonarr_handlers/downloads/downloads_handler_tests.rs +++ b/src/handlers/sonarr_handlers/downloads/downloads_handler_tests.rs @@ -1,11 +1,13 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::downloads::DownloadsHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::download_record; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS}; use crate::models::sonarr_models::DownloadRecord; @@ -138,7 +140,7 @@ mod tests { #[case( ActiveSonarrBlock::Downloads, ActiveSonarrBlock::DeleteDownloadPrompt, - SonarrEvent::DeleteDownload(None) + SonarrEvent::DeleteDownload(1) )] #[case( ActiveSonarrBlock::Downloads, @@ -155,7 +157,7 @@ mod tests { .data .sonarr_data .downloads - .set_items(vec![DownloadRecord::default()]); + .set_items(vec![download_record()]); app.data.sonarr_data.prompt_confirm = true; app.push_navigation_stack(base_route.into()); app.push_navigation_stack(prompt_block.into()); @@ -338,7 +340,7 @@ mod tests { #[case( ActiveSonarrBlock::Downloads, ActiveSonarrBlock::DeleteDownloadPrompt, - SonarrEvent::DeleteDownload(None) + SonarrEvent::DeleteDownload(1) )] #[case( ActiveSonarrBlock::Downloads, @@ -355,7 +357,7 @@ mod tests { .data .sonarr_data .downloads - .set_items(vec![DownloadRecord::default()]); + .set_items(vec![download_record()]); app.push_navigation_stack(base_route.into()); app.push_navigation_stack(prompt_block.into()); @@ -387,6 +389,26 @@ mod tests { }) } + #[test] + fn test_extract_download_id() { + let mut app = App::default(); + app + .data + .sonarr_data + .downloads + .set_items(vec![download_record()]); + + let download_id = DownloadsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Downloads, + None, + ) + .extract_download_id(); + + assert_eq!(download_id, 1); + } + #[test] fn test_downloads_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/downloads/mod.rs b/src/handlers/sonarr_handlers/downloads/mod.rs index 6b1fe51..8edf0d0 100644 --- a/src/handlers/sonarr_handlers/downloads/mod.rs +++ b/src/handlers/sonarr_handlers/downloads/mod.rs @@ -27,6 +27,10 @@ impl<'a, 'b> DownloadsHandler<'a, 'b> { self.app.data.sonarr_data.downloads, DownloadRecord ); + + fn extract_download_id(&self) -> i64 { + self.app.data.sonarr_data.downloads.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a, 'b> { @@ -95,7 +99,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a, match self.active_sonarr_block { ActiveSonarrBlock::DeleteDownloadPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteDownload(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteDownload(self.extract_download_id())); } self.app.pop_navigation_stack(); @@ -138,7 +143,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a, ActiveSonarrBlock::DeleteDownloadPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteDownload(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteDownload(self.extract_download_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs index ca41e51..7e5cf98 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler.rs @@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; +use crate::models::servarr_models::EditIndexerParams; use crate::network::sonarr_network::SonarrEvent; use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; @@ -17,6 +19,60 @@ pub(super) struct EditIndexerHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> EditIndexerHandler<'a, 'b> { + fn build_edit_indexer_params(&mut self) -> EditIndexerParams { + let indexer_id = self.app.data.sonarr_data.indexers.current_selection().id; + let tags = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + let params = { + let EditIndexerModal { + name, + enable_rss, + enable_automatic_search, + enable_interactive_search, + url, + api_key, + seed_ratio, + priority, + .. + } = self + .app + .data + .sonarr_data + .edit_indexer_modal + .as_ref() + .unwrap(); + + EditIndexerParams { + indexer_id, + name: Some(name.text.clone()), + enable_rss: Some(enable_rss.unwrap_or_default()), + enable_automatic_search: Some(enable_automatic_search.unwrap_or_default()), + enable_interactive_search: Some(enable_interactive_search.unwrap_or_default()), + url: Some(url.text.clone()), + api_key: Some(api_key.text.clone()), + seed_ratio: Some(seed_ratio.text.clone()), + tags: None, + tag_input_string: Some(tags), + priority: Some(*priority), + clear_tags: false, + } + }; + + self.app.data.sonarr_data.edit_indexer_modal = None; + + params + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'a, 'b> { fn accepts(active_block: ActiveSonarrBlock) -> bool { EDIT_INDEXER_BLOCKS.contains(&active_block) @@ -297,12 +353,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<' let selected_block = self.app.data.sonarr_data.selected_block.get_active_block(); match selected_block { ActiveSonarrBlock::EditIndexerConfirmPrompt => { - let sonarr_data = &mut self.app.data.sonarr_data; - if sonarr_data.prompt_confirm { - sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditIndexer(None)); + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::EditIndexer(self.build_edit_indexer_params())); self.app.should_refresh = true; } else { - sonarr_data.edit_indexer_modal = None; + self.app.data.sonarr_data.edit_indexer_modal = None; } self.app.pop_navigation_stack(); @@ -464,7 +520,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<' && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditIndexer(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::EditIndexer(self.build_edit_indexer_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs index 1008ec2..100658e 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_handler_tests.rs @@ -4,9 +4,12 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::indexer; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; + use crate::models::servarr_models::EditIndexerParams; + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; mod test_handle_scroll_up_and_down { @@ -896,7 +899,32 @@ mod tests { .sonarr_data .selected_block .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); - app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + 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(), + priority: 0, + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; app.data.sonarr_data.prompt_confirm = true; EditIndexerHandler::with( @@ -908,11 +936,11 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); - assert!(app.data.sonarr_data.edit_indexer_modal.is_some()); + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); assert!(app.should_refresh); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditIndexer(None)) + Some(SonarrEvent::EditIndexer(expected_edit_indexer_params)) ); } @@ -1408,7 +1436,7 @@ mod tests { use crate::models::servarr_data::sonarr::sonarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS; use crate::models::BlockSelectionState; use crate::network::sonarr_network::SonarrEvent; - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use super::*; @@ -1709,7 +1737,32 @@ mod tests { .sonarr_data .selected_block .set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); - app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); + 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(), + priority: 0, + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; EditIndexerHandler::with( DEFAULT_KEYBINDINGS.confirm.key, @@ -1720,11 +1773,11 @@ mod tests { .handle(); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); - assert!(app.data.sonarr_data.edit_indexer_modal.is_some()); + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); assert!(app.should_refresh); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditIndexer(None)) + Some(SonarrEvent::EditIndexer(expected_edit_indexer_params)) ); } } @@ -1740,6 +1793,48 @@ mod tests { }) } + #[test] + fn test_build_edit_indexer_params() { + let mut app = App::default(); + 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(), + priority: 0, + }; + app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; + + let params = EditIndexerHandler::with( + DEFAULT_KEYBINDINGS.confirm.key, + &mut app, + ActiveSonarrBlock::EditIndexerPrompt, + None, + ) + .build_edit_indexer_params(); + + assert_eq!(params, expected_edit_indexer_params); + assert!(app.data.sonarr_data.edit_indexer_modal.is_none()); + } + #[test] fn test_edit_indexer_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs index 94afa1d..31ea228 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -6,6 +6,7 @@ use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, }; +use crate::models::sonarr_models::IndexerSettings; use crate::network::sonarr_network::SonarrEvent; #[cfg(test)] @@ -19,6 +20,23 @@ pub(super) struct IndexerSettingsHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> IndexerSettingsHandler<'a, 'b> { + fn build_edit_indexer_settings_params(&mut self) -> IndexerSettings { + let indexer_settings = self + .app + .data + .sonarr_data + .indexer_settings + .as_ref() + .unwrap() + .clone(); + + self.app.data.sonarr_data.indexer_settings = None; + + indexer_settings + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandler<'a, 'b> { fn accepts(active_block: ActiveSonarrBlock) -> bool { INDEXER_SETTINGS_BLOCKS.contains(&active_block) @@ -119,12 +137,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl ActiveSonarrBlock::AllIndexerSettingsPrompt => { match self.app.data.sonarr_data.selected_block.get_active_block() { ActiveSonarrBlock::IndexerSettingsConfirmPrompt => { - let sonarr_data = &mut self.app.data.sonarr_data; - if sonarr_data.prompt_confirm { - sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(None)); + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_params()), + ); self.app.should_refresh = true; } else { - sonarr_data.indexer_settings = None; + self.app.data.sonarr_data.indexer_settings = None; } self.app.pop_navigation_stack(); @@ -172,8 +191,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::EditAllIndexerSettings(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings( + self.build_edit_indexer_settings_params(), + )); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs index 8301125..ef33b99 100644 --- a/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs +++ b/src/handlers/sonarr_handlers/indexers/edit_indexer_settings_handler_tests.rs @@ -1,11 +1,13 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::indexer_settings; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, @@ -285,7 +287,7 @@ mod tests { .sonarr_data .selected_block .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); - app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.indexer_settings = Some(indexer_settings()); app.data.sonarr_data.prompt_confirm = true; IndexerSettingsHandler::with( @@ -299,9 +301,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditAllIndexerSettings(None)) + Some(SonarrEvent::EditAllIndexerSettings(indexer_settings())) ); - assert!(app.data.sonarr_data.indexer_settings.is_some()); + assert!(app.data.sonarr_data.indexer_settings.is_none()); assert!(app.should_refresh); } @@ -468,11 +470,11 @@ mod tests { mod test_handle_key_char { use crate::{ models::{ - servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, - sonarr_models::IndexerSettings, BlockSelectionState, + servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, BlockSelectionState, }, network::sonarr_network::SonarrEvent, }; + use pretty_assertions::assert_eq; use super::*; @@ -488,7 +490,7 @@ mod tests { .sonarr_data .selected_block .set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); - app.data.sonarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.sonarr_data.indexer_settings = Some(indexer_settings()); IndexerSettingsHandler::with( DEFAULT_KEYBINDINGS.confirm.key, @@ -501,9 +503,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditAllIndexerSettings(None)) + Some(SonarrEvent::EditAllIndexerSettings(indexer_settings())) ); - assert!(app.data.sonarr_data.indexer_settings.is_some()); + assert!(app.data.sonarr_data.indexer_settings.is_none()); assert!(app.should_refresh); } } @@ -519,6 +521,23 @@ mod tests { }) } + #[test] + fn test_build_edit_indexer_settings_params() { + let mut app = App::default(); + app.data.sonarr_data.indexer_settings = Some(indexer_settings()); + + let actual_indexer_settings = IndexerSettingsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AllIndexerSettingsPrompt, + None, + ) + .build_edit_indexer_settings_params(); + + assert_eq!(actual_indexer_settings, indexer_settings()); + assert!(app.data.sonarr_data.indexer_settings.is_none()); + } + #[test] fn test_edit_indexer_settings_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs index 9a4173d..d097745 100644 --- a/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/sonarr_handlers/indexers/indexers_handler_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use rstest::rstest; use strum::IntoEnumIterator; @@ -7,6 +8,7 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::indexers::IndexersHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::indexer; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, @@ -245,11 +247,7 @@ mod tests { #[test] fn test_delete_indexer_prompt_confirm_submit() { let mut app = App::default(); - app - .data - .sonarr_data - .indexers - .set_items(vec![Indexer::default()]); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); app.data.sonarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); @@ -265,7 +263,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteIndexer(None)) + Some(SonarrEvent::DeleteIndexer(1)) ); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); } @@ -556,11 +554,7 @@ mod tests { #[test] fn test_delete_indexer_prompt_confirm() { let mut app = App::default(); - app - .data - .sonarr_data - .indexers - .set_items(vec![Indexer::default()]); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); @@ -575,7 +569,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteIndexer(None)) + Some(SonarrEvent::DeleteIndexer(1)) ); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); } @@ -649,6 +643,22 @@ mod tests { }) } + #[test] + fn test_extract_indexer_id() { + let mut app = App::default(); + app.data.sonarr_data.indexers.set_items(vec![indexer()]); + + let indexer_id = IndexersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::Indexers, + None, + ) + .extract_indexer_id(); + + assert_eq!(indexer_id, 1); + } + #[test] fn test_indexers_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/indexers/mod.rs b/src/handlers/sonarr_handlers/indexers/mod.rs index 72e6908..12375ab 100644 --- a/src/handlers/sonarr_handlers/indexers/mod.rs +++ b/src/handlers/sonarr_handlers/indexers/mod.rs @@ -33,6 +33,10 @@ pub(super) struct IndexersHandler<'a, 'b> { impl<'a, 'b> IndexersHandler<'a, 'b> { handle_table_events!(self, indexers, self.app.data.sonarr_data.indexers, Indexer); + + fn extract_indexer_id(&self) -> i64 { + self.app.data.sonarr_data.indexers.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, 'b> { @@ -115,9 +119,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, fn handle_submit(&mut self) { match self.active_sonarr_block { ActiveSonarrBlock::DeleteIndexerPrompt => { - let sonarr_data = &mut self.app.data.sonarr_data; - if sonarr_data.prompt_confirm { - sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteIndexer(None)); + if self.app.data.sonarr_data.prompt_confirm { + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id())); } self.app.pop_navigation_stack(); @@ -189,7 +193,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a, ActiveSonarrBlock::DeleteIndexerPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteIndexer(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/library/add_series_handler.rs b/src/handlers/sonarr_handlers/library/add_series_handler.rs index 80d3c35..a98c2ba 100644 --- a/src/handlers/sonarr_handlers/library/add_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/add_series_handler.rs @@ -1,10 +1,12 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::modals::AddSeriesModal; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, }; -use crate::models::sonarr_models::AddSeriesSearchResult; +use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult}; +use crate::models::stateful_table::StatefulTable; use crate::models::{BlockSelectionState, Scrollable}; use crate::network::sonarr_network::SonarrEvent; use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; @@ -30,9 +32,90 @@ impl<'a, 'b> AddSeriesHandler<'a, 'b> { .sonarr_data .add_searched_series .as_mut() - .unwrap(), + .unwrap_or(&mut StatefulTable::default()), AddSeriesSearchResult ); + + fn build_add_series_body(&mut self) -> AddSeriesBody { + let tags = self + .app + .data + .sonarr_data + .add_series_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + let AddSeriesModal { + root_folder_list, + monitor_list, + quality_profile_list, + language_profile_list, + series_type_list, + use_season_folder, + .. + } = self.app.data.sonarr_data.add_series_modal.as_ref().unwrap(); + let season_folder = *use_season_folder; + let (tvdb_id, title) = { + let AddSeriesSearchResult { tvdb_id, title, .. } = self + .app + .data + .sonarr_data + .add_searched_series + .as_ref() + .unwrap() + .current_selection() + .clone(); + (tvdb_id, title.text) + }; + let quality_profile = quality_profile_list.current_selection(); + let quality_profile_id = *self + .app + .data + .sonarr_data + .quality_profile_map + .iter() + .filter(|(_, value)| *value == quality_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + let language_profile = language_profile_list.current_selection(); + let language_profile_id = *self + .app + .data + .sonarr_data + .language_profiles_map + .iter() + .filter(|(_, value)| *value == language_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + + let path = root_folder_list.current_selection().path.clone(); + let monitor = monitor_list.current_selection().to_string(); + let series_type = series_type_list.current_selection().to_string(); + + self.app.data.sonarr_data.add_series_modal = None; + + AddSeriesBody { + tvdb_id, + title, + monitored: true, + root_folder_path: path, + quality_profile_id, + language_profile_id, + series_type, + season_folder, + tags: Vec::new(), + tag_input_string: Some(tags), + add_options: AddSeriesOptions { + monitor, + search_for_cutoff_unmet_episodes: true, + search_for_missing_episodes: true, + }, + } + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, 'b> { @@ -403,7 +486,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, match self.app.data.sonarr_data.selected_block.get_active_block() { ActiveSonarrBlock::AddSeriesConfirmPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::AddSeries(self.build_add_series_body())); } self.app.pop_navigation_stack(); @@ -534,7 +618,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a, && key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::AddSeries(self.build_add_series_body())); self.app.pop_navigation_stack(); } } diff --git a/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs b/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs index d00fdf1..c38ac00 100644 --- a/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/add_series_handler_tests.rs @@ -1,16 +1,22 @@ #[cfg(test)] mod tests { - use pretty_assertions::assert_str_eq; + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::library::add_series_handler::AddSeriesHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::add_series_search_result; use crate::handlers::KeyEventHandler; + use crate::models::servarr_data::sonarr::modals::AddSeriesModal; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ADD_SERIES_BLOCKS}; use crate::models::servarr_models::RootFolder; - use crate::models::sonarr_models::{AddSeriesSearchResult, SeriesMonitor, SeriesType}; + use crate::models::sonarr_models::{ + AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, SeriesMonitor, SeriesType, + }; + use crate::models::stateful_table::StatefulTable; use crate::models::HorizontallyScrollableText; mod test_handle_scroll_up_and_down { @@ -366,6 +372,7 @@ mod tests { } mod test_handle_home_end { + use pretty_assertions::assert_eq; use std::sync::atomic::Ordering; use strum::IntoEnumIterator; @@ -763,6 +770,7 @@ mod tests { } mod test_handle_left_right_action { + use pretty_assertions::assert_eq; use std::sync::atomic::Ordering; use crate::models::servarr_data::sonarr::modals::AddSeriesModal; @@ -1109,10 +1117,67 @@ mod tests { #[test] fn test_add_series_confirm_prompt_prompt_confirmation_submit() { let mut app = App::default(); - app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.data.sonarr_data.prompt_confirm = true; + let mut add_series_modal = AddSeriesModal { + use_season_folder: true, + tags: "usenet, testing".into(), + ..AddSeriesModal::default() + }; + add_series_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_series_modal.root_folder_list.state.select(Some(1)); + add_series_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_series_modal + .language_profile_list + .set_items(vec!["English".to_owned()]); + add_series_modal + .monitor_list + .set_items(Vec::from_iter(SeriesMonitor::iter())); + add_series_modal + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.add_series_modal = Some(add_series_modal); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = BiMap::from_iter([(2222, "English".to_owned())]); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![add_series_search_result()]); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + let expected_add_series_body = AddSeriesBody { + tvdb_id: 1234, + title: "Test".to_owned(), + monitored: true, + root_folder_path: "/nfs2".to_owned(), + quality_profile_id: 2222, + language_profile_id: 2222, + series_type: "standard".to_owned(), + season_folder: true, + tags: Vec::default(), + tag_input_string: Some("usenet, testing".to_owned()), + add_options: AddSeriesOptions { + monitor: "all".to_owned(), + search_for_cutoff_unmet_episodes: true, + search_for_missing_episodes: true, + }, + }; app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); app .data @@ -1131,9 +1196,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::AddSeries(None)) + Some(SonarrEvent::AddSeries(expected_add_series_body)) ); - assert!(app.data.sonarr_data.add_series_modal.is_some()); + assert!(app.data.sonarr_data.add_series_modal.is_none()); } #[rstest] @@ -1440,6 +1505,7 @@ mod tests { }, network::sonarr_network::SonarrEvent, }; + use pretty_assertions::assert_eq; #[test] fn test_add_series_search_input_backspace() { @@ -1553,7 +1619,64 @@ mod tests { #[test] fn test_add_series_confirm_prompt_prompt_confirmation_confirm() { let mut app = App::default(); - app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); + let mut add_series_modal = AddSeriesModal { + use_season_folder: true, + tags: "usenet, testing".into(), + ..AddSeriesModal::default() + }; + add_series_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_series_modal.root_folder_list.state.select(Some(1)); + add_series_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_series_modal + .language_profile_list + .set_items(vec!["English".to_owned()]); + add_series_modal + .monitor_list + .set_items(Vec::from_iter(SeriesMonitor::iter())); + add_series_modal + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.add_series_modal = Some(add_series_modal); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = BiMap::from_iter([(2222, "English".to_owned())]); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![add_series_search_result()]); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + let expected_add_series_body = AddSeriesBody { + tvdb_id: 1234, + title: "Test".to_owned(), + monitored: true, + root_folder_path: "/nfs2".to_owned(), + quality_profile_id: 2222, + language_profile_id: 2222, + series_type: "standard".to_owned(), + season_folder: true, + tags: Vec::default(), + tag_input_string: Some("usenet, testing".to_owned()), + add_options: AddSeriesOptions { + monitor: "all".to_owned(), + search_for_cutoff_unmet_episodes: true, + search_for_missing_episodes: true, + }, + }; app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); @@ -1574,9 +1697,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::AddSeries(None)) + Some(SonarrEvent::AddSeries(expected_add_series_body)) ); - assert!(app.data.sonarr_data.add_series_modal.is_some()); + assert!(app.data.sonarr_data.add_series_modal.is_none()); } } @@ -1591,6 +1714,93 @@ mod tests { }); } + #[test] + fn test_add_series_search_no_panic_on_none_search_result() { + let mut app = App::default(); + app.data.sonarr_data.add_series_search = None; + + AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AddSeriesSearchResults, + None, + ) + .handle(); + } + + #[test] + fn test_build_add_series_body() { + let mut app = App::default(); + let mut add_series_modal = AddSeriesModal { + use_season_folder: true, + tags: "usenet, testing".into(), + ..AddSeriesModal::default() + }; + add_series_modal.root_folder_list.set_items(vec![ + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + }, + RootFolder { + id: 2, + path: "/nfs2".to_owned(), + accessible: true, + free_space: 21990232555520, + unmapped_folders: None, + }, + ]); + add_series_modal.root_folder_list.state.select(Some(1)); + add_series_modal + .quality_profile_list + .set_items(vec!["HD - 1080p".to_owned()]); + add_series_modal + .language_profile_list + .set_items(vec!["English".to_owned()]); + add_series_modal + .monitor_list + .set_items(Vec::from_iter(SeriesMonitor::iter())); + add_series_modal + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.add_series_modal = Some(add_series_modal); + app.data.sonarr_data.quality_profile_map = BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = BiMap::from_iter([(2222, "English".to_owned())]); + let mut add_searched_series = StatefulTable::default(); + add_searched_series.set_items(vec![add_series_search_result()]); + app.data.sonarr_data.add_searched_series = Some(add_searched_series); + let expected_add_series_body = AddSeriesBody { + tvdb_id: 1234, + title: "Test".to_owned(), + monitored: true, + root_folder_path: "/nfs2".to_owned(), + quality_profile_id: 2222, + language_profile_id: 2222, + series_type: "standard".to_owned(), + season_folder: true, + tags: Vec::default(), + tag_input_string: Some("usenet, testing".to_owned()), + add_options: AddSeriesOptions { + monitor: "all".to_owned(), + search_for_cutoff_unmet_episodes: true, + search_for_missing_episodes: true, + }, + }; + + let add_series_body = AddSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AddSeriesPrompt, + None, + ) + .build_add_series_body(); + + assert_eq!(add_series_body, expected_add_series_body); + assert!(app.data.sonarr_data.add_series_modal.is_none()); + } + #[test] fn test_add_series_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/library/delete_series_handler.rs b/src/handlers/sonarr_handlers/library/delete_series_handler.rs index a9fbeff..51326c2 100644 --- a/src/handlers/sonarr_handlers/library/delete_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/delete_series_handler.rs @@ -1,9 +1,10 @@ +use crate::models::sonarr_models::DeleteSeriesParams; +use crate::network::sonarr_network::SonarrEvent; use crate::{ app::{key_binding::DEFAULT_KEYBINDINGS, App}, event::Key, handlers::{handle_prompt_toggle, KeyEventHandler}, models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}, - network::sonarr_network::SonarrEvent, }; #[cfg(test)] @@ -17,6 +18,21 @@ pub(super) struct DeleteSeriesHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> DeleteSeriesHandler<'a, 'b> { + fn build_delete_series_params(&mut self) -> DeleteSeriesParams { + let id = self.app.data.sonarr_data.series.current_selection().id; + let delete_series_files = self.app.data.sonarr_data.delete_series_files; + let add_list_exclusion = self.app.data.sonarr_data.add_list_exclusion; + self.app.data.sonarr_data.reset_delete_series_preferences(); + + DeleteSeriesParams { + id, + delete_series_files, + add_list_exclusion, + } + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<'a, 'b> { fn accepts(active_block: ActiveSonarrBlock) -> bool { DELETE_SERIES_BLOCKS.contains(&active_block) @@ -73,7 +89,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler< match self.app.data.sonarr_data.selected_block.get_active_block() { ActiveSonarrBlock::DeleteSeriesConfirmPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteSeries(self.build_delete_series_params())); self.app.should_refresh = true; } else { self.app.data.sonarr_data.reset_delete_series_preferences(); @@ -109,7 +126,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler< && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::DeleteSeries(self.build_delete_series_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/sonarr_handlers/library/delete_series_handler_tests.rs b/src/handlers/sonarr_handlers/library/delete_series_handler_tests.rs index 13469da..cdaaae4 100644 --- a/src/handlers/sonarr_handlers/library/delete_series_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/delete_series_handler_tests.rs @@ -1,13 +1,16 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::library::delete_series_handler::DeleteSeriesHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::series; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}; + use crate::models::sonarr_models::DeleteSeriesParams; mod test_handle_scroll_up_and_down { use pretty_assertions::assert_eq; @@ -132,6 +135,12 @@ mod tests { app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.delete_series_files = true; app.data.sonarr_data.add_list_exclusion = true; + app.data.sonarr_data.series.set_items(vec![series()]); + let expected_delete_series_params = DeleteSeriesParams { + id: 1, + delete_series_files: true, + add_list_exclusion: true, + }; app.data.sonarr_data.selected_block = BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS); app @@ -151,12 +160,12 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteSeries(None)) + Some(SonarrEvent::DeleteSeries(expected_delete_series_params)) ); assert!(app.should_refresh); assert!(app.data.sonarr_data.prompt_confirm); - assert!(app.data.sonarr_data.delete_series_files); - assert!(app.data.sonarr_data.add_list_exclusion); + assert!(!app.data.sonarr_data.delete_series_files); + assert!(!app.data.sonarr_data.add_list_exclusion); } #[test] @@ -222,6 +231,7 @@ mod tests { mod test_handle_esc { use super::*; + use pretty_assertions::assert_eq; use rstest::rstest; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; @@ -258,6 +268,7 @@ mod tests { }, network::sonarr_network::SonarrEvent, }; + use pretty_assertions::assert_eq; use super::*; @@ -268,6 +279,12 @@ mod tests { app.push_navigation_stack(ActiveSonarrBlock::DeleteSeriesPrompt.into()); app.data.sonarr_data.delete_series_files = true; app.data.sonarr_data.add_list_exclusion = true; + app.data.sonarr_data.series.set_items(vec![series()]); + let expected_delete_series_params = DeleteSeriesParams { + id: 1, + delete_series_files: true, + add_list_exclusion: true, + }; app.data.sonarr_data.selected_block = BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS); app @@ -287,12 +304,12 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteSeries(None)) + Some(SonarrEvent::DeleteSeries(expected_delete_series_params)) ); assert!(app.should_refresh); assert!(app.data.sonarr_data.prompt_confirm); - assert!(app.data.sonarr_data.delete_series_files); - assert!(app.data.sonarr_data.add_list_exclusion); + assert!(!app.data.sonarr_data.delete_series_files); + assert!(!app.data.sonarr_data.add_list_exclusion); } } @@ -307,6 +324,31 @@ mod tests { }); } + #[test] + fn test_build_delete_series_params() { + let mut app = App::default(); + app.data.sonarr_data.series.set_items(vec![series()]); + app.data.sonarr_data.delete_series_files = true; + app.data.sonarr_data.add_list_exclusion = true; + let expected_delete_series_params = DeleteSeriesParams { + id: 1, + delete_series_files: true, + add_list_exclusion: true, + }; + + let delete_series_params = DeleteSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::DeleteSeriesPrompt, + None, + ) + .build_delete_series_params(); + + assert_eq!(delete_series_params, expected_delete_series_params); + assert!(!app.data.sonarr_data.delete_series_files); + assert!(!app.data.sonarr_data.add_list_exclusion); + } + #[test] fn test_delete_series_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/library/edit_series_handler.rs b/src/handlers/sonarr_handlers/library/edit_series_handler.rs index dbab8c8..4583b02 100644 --- a/src/handlers/sonarr_handlers/library/edit_series_handler.rs +++ b/src/handlers/sonarr_handlers/library/edit_series_handler.rs @@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; +use crate::models::servarr_data::sonarr::modals::EditSeriesModal; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS}; +use crate::models::sonarr_models::EditSeriesParams; use crate::models::Scrollable; use crate::network::sonarr_network::SonarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; @@ -18,6 +20,78 @@ pub(super) struct EditSeriesHandler<'a, 'b> { context: Option, } +impl<'a, 'b> EditSeriesHandler<'a, 'b> { + fn build_edit_series_params(&mut self) -> EditSeriesParams { + let series_id = self.app.data.sonarr_data.series.current_selection().id; + let tags = self + .app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap() + .tags + .text + .clone(); + + let params = { + let EditSeriesModal { + monitored, + use_season_folders, + path, + series_type_list, + quality_profile_list, + language_profile_list, + .. + } = self + .app + .data + .sonarr_data + .edit_series_modal + .as_ref() + .unwrap(); + let quality_profile = quality_profile_list.current_selection(); + let quality_profile_id = *self + .app + .data + .sonarr_data + .quality_profile_map + .iter() + .filter(|(_, value)| *value == quality_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + let language_profile = language_profile_list.current_selection(); + let language_profile_id = *self + .app + .data + .sonarr_data + .language_profiles_map + .iter() + .filter(|(_, value)| *value == language_profile) + .map(|(key, _)| key) + .next() + .unwrap(); + + EditSeriesParams { + series_id, + monitored: Some(monitored.unwrap_or_default()), + use_season_folders: Some(use_season_folders.unwrap_or_default()), + series_type: Some(*series_type_list.current_selection()), + quality_profile_id: Some(quality_profile_id), + language_profile_id: Some(language_profile_id), + root_folder_path: Some(path.text.clone()), + tag_input_string: Some(tags), + ..EditSeriesParams::default() + } + }; + + self.app.data.sonarr_data.edit_series_modal = None; + + params + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a, 'b> { fn accepts(active_block: ActiveSonarrBlock) -> bool { EDIT_SERIES_BLOCKS.contains(&active_block) @@ -258,7 +332,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a match self.app.data.sonarr_data.selected_block.get_active_block() { ActiveSonarrBlock::EditSeriesConfirmPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::EditSeries(self.build_edit_series_params())); self.app.should_refresh = true; } @@ -392,7 +467,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a && key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditSeries(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::EditSeries(self.build_edit_series_params())); self.app.should_refresh = true; self.app.pop_navigation_stack(); diff --git a/src/handlers/sonarr_handlers/library/edit_series_handler_tests.rs b/src/handlers/sonarr_handlers/library/edit_series_handler_tests.rs index 6b73c63..fdc4828 100644 --- a/src/handlers/sonarr_handlers/library/edit_series_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/edit_series_handler_tests.rs @@ -1,16 +1,18 @@ #[cfg(test)] mod tests { - use pretty_assertions::assert_str_eq; + use bimap::BiMap; + use pretty_assertions::{assert_eq, assert_str_eq}; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::library::edit_series_handler::EditSeriesHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::series; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::modals::EditSeriesModal; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS}; - use crate::models::sonarr_models::SeriesType; + use crate::models::sonarr_models::{EditSeriesParams, Series, SeriesType}; mod test_handle_scroll_up_and_down { use pretty_assertions::assert_eq; @@ -243,6 +245,7 @@ mod tests { } mod test_handle_home_end { + use pretty_assertions::assert_eq; use std::sync::atomic::Ordering; use strum::IntoEnumIterator; @@ -531,6 +534,7 @@ mod tests { } mod test_handle_left_right_action { + use pretty_assertions::assert_eq; use std::sync::atomic::Ordering; use crate::models::servarr_data::sonarr::modals::EditSeriesModal; @@ -770,7 +774,43 @@ mod tests { #[test] fn test_edit_series_confirm_prompt_prompt_confirmation_submit() { let mut app = App::default(); - app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); + let mut edit_series = EditSeriesModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + use_season_folders: Some(false), + ..EditSeriesModal::default() + }; + edit_series + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_series + .language_profile_list + .set_items(vec!["Any".to_owned(), "English".to_owned()]); + edit_series + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.edit_series_modal = Some(edit_series); + app.data.sonarr_data.series.set_items(vec![Series { + monitored: false, + season_folder: false, + ..series() + }]); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "English".to_owned())]); + let expected_edit_series_params = EditSeriesParams { + series_id: 1, + monitored: Some(false), + use_season_folders: Some(false), + series_type: Some(SeriesType::Standard), + quality_profile_id: Some(1111), + language_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".to_owned()), + ..EditSeriesParams::default() + }; app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into()); app.data.sonarr_data.prompt_confirm = true; @@ -792,9 +832,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditSeries(None)) + Some(SonarrEvent::EditSeries(expected_edit_series_params)) ); - assert!(app.data.sonarr_data.edit_series_modal.is_some()); + assert!(app.data.sonarr_data.edit_series_modal.is_none()); assert!(app.should_refresh); } @@ -1135,6 +1175,7 @@ mod tests { }, network::sonarr_network::SonarrEvent, }; + use pretty_assertions::assert_eq; #[test] fn test_edit_series_path_input_backspace() { @@ -1253,7 +1294,43 @@ mod tests { #[test] fn test_edit_series_confirm_prompt_prompt_confirm() { let mut app = App::default(); - app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); + let mut edit_series = EditSeriesModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + use_season_folders: Some(false), + ..EditSeriesModal::default() + }; + edit_series + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_series + .language_profile_list + .set_items(vec!["Any".to_owned(), "English".to_owned()]); + edit_series + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.edit_series_modal = Some(edit_series); + app.data.sonarr_data.series.set_items(vec![Series { + monitored: false, + season_folder: false, + ..series() + }]); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "English".to_owned())]); + let expected_edit_series_params = EditSeriesParams { + series_id: 1, + monitored: Some(false), + use_season_folders: Some(false), + series_type: Some(SeriesType::Standard), + quality_profile_id: Some(1111), + language_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".to_owned()), + ..EditSeriesParams::default() + }; app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into()); app.data.sonarr_data.selected_block = BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); @@ -1274,9 +1351,9 @@ mod tests { assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::EditSeries(None)) + Some(SonarrEvent::EditSeries(expected_edit_series_params)) ); - assert!(app.data.sonarr_data.edit_series_modal.is_some()); + assert!(app.data.sonarr_data.edit_series_modal.is_none()); assert!(app.should_refresh); } } @@ -1292,6 +1369,59 @@ mod tests { }); } + #[test] + fn test_build_edit_series_params() { + let mut app = App::default(); + let mut edit_series = EditSeriesModal { + tags: "usenet, testing".to_owned().into(), + path: "/nfs/Test Path".to_owned().into(), + monitored: Some(false), + use_season_folders: Some(false), + ..EditSeriesModal::default() + }; + edit_series + .quality_profile_list + .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); + edit_series + .language_profile_list + .set_items(vec!["Any".to_owned(), "English".to_owned()]); + edit_series + .series_type_list + .set_items(Vec::from_iter(SeriesType::iter())); + app.data.sonarr_data.edit_series_modal = Some(edit_series); + app.data.sonarr_data.series.set_items(vec![Series { + monitored: false, + season_folder: false, + ..series() + }]); + app.data.sonarr_data.quality_profile_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); + app.data.sonarr_data.language_profiles_map = + BiMap::from_iter([(1111, "Any".to_owned()), (2222, "English".to_owned())]); + let expected_edit_series_params = EditSeriesParams { + series_id: 1, + monitored: Some(false), + use_season_folders: Some(false), + series_type: Some(SeriesType::Standard), + quality_profile_id: Some(1111), + language_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".to_owned()), + ..EditSeriesParams::default() + }; + + let edit_series_params = EditSeriesHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EditSeriesPrompt, + None, + ) + .build_edit_series_params(); + + assert_eq!(edit_series_params, expected_edit_series_params); + assert!(app.data.sonarr_data.edit_series_modal.is_none()); + } + #[test] fn test_edit_series_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/library/episode_details_handler.rs b/src/handlers/sonarr_handlers/library/episode_details_handler.rs index 026fa11..9d987d2 100644 --- a/src/handlers/sonarr_handlers/library/episode_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/episode_details_handler.rs @@ -53,6 +53,19 @@ impl<'a, 'b> EpisodeDetailsHandler<'a, 'b> { .episode_releases, SonarrRelease ); + + fn extract_episode_id(&self) -> i64 { + self + .app + .data + .sonarr_data + .season_details_modal + .as_ref() + .expect("Season details modal is undefined") + .episodes + .current_selection() + .id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandler<'a, 'b> { @@ -204,8 +217,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle } ActiveSonarrBlock::AutomaticallySearchEpisodePrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticEpisodeSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()), + ); } self.app.pop_navigation_stack(); @@ -308,8 +322,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle if key == DEFAULT_KEYBINDINGS.confirm.key => { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticEpisodeSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()), + ); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/library/episode_details_handler_tests.rs b/src/handlers/sonarr_handlers/library/episode_details_handler_tests.rs index c5d31f3..dfba3d3 100644 --- a/src/handlers/sonarr_handlers/library/episode_details_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/episode_details_handler_tests.rs @@ -3,14 +3,16 @@ mod tests { use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::episode; use crate::handlers::KeyEventHandler; - use crate::models::servarr_data::sonarr::modals::EpisodeDetailsModal; + use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal}; use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, }; use crate::models::sonarr_models::SonarrReleaseDownloadBody; use crate::models::stateful_table::StatefulTable; + use pretty_assertions::assert_eq; use rstest::rstest; use strum::IntoEnumIterator; @@ -206,7 +208,7 @@ mod tests { #[rstest] #[case( ActiveSonarrBlock::AutomaticallySearchEpisodePrompt, - SonarrEvent::TriggerAutomaticEpisodeSearch(None) + SonarrEvent::TriggerAutomaticEpisodeSearch(1) )] fn test_episode_details_prompt_confirm_submit( #[case] prompt_block: ActiveSonarrBlock, @@ -221,6 +223,14 @@ mod tests { ) { let mut app = App::default(); app.data.sonarr_data = create_test_sonarr_data(); + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); app.data.sonarr_data.prompt_confirm = true; app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(prompt_block.into()); @@ -543,6 +553,14 @@ mod tests { ) { let mut app = App::default(); app.data.sonarr_data = create_test_sonarr_data(); + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); app.data.sonarr_data.prompt_confirm = true; app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into()); @@ -559,7 +577,7 @@ mod tests { assert_eq!(app.get_current_route(), active_sonarr_block.into()); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::TriggerAutomaticEpisodeSearch(None)) + Some(SonarrEvent::TriggerAutomaticEpisodeSearch(1)) ); } @@ -607,6 +625,38 @@ mod tests { }); } + #[test] + fn test_extract_episode_id() { + let mut app = App::default(); + let mut season_details_modal = SeasonDetailsModal::default(); + season_details_modal.episodes.set_items(vec![episode()]); + app.data.sonarr_data.season_details_modal = Some(season_details_modal); + + let episode_id = EpisodeDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EpisodeDetails, + None, + ) + .extract_episode_id(); + + assert_eq!(episode_id, 1); + } + + #[test] + #[should_panic = "Season details modal is undefined"] + fn test_extract_episode_id_panics_when_season_details_modal_is_none() { + let mut app = App::default(); + + EpisodeDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::EpisodeDetails, + None, + ) + .extract_episode_id(); + } + #[test] fn test_episode_details_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/library/season_details_handler.rs b/src/handlers/sonarr_handlers/library/season_details_handler.rs index c66ab09..8036cad 100644 --- a/src/handlers/sonarr_handlers/library/season_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/season_details_handler.rs @@ -65,6 +65,44 @@ impl<'a, 'b> SeasonDetailsHandler<'a, 'b> { .season_releases, SonarrRelease ); + + fn extract_episode_file_id(&self) -> i64 { + self + .app + .data + .sonarr_data + .season_details_modal + .as_ref() + .expect("Season details have not been loaded") + .episodes + .current_selection() + .episode_file_id + } + + fn extract_episode_id(&self) -> i64 { + self + .app + .data + .sonarr_data + .season_details_modal + .as_ref() + .expect("Season details have not been loaded") + .episodes + .current_selection() + .id + } + + fn extract_series_id_season_number_tuple(&self) -> (i64, i64) { + let series_id = self.app.data.sonarr_data.series.current_selection().id; + let season_number = self + .app + .data + .sonarr_data + .seasons + .current_selection() + .season_number; + (series_id, season_number) + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler<'a, 'b> { @@ -233,16 +271,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler .push_navigation_stack(ActiveSonarrBlock::SeasonHistoryDetails.into()), ActiveSonarrBlock::DeleteEpisodeFilePrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteEpisodeFile(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile( + self.extract_episode_file_id(), + )); } self.app.pop_navigation_stack(); } ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticSeasonSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()), + ); } self.app.pop_navigation_stack(); @@ -340,8 +380,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler match self.active_sonarr_block { ActiveSonarrBlock::SeasonDetails if self.key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::ToggleEpisodeMonitoring(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()), + ); self .app @@ -366,15 +407,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler if key == DEFAULT_KEYBINDINGS.confirm.key => { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticSeasonSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()), + ); self.app.pop_navigation_stack(); } ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteEpisodeFile(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile( + self.extract_episode_file_id(), + )); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/library/season_details_handler_tests.rs b/src/handlers/sonarr_handlers/library/season_details_handler_tests.rs index dfe2a88..02f2f7b 100644 --- a/src/handlers/sonarr_handlers/library/season_details_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/season_details_handler_tests.rs @@ -5,6 +5,7 @@ mod tests { use crate::handlers::sonarr_handlers::library::season_details_handler::{ releases_sorting_options, SeasonDetailsHandler, }; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::episode; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::modals::SeasonDetailsModal; use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data; @@ -14,7 +15,7 @@ mod tests { use crate::models::servarr_models::{Language, Quality, QualityWrapper}; use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody}; use crate::models::HorizontallyScrollableText; - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; use serde_json::Number; use std::cmp::Ordering; @@ -275,11 +276,11 @@ mod tests { #[rstest] #[case( ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, - SonarrEvent::TriggerAutomaticSeasonSearch(None) + SonarrEvent::TriggerAutomaticSeasonSearch((0, 0)) )] #[case( ActiveSonarrBlock::DeleteEpisodeFilePrompt, - SonarrEvent::DeleteEpisodeFile(None) + SonarrEvent::DeleteEpisodeFile(0) )] fn test_season_details_prompt_confirm_submit( #[case] prompt_block: ActiveSonarrBlock, @@ -550,6 +551,14 @@ mod tests { fn test_toggle_monitoring_key() { let mut app = App::default(); app.data.sonarr_data = create_test_sonarr_data(); + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into()); app.is_routing = false; @@ -569,7 +578,7 @@ mod tests { assert!(app.is_routing); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::ToggleEpisodeMonitoring(None)) + Some(SonarrEvent::ToggleEpisodeMonitoring(1)) ); } @@ -704,11 +713,11 @@ mod tests { #[rstest] #[case( ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, - SonarrEvent::TriggerAutomaticSeasonSearch(None) + SonarrEvent::TriggerAutomaticSeasonSearch((0, 0)) )] #[case( ActiveSonarrBlock::DeleteEpisodeFilePrompt, - SonarrEvent::DeleteEpisodeFile(None) + SonarrEvent::DeleteEpisodeFile(0) )] fn test_season_details_prompt_confirm_confirm_key( #[case] prompt_block: ActiveSonarrBlock, @@ -783,6 +792,85 @@ mod tests { }); } + #[test] + fn test_extract_episode_file_id() { + let mut app = App::default(); + app.data.sonarr_data = create_test_sonarr_data(); + + let episode_file_id = SeasonDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeasonDetails, + None, + ) + .extract_episode_file_id(); + + assert_eq!(episode_file_id, 0); + } + + #[test] + #[should_panic(expected = "Season details have not been loaded")] + fn test_extract_episode_file_id_empty_season_details_modal_panics() { + let mut app = App::default(); + + SeasonDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeasonDetails, + None, + ) + .extract_episode_file_id(); + } + + #[test] + fn test_extract_episode_id() { + let mut app = App::default(); + let mut season_details_modal = SeasonDetailsModal::default(); + season_details_modal.episodes.set_items(vec![episode()]); + app.data.sonarr_data.season_details_modal = Some(season_details_modal); + + let episode_id = SeasonDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeasonDetails, + None, + ) + .extract_episode_id(); + + assert_eq!(episode_id, 1); + } + + #[test] + #[should_panic(expected = "Season details have not been loaded")] + fn test_extract_episode_id_panic_when_season_details_modal_is_none() { + let mut app = App::default(); + + SeasonDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeasonDetails, + None, + ) + .extract_episode_id(); + } + + #[test] + fn test_extract_series_id_season_number_tuple() { + let mut app = App::default(); + app.data.sonarr_data = create_test_sonarr_data(); + + let (series_id, season_number) = SeasonDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeasonDetails, + None, + ) + .extract_series_id_season_number_tuple(); + + assert_eq!(series_id, 0); + assert_eq!(season_number, 0); + } + #[test] fn test_season_details_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/library/series_details_handler.rs b/src/handlers/sonarr_handlers/library/series_details_handler.rs index d3201d2..c2ada4b 100644 --- a/src/handlers/sonarr_handlers/library/series_details_handler.rs +++ b/src/handlers/sonarr_handlers/library/series_details_handler.rs @@ -37,6 +37,23 @@ impl<'a, 'b> SeriesDetailsHandler<'a, 'b> { .expect("Series history is undefined"), SonarrHistoryItem ); + + fn extract_series_id_season_number_tuple(&self) -> (i64, i64) { + let series_id = self.app.data.sonarr_data.series.current_selection().id; + let season_number = self + .app + .data + .sonarr_data + .seasons + .current_selection() + .season_number; + + (series_id, season_number) + } + + fn extract_series_id(&self) -> i64 { + self.app.data.sonarr_data.series.current_selection().id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler<'a, 'b> { @@ -168,8 +185,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler } ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticSeriesSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()), + ); } self.app.pop_navigation_stack(); @@ -177,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler ActiveSonarrBlock::UpdateAndScanSeriesPrompt => { if self.app.data.sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::UpdateAndScanSeries(None)); + Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id())); } self.app.pop_navigation_stack(); @@ -259,8 +277,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler } _ if key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::ToggleSeasonMonitoring(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()), + ); self .app @@ -299,8 +318,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => { if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::TriggerAutomaticSeriesSearch(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some( + SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()), + ); self.app.pop_navigation_stack(); } @@ -308,7 +328,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler ActiveSonarrBlock::UpdateAndScanSeriesPrompt => { if self.app.data.sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::UpdateAndScanSeries(None)); + Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id())); } self.app.pop_navigation_stack(); diff --git a/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs b/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs index 3973547..a8a4600 100644 --- a/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs +++ b/src/handlers/sonarr_handlers/library/series_details_handler_tests.rs @@ -4,6 +4,7 @@ mod tests { use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::{season, series}; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ ActiveSonarrBlock, SERIES_DETAILS_BLOCKS, @@ -11,6 +12,7 @@ mod tests { use crate::models::sonarr_models::Season; use crate::models::sonarr_models::SonarrHistoryItem; use crate::models::stateful_table::StatefulTable; + use pretty_assertions::assert_eq; use rstest::rstest; use strum::IntoEnumIterator; @@ -181,11 +183,11 @@ mod tests { #[rstest] #[case( ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, - SonarrEvent::TriggerAutomaticSeriesSearch(None) + SonarrEvent::TriggerAutomaticSeriesSearch(1) )] #[case( ActiveSonarrBlock::UpdateAndScanSeriesPrompt, - SonarrEvent::UpdateAndScanSeries(None) + SonarrEvent::UpdateAndScanSeries(1) )] fn test_series_details_prompt_confirm_submit( #[case] prompt_block: ActiveSonarrBlock, @@ -193,6 +195,7 @@ mod tests { ) { let mut app = App::default(); app.data.sonarr_data.prompt_confirm = true; + app.data.sonarr_data.series.set_items(vec![series()]); app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into()); app.push_navigation_stack(prompt_block.into()); @@ -398,7 +401,7 @@ mod tests { assert!(app.is_routing); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::ToggleSeasonMonitoring(None)) + Some(SonarrEvent::ToggleSeasonMonitoring((0, 0))) ); } @@ -565,11 +568,11 @@ mod tests { #[rstest] #[case( ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, - SonarrEvent::TriggerAutomaticSeriesSearch(None) + SonarrEvent::TriggerAutomaticSeriesSearch(1) )] #[case( ActiveSonarrBlock::UpdateAndScanSeriesPrompt, - SonarrEvent::UpdateAndScanSeries(None) + SonarrEvent::UpdateAndScanSeries(1) )] fn test_series_details_prompt_confirm_confirm_key( #[case] prompt_block: ActiveSonarrBlock, @@ -579,6 +582,7 @@ mod tests { ) { let mut app = App::default(); app.data.sonarr_data.prompt_confirm = true; + app.data.sonarr_data.series.set_items(vec![series()]); app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(prompt_block.into()); @@ -610,6 +614,39 @@ mod tests { }); } + #[test] + fn test_extract_series_id_season_number_tuple() { + let mut app = App::default(); + app.data.sonarr_data.series.set_items(vec![series()]); + app.data.sonarr_data.seasons.set_items(vec![season()]); + + let series_id_season_number_tuple = SeriesDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeriesDetails, + None, + ) + .extract_series_id_season_number_tuple(); + + assert_eq!(series_id_season_number_tuple, (1, 1)); + } + + #[test] + fn test_extract_series_id() { + let mut app = App::default(); + app.data.sonarr_data.series.set_items(vec![series()]); + + let series_id = SeriesDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SeriesDetails, + None, + ) + .extract_series_id(); + + assert_eq!(series_id, 1); + } + #[test] fn test_series_details_handler_is_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/root_folders/mod.rs b/src/handlers/sonarr_handlers/root_folders/mod.rs index 6e1d850..fc1069a 100644 --- a/src/handlers/sonarr_handlers/root_folders/mod.rs +++ b/src/handlers/sonarr_handlers/root_folders/mod.rs @@ -5,7 +5,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_FOLDERS_BLOCKS}; -use crate::models::servarr_models::RootFolder; +use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::HorizontallyScrollableText; use crate::network::sonarr_network::SonarrEvent; use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; @@ -28,6 +28,30 @@ impl<'a, 'b> RootFoldersHandler<'a, 'b> { self.app.data.sonarr_data.root_folders, RootFolder ); + + fn build_add_root_folder_body(&mut self) -> AddRootFolderBody { + let root_folder = self + .app + .data + .sonarr_data + .edit_root_folder + .as_ref() + .unwrap() + .text + .clone(); + self.app.data.sonarr_data.edit_root_folder = None; + AddRootFolderBody { path: root_folder } + } + + fn extract_root_folder_id(&self) -> i64 { + self + .app + .data + .sonarr_data + .root_folders + .current_selection() + .id + } } impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'a, 'b> { @@ -124,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<' ActiveSonarrBlock::DeleteRootFolderPrompt => { if self.app.data.sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteRootFolder(None)); + Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id())); } self.app.pop_navigation_stack(); @@ -140,7 +164,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<' .text .is_empty() => { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddRootFolder(None)); + self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::AddRootFolder( + self.build_add_root_folder_body(), + )); self.app.data.sonarr_data.prompt_confirm = true; self.app.should_ignore_quit_key = false; self.app.pop_navigation_stack(); @@ -192,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<' if key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm_action = - Some(SonarrEvent::DeleteRootFolder(None)); + Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id())); self.app.pop_navigation_stack(); } diff --git a/src/handlers/sonarr_handlers/root_folders/root_folders_handler_tests.rs b/src/handlers/sonarr_handlers/root_folders/root_folders_handler_tests.rs index 1578234..018378b 100644 --- a/src/handlers/sonarr_handlers/root_folders/root_folders_handler_tests.rs +++ b/src/handlers/sonarr_handlers/root_folders/root_folders_handler_tests.rs @@ -1,14 +1,16 @@ #[cfg(test)] mod tests { + use pretty_assertions::assert_eq; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; use crate::handlers::sonarr_handlers::root_folders::RootFoldersHandler; + use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::root_folder; use crate::handlers::KeyEventHandler; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_FOLDERS_BLOCKS}; - use crate::models::servarr_models::RootFolder; + use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::HorizontallyScrollableText; mod test_handle_home_end { @@ -257,6 +259,9 @@ mod tests { #[test] fn test_add_root_folder_prompt_confirm_submit() { let mut app = App::default(); + let expected_add_root_folder_body = AddRootFolderBody { + path: "Test".to_owned(), + }; app .data .sonarr_data @@ -280,12 +285,13 @@ mod tests { assert!(!app.should_ignore_quit_key); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::AddRootFolder(None)) + Some(SonarrEvent::AddRootFolder(expected_add_root_folder_body)) ); assert_eq!( app.get_current_route(), ActiveSonarrBlock::RootFolders.into() ); + assert!(app.data.sonarr_data.edit_root_folder.is_none()); } #[test] @@ -321,7 +327,7 @@ mod tests { .data .sonarr_data .root_folders - .set_items(vec![RootFolder::default()]); + .set_items(vec![root_folder()]); app.data.sonarr_data.prompt_confirm = true; app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into()); @@ -337,7 +343,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteRootFolder(None)) + Some(SonarrEvent::DeleteRootFolder(1)) ); assert_eq!( app.get_current_route(), @@ -614,7 +620,7 @@ mod tests { .data .sonarr_data .root_folders - .set_items(vec![RootFolder::default()]); + .set_items(vec![root_folder()]); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into()); @@ -629,7 +635,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::DeleteRootFolder(None)) + Some(SonarrEvent::DeleteRootFolder(1)) ); assert_eq!( app.get_current_route(), @@ -649,6 +655,46 @@ mod tests { }) } + #[test] + fn test_extract_root_folder_id() { + let mut app = App::default(); + app + .data + .sonarr_data + .root_folders + .set_items(vec![root_folder()]); + + let root_folder_id = RootFoldersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::DeleteRootFolderPrompt, + None, + ) + .extract_root_folder_id(); + + assert_eq!(root_folder_id, 1); + } + + #[test] + fn test_build_add_root_folder_body() { + let mut app = App::default(); + app.data.sonarr_data.edit_root_folder = Some("/nfs/test".into()); + let expected_add_root_folder_body = AddRootFolderBody { + path: "/nfs/test".to_owned(), + }; + + let root_folder = RootFoldersHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::AddRootFolderPrompt, + None, + ) + .build_add_root_folder_body(); + + assert_eq!(root_folder, expected_add_root_folder_body); + assert!(app.data.sonarr_data.edit_root_folder.is_none()); + } + #[test] fn test_root_folders_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs b/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs index 226e2dd..61a0c1c 100644 --- a/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs +++ b/src/handlers/sonarr_handlers/sonarr_handler_test_utils.rs @@ -1,6 +1,17 @@ #[cfg(test)] #[macro_use] -mod utils { +pub(in crate::handlers::sonarr_handlers) mod utils { + use crate::models::servarr_models::{ + Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder, + }; + use crate::models::sonarr_models::{ + AddSeriesSearchResult, AddSeriesSearchResultStatistics, DownloadRecord, DownloadStatus, + Episode, EpisodeFile, IndexerSettings, MediaInfo, Rating, Season, SeasonStatistics, Series, + SeriesStatistics, SeriesStatus, SeriesType, + }; + use crate::models::HorizontallyScrollableText; + use chrono::DateTime; + use serde_json::{json, Number}; #[macro_export] macro_rules! test_edit_series_key { @@ -154,4 +165,230 @@ mod utils { ); }; } + + pub fn add_series_search_result() -> AddSeriesSearchResult { + AddSeriesSearchResult { + tvdb_id: 1234, + title: HorizontallyScrollableText::from("Test"), + status: Some("continuing".to_owned()), + ended: false, + overview: Some("New series blah blah blah".to_owned()), + genres: genres(), + year: 2023, + network: Some("Prime Video".to_owned()), + runtime: 60, + ratings: Some(rating()), + statistics: Some(add_series_search_result_statistics()), + } + } + + pub fn add_series_search_result_statistics() -> AddSeriesSearchResultStatistics { + AddSeriesSearchResultStatistics { season_count: 3 } + } + + pub fn download_record() -> DownloadRecord { + DownloadRecord { + title: "Test Download Title".to_owned(), + status: DownloadStatus::Downloading, + id: 1, + episode_id: 1, + size: 3543348019f64, + sizeleft: 1771674009f64, + output_path: Some(HorizontallyScrollableText::from( + "/nfs/tv/Test show/season 1/", + )), + indexer: "kickass torrents".to_owned(), + download_client: Some("transmission".to_owned()), + } + } + + pub fn episode() -> Episode { + Episode { + id: 1, + series_id: 1, + tvdb_id: 1234, + episode_file_id: 1, + season_number: 1, + episode_number: 1, + title: "Something cool".to_owned(), + air_date_utc: Some(DateTime::from( + DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap(), + )), + overview: Some("Okay so this one time at band camp...".to_owned()), + has_file: true, + monitored: true, + episode_file: Some(episode_file()), + } + } + + pub fn episode_file() -> EpisodeFile { + EpisodeFile { + id: 1, + relative_path: "/season 1/episode 1.mkv".to_owned(), + path: "/nfs/tv/series/season 1/episode 1.mkv".to_owned(), + size: 3543348019, + quality: quality_wrapper(), + languages: vec![language()], + date_added: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()), + media_info: Some(media_info()), + } + } + + pub fn genres() -> Vec { + vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()] + } + + pub fn indexer() -> Indexer { + Indexer { + enable_rss: true, + enable_automatic_search: true, + enable_interactive_search: true, + supports_rss: true, + supports_search: true, + protocol: "torrent".to_owned(), + priority: 25, + download_client_id: 0, + name: Some("Test Indexer".to_owned()), + implementation_name: Some("Torznab".to_owned()), + implementation: Some("Torznab".to_owned()), + config_contract: Some("TorznabSettings".to_owned()), + tags: vec![Number::from(1)], + id: 1, + fields: Some(vec![ + IndexerField { + name: Some("baseUrl".to_owned()), + value: Some(json!("https://test.com")), + }, + IndexerField { + name: Some("apiKey".to_owned()), + value: Some(json!("")), + }, + IndexerField { + name: Some("seedCriteria.seedRatio".to_owned()), + value: Some(json!("1.2")), + }, + ]), + } + } + + pub fn indexer_settings() -> IndexerSettings { + IndexerSettings { + id: 1, + minimum_age: 1, + retention: 1, + maximum_size: 12345, + rss_sync_interval: 60, + } + } + + pub fn language() -> Language { + Language { + id: 1, + name: "English".to_owned(), + } + } + + pub fn media_info() -> MediaInfo { + MediaInfo { + audio_bitrate: 0, + audio_channels: Number::from_f64(7.1).unwrap(), + audio_codec: Some("AAC".to_owned()), + audio_languages: Some("eng".to_owned()), + audio_stream_count: 1, + video_bit_depth: 10, + video_bitrate: 0, + video_codec: "x265".to_owned(), + video_fps: Number::from_f64(23.976).unwrap(), + resolution: "1920x1080".to_owned(), + run_time: "23:51".to_owned(), + scan_type: "Progressive".to_owned(), + subtitles: Some("English".to_owned()), + } + } + pub fn quality() -> Quality { + Quality { + name: "Bluray-1080p".to_owned(), + } + } + + pub fn quality_wrapper() -> QualityWrapper { + QualityWrapper { quality: quality() } + } + + pub fn rating() -> Rating { + Rating { + votes: 406744, + value: 8.4, + } + } + + pub fn season() -> Season { + Season { + title: None, + season_number: 1, + monitored: true, + statistics: season_statistics(), + } + } + + pub fn season_statistics() -> SeasonStatistics { + SeasonStatistics { + previous_airing: Some(DateTime::from( + DateTime::parse_from_rfc3339("2022-10-24T01:00:00Z").unwrap(), + )), + next_airing: None, + episode_file_count: 10, + episode_count: 10, + total_episode_count: 10, + size_on_disk: 36708563419, + percent_of_episodes: 100.0, + } + } + + pub fn series() -> Series { + Series { + title: "Test".to_owned().into(), + status: SeriesStatus::Continuing, + ended: false, + overview: Some("Blah blah blah".to_owned()), + network: Some("HBO".to_owned()), + seasons: Some(vec![season()]), + year: 2022, + path: "/nfs/tv/Test".to_owned(), + quality_profile_id: 6, + language_profile_id: 1, + season_folder: true, + monitored: true, + runtime: 63, + tvdb_id: 371572, + series_type: SeriesType::Standard, + certification: Some("TV-MA".to_owned()), + genres: vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()], + tags: vec![Number::from(3)], + ratings: rating(), + statistics: Some(series_statistics()), + id: 1, + } + } + + pub fn series_statistics() -> SeriesStatistics { + SeriesStatistics { + season_count: 2, + episode_file_count: 18, + episode_count: 18, + total_episode_count: 50, + size_on_disk: 63894022699, + percent_of_episodes: 100.0, + } + } + + pub fn root_folder() -> RootFolder { + RootFolder { + id: 1, + path: "/nfs".to_owned(), + accessible: true, + free_space: 219902325555200, + unmapped_folders: None, + } + } } diff --git a/src/handlers/sonarr_handlers/system/system_details_handler.rs b/src/handlers/sonarr_handlers/system/system_details_handler.rs index 9df82b6..477d1f7 100644 --- a/src/handlers/sonarr_handlers/system/system_details_handler.rs +++ b/src/handlers/sonarr_handlers/system/system_details_handler.rs @@ -3,6 +3,7 @@ use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS}; +use crate::models::sonarr_models::SonarrTaskName; use crate::models::stateful_list::StatefulList; use crate::models::Scrollable; use crate::network::sonarr_network::SonarrEvent; @@ -18,6 +19,18 @@ pub(super) struct SystemDetailsHandler<'a, 'b> { _context: Option, } +impl<'a, 'b> SystemDetailsHandler<'a, 'b> { + fn extract_task_name(&self) -> SonarrTaskName { + self + .app + .data + .sonarr_data + .tasks + .current_selection() + .task_name + } +} + impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler<'a, 'b> { fn accepts(active_block: ActiveSonarrBlock) -> bool { SYSTEM_DETAILS_BLOCKS.contains(&active_block) @@ -137,7 +150,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler } ActiveSonarrBlock::SystemTaskStartConfirmPrompt => { if self.app.data.sonarr_data.prompt_confirm { - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::StartTask(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::StartTask(self.extract_task_name())); } self.app.pop_navigation_stack(); @@ -174,7 +188,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler && self.key == DEFAULT_KEYBINDINGS.confirm.key { self.app.data.sonarr_data.prompt_confirm = true; - self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::StartTask(None)); + self.app.data.sonarr_data.prompt_confirm_action = + Some(SonarrEvent::StartTask(self.extract_task_name())); self.app.pop_navigation_stack(); } } diff --git a/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs b/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs index 94ad27f..550235b 100644 --- a/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs +++ b/src/handlers/sonarr_handlers/system/system_details_handler_tests.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use pretty_assertions::assert_str_eq; + use pretty_assertions::{assert_eq, assert_str_eq}; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; @@ -12,10 +12,11 @@ mod tests { ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS, }; use crate::models::servarr_models::QueueEvent; - use crate::models::sonarr_models::SonarrTask; + use crate::models::sonarr_models::{SonarrTask, SonarrTaskName}; use crate::models::{HorizontallyScrollableText, ScrollableText}; mod test_handle_scroll_up_and_down { + use pretty_assertions::assert_eq; use rstest::rstest; use crate::models::{HorizontallyScrollableText, ScrollableText}; @@ -245,6 +246,7 @@ mod tests { use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; + use pretty_assertions::assert_eq; test_iterable_home_and_end!( test_log_details_home_end, @@ -693,6 +695,11 @@ mod tests { let mut app = App::default(); app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); app.data.sonarr_data.prompt_confirm = true; + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); @@ -707,7 +714,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::StartTask(None)) + Some(SonarrEvent::StartTask(SonarrTaskName::default())) ); assert_eq!( app.get_current_route(), @@ -848,6 +855,7 @@ mod tests { } mod test_handle_key_char { + use pretty_assertions::assert_eq; use rstest::rstest; use crate::network::sonarr_network::SonarrEvent; @@ -914,6 +922,11 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveSonarrBlock::System.into()); app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); @@ -928,7 +941,7 @@ mod tests { assert!(app.data.sonarr_data.prompt_confirm); assert_eq!( app.data.sonarr_data.prompt_confirm_action, - Some(SonarrEvent::StartTask(None)) + Some(SonarrEvent::StartTask(SonarrTaskName::default())) ); assert_eq!( app.get_current_route(), @@ -948,6 +961,26 @@ mod tests { }) } + #[test] + fn test_extract_task_name() { + let mut app = App::default(); + app + .data + .sonarr_data + .tasks + .set_items(vec![SonarrTask::default()]); + + let task_name = SystemDetailsHandler::with( + DEFAULT_KEYBINDINGS.esc.key, + &mut app, + ActiveSonarrBlock::SystemTasks, + None, + ) + .extract_task_name(); + + assert_eq!(task_name, SonarrTaskName::default()); + } + #[test] fn test_system_details_handler_not_ready_when_loading() { let mut app = App::default(); diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index a99974e..0ab50cb 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -29,6 +29,8 @@ pub struct AddMovieBody { pub minimum_availability: String, pub monitored: bool, pub tags: Vec, + #[serde(skip_serializing, skip_deserializing)] + pub tag_input_string: Option, pub add_options: AddMovieOptions, } @@ -135,7 +137,7 @@ pub enum CreditType { Crew, } -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)] #[serde(rename_all = "lowercase")] pub struct DeleteMovieParams { pub id: i64, @@ -188,6 +190,8 @@ pub struct EditMovieParams { pub quality_profile_id: Option, pub root_folder_path: Option, pub tags: Option>, + #[serde(skip_serializing, skip_deserializing)] + pub tag_input_string: Option, pub clear_tags: bool, } diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs index 4f1810b..3a8b740 100644 --- a/src/models/servarr_models.rs +++ b/src/models/servarr_models.rs @@ -11,7 +11,7 @@ use super::HorizontallyScrollableText; #[path = "servarr_models_tests.rs"] mod servarr_models_tests; -#[derive(Default, Serialize, Debug)] +#[derive(Default, Serialize, Debug, Clone, PartialEq, Eq)] pub struct AddRootFolderBody { pub path: String, } @@ -101,6 +101,8 @@ pub struct EditIndexerParams { pub api_key: Option, pub seed_ratio: Option, pub tags: Option>, + #[serde(skip_serializing, skip_deserializing)] + pub tag_input_string: Option, pub priority: Option, pub clear_tags: bool, } diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 48f55ef..5606a73 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -34,6 +34,8 @@ pub struct AddSeriesBody { pub series_type: String, pub season_folder: bool, pub tags: Vec, + #[serde(skip_serializing, skip_deserializing)] + pub tag_input_string: Option, pub add_options: AddSeriesOptions, } @@ -95,7 +97,7 @@ pub struct BlocklistResponse { pub records: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)] #[serde(rename_all = "lowercase")] pub struct DeleteSeriesParams { pub id: i64, @@ -192,6 +194,8 @@ pub struct EditSeriesParams { pub series_type: Option, pub root_folder_path: Option, pub tags: Option>, + #[serde(skip_serializing, skip_deserializing)] + pub tag_input_string: Option, pub clear_tags: bool, } diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 60497af..3d5cd3d 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -1,22 +1,19 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use std::fmt::Debug; use indoc::formatdoc; -use log::{debug, info, warn}; +use log::{debug, info}; use serde_json::{json, Value}; use urlencoding::encode; use crate::models::radarr_models::{ - AddMovieBody, AddMovieOptions, AddMovieSearchResult, BlocklistResponse, Collection, - CollectionMovie, Credit, CreditType, DeleteMovieParams, DownloadRecord, DownloadsResponse, - EditCollectionParams, EditMovieParams, IndexerSettings, IndexerTestResult, Movie, - MovieCommandBody, MovieHistoryItem, RadarrRelease, RadarrReleaseDownloadBody, RadarrSerdeable, - RadarrTask, RadarrTaskName, SystemStatus, -}; -use crate::models::servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}; -use crate::models::servarr_data::radarr::modals::{ - AddMovieModal, EditCollectionModal, EditMovieModal, MovieDetailsModal, + AddMovieBody, AddMovieSearchResult, BlocklistResponse, Collection, Credit, CreditType, + DeleteMovieParams, DownloadRecord, DownloadsResponse, 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::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::{ AddRootFolderBody, CommandBody, DiskSpace, EditIndexerParams, HostConfig, Indexer, LogResponse, @@ -35,36 +32,36 @@ mod radarr_network_tests; #[derive(Debug, Eq, PartialEq, Clone)] pub enum RadarrEvent { - AddMovie(Option), - AddRootFolder(Option), + AddMovie(AddMovieBody), + AddRootFolder(AddRootFolderBody), AddTag(String), ClearBlocklist, - DeleteBlocklistItem(Option), - DeleteDownload(Option), - DeleteIndexer(Option), - DeleteMovie(Option), - DeleteRootFolder(Option), + DeleteBlocklistItem(i64), + DeleteDownload(i64), + DeleteIndexer(i64), + DeleteMovie(DeleteMovieParams), + DeleteRootFolder(i64), DeleteTag(i64), - DownloadRelease(Option), - EditAllIndexerSettings(Option), - EditCollection(Option), - EditIndexer(Option), - EditMovie(Option), + DownloadRelease(RadarrReleaseDownloadBody), + EditAllIndexerSettings(IndexerSettings), + EditCollection(EditCollectionParams), + EditIndexer(EditIndexerParams), + EditMovie(EditMovieParams), GetBlocklist, GetCollections, GetDownloads, GetHostConfig, GetIndexers, GetAllIndexerSettings, - GetLogs(Option), - GetMovieCredits(Option), - GetMovieDetails(Option), - GetMovieHistory(Option), + GetLogs(u64), + GetMovieCredits(i64), + GetMovieDetails(i64), + GetMovieHistory(i64), GetMovies, GetDiskSpace, GetQualityProfiles, GetQueuedEvents, - GetReleases(Option), + GetReleases(i64), GetRootFolders, GetSecurityConfig, GetStatus, @@ -72,13 +69,13 @@ pub enum RadarrEvent { GetTasks, GetUpdates, HealthCheck, - SearchNewMovie(Option), - StartTask(Option), - TestIndexer(Option), + SearchNewMovie(String), + StartTask(RadarrTaskName), + TestIndexer(i64), TestAllIndexers, - TriggerAutomaticSearch(Option), + TriggerAutomaticSearch(i64), UpdateAllMovies, - UpdateAndScan(Option), + UpdateAndScan(i64), UpdateCollections, UpdateDownloads, } @@ -282,101 +279,20 @@ impl<'a, 'b> Network<'a, 'b> { } } - async fn add_movie(&mut self, add_movie_body_option: Option) -> Result { + async fn add_movie(&mut self, mut add_movie_body: AddMovieBody) -> Result { info!("Adding new movie to Radarr"); - let event = RadarrEvent::AddMovie(None); - let body = if let Some(add_movie_body) = add_movie_body_option { - add_movie_body - } else { - let tags = self - .app - .lock() - .await - .data - .radarr_data - .add_movie_modal - .as_ref() - .unwrap() - .tags - .text - .clone(); - let tag_ids_vec = self.extract_and_add_radarr_tag_ids_vec(tags).await; - let mut app = self.app.lock().await; - let AddMovieModal { - root_folder_list, - monitor_list, - minimum_availability_list, - quality_profile_list, - .. - } = app.data.radarr_data.add_movie_modal.as_ref().unwrap(); - let (tmdb_id, title) = if let Route::Radarr(active_radarr_block, _) = app.get_current_route() - { - if active_radarr_block == ActiveRadarrBlock::CollectionDetails { - let CollectionMovie { tmdb_id, title, .. } = app - .data - .radarr_data - .collection_movies - .current_selection() - .clone(); - (tmdb_id, title.text) - } else { - let AddMovieSearchResult { tmdb_id, title, .. } = app - .data - .radarr_data - .add_searched_movies - .as_ref() - .unwrap() - .current_selection() - .clone(); - (tmdb_id, title.text) - } - } else { - let AddMovieSearchResult { tmdb_id, title, .. } = app - .data - .radarr_data - .add_searched_movies - .as_ref() - .unwrap() - .current_selection() - .clone(); - (tmdb_id, title.text) - }; - let quality_profile = quality_profile_list.current_selection(); - let quality_profile_id = *app - .data - .radarr_data - .quality_profile_map - .iter() - .filter(|(_, value)| *value == quality_profile) - .map(|(key, _)| key) - .next() - .unwrap(); + let event = RadarrEvent::AddMovie(add_movie_body.clone()); + if let Some(tag_input_string) = add_movie_body.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_radarr_tag_ids_vec(tag_input_string.clone()) + .await; + add_movie_body.tags = tag_ids_vec; + } - let path = root_folder_list.current_selection().path.clone(); - let monitor = monitor_list.current_selection().to_string(); - let minimum_availability = minimum_availability_list.current_selection().to_string(); - - app.data.radarr_data.add_movie_modal = None; - - AddMovieBody { - tmdb_id, - title, - root_folder_path: path, - minimum_availability, - monitored: true, - quality_profile_id, - tags: tag_ids_vec, - add_options: AddMovieOptions { - monitor, - search_for_movie: true, - }, - } - }; - - debug!("Add movie body: {body:?}"); + debug!("Add movie body: {add_movie_body:?}"); let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .request_props_from(event, RequestMethod::Post, Some(add_movie_body), None, None) .await; self @@ -384,31 +300,23 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn add_radarr_root_folder(&mut self, root_folder: Option) -> Result { + async fn add_radarr_root_folder( + &mut self, + add_root_folder_body: AddRootFolderBody, + ) -> Result { info!("Adding new root folder to Radarr"); - let event = RadarrEvent::AddRootFolder(None); - let body = if let Some(path) = root_folder { - AddRootFolderBody { path } - } else { - let mut app = self.app.lock().await; - let path = app - .data - .radarr_data - .edit_root_folder - .as_ref() - .unwrap() - .text - .clone(); + let event = RadarrEvent::AddRootFolder(add_root_folder_body.clone()); - app.data.radarr_data.edit_root_folder = None; - - AddRootFolderBody { path } - }; - - debug!("Add root folder body: {body:?}"); + debug!("Add root folder body: {add_root_folder_body:?}"); let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .request_props_from( + event, + RequestMethod::Post, + Some(add_root_folder_body), + None, + None, + ) .await; self @@ -487,30 +395,17 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_radarr_blocklist_item(&mut self, blocklist_item_id: Option) -> Result<()> { - let event = RadarrEvent::DeleteBlocklistItem(None); - let id = if let Some(b_id) = blocklist_item_id { - b_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .blocklist - .current_selection() - .id - }; + async fn delete_radarr_blocklist_item(&mut self, blocklist_item_id: i64) -> Result<()> { + let event = RadarrEvent::DeleteBlocklistItem(blocklist_item_id); - info!("Deleting Radarr blocklist item for item with id: {id}"); + info!("Deleting Radarr blocklist item for item with id: {blocklist_item_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{blocklist_item_id}")), None, ) .await; @@ -520,30 +415,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_radarr_download(&mut self, download_id: Option) -> Result<()> { - let event = RadarrEvent::DeleteDownload(None); - let id = if let Some(dl_id) = download_id { - dl_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .downloads - .current_selection() - .id - }; - - info!("Deleting Radarr download for download with id: {id}"); + async fn delete_radarr_download(&mut self, download_id: i64) -> Result<()> { + let event = RadarrEvent::DeleteDownload(download_id); + info!("Deleting Radarr download for download with id: {download_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{download_id}")), None, ) .await; @@ -553,30 +434,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_radarr_indexer(&mut self, indexer_id: Option) -> Result<()> { - let event = RadarrEvent::DeleteIndexer(None); - let id = if let Some(i_id) = indexer_id { - i_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .indexers - .current_selection() - .id - }; - - info!("Deleting Radarr indexer for indexer with id: {id}"); + async fn delete_radarr_indexer(&mut self, indexer_id: i64) -> Result<()> { + let event = RadarrEvent::DeleteIndexer(indexer_id); + info!("Deleting Radarr indexer for indexer with id: {indexer_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{indexer_id}")), None, ) .await; @@ -586,75 +453,42 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_movie(&mut self, delete_movie_params: Option) -> Result<()> { - let event = RadarrEvent::DeleteMovie(None); - let (movie_id, delete_files, add_import_exclusion) = if let Some(params) = delete_movie_params { - ( - params.id, - params.delete_movie_files, - params.add_list_exclusion, - ) - } else { - let (movie_id, _) = self.extract_movie_id(None).await; - let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files; - let add_import_exclusion = self.app.lock().await.data.radarr_data.add_list_exclusion; - - (movie_id, delete_files, add_import_exclusion) - }; - - info!("Deleting Radarr movie with ID: {movie_id} with deleteFiles={delete_files} and addImportExclusion={add_import_exclusion}"); + async fn delete_movie(&mut self, delete_movie_params: DeleteMovieParams) -> Result<()> { + let event = RadarrEvent::DeleteMovie(delete_movie_params.clone()); + let DeleteMovieParams { + id, + delete_movie_files, + add_list_exclusion, + } = delete_movie_params; + info!("Deleting Radarr movie with ID: {id} with deleteFiles={delete_movie_files} and addImportExclusion={add_list_exclusion}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{movie_id}")), + Some(format!("/{id}")), Some(format!( - "deleteFiles={delete_files}&addImportExclusion={add_import_exclusion}" + "deleteFiles={delete_movie_files}&addImportExclusion={add_list_exclusion}" )), ) .await; - let resp = self - .handle_request::<(), ()>(request_props, |_, _| ()) - .await; - self - .app - .lock() + .handle_request::<(), ()>(request_props, |_, _| ()) .await - .data - .radarr_data - .reset_delete_movie_preferences(); - - resp } - async fn delete_radarr_root_folder(&mut self, root_folder_id: Option) -> Result<()> { - let event = RadarrEvent::DeleteRootFolder(None); - let id = if let Some(rf_id) = root_folder_id { - rf_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .root_folders - .current_selection() - .id - }; - - info!("Deleting Radarr root folder for folder with id: {id}"); + async fn delete_radarr_root_folder(&mut self, root_folder_id: i64) -> Result<()> { + let event = RadarrEvent::DeleteRootFolder(root_folder_id); + info!("Deleting Radarr root folder for folder with id: {root_folder_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{root_folder_id}")), None, ) .await; @@ -664,46 +498,12 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn download_radarr_release( - &mut self, - params: Option, - ) -> Result { - let event = RadarrEvent::DownloadRelease(None); - let body = if let Some(release_download_body) = params { - info!("Downloading Radarr release with params: {release_download_body:?}"); - release_download_body - } else { - let (movie_id, _) = self.extract_movie_id(None).await; - let (guid, title, indexer_id) = { - let app = self.app.lock().await; - let RadarrRelease { - guid, - title, - indexer_id, - .. - } = app - .data - .radarr_data - .movie_details_modal - .as_ref() - .unwrap() - .movie_releases - .current_selection(); - - (guid.clone(), title.clone(), *indexer_id) - }; - - info!("Downloading release: {title}"); - - RadarrReleaseDownloadBody { - guid, - indexer_id, - movie_id, - } - }; + async fn download_radarr_release(&mut self, params: RadarrReleaseDownloadBody) -> Result { + let event = RadarrEvent::DownloadRelease(params.clone()); + info!("Downloading Radarr release with params: {params:?}"); let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .request_props_from(event, RequestMethod::Post, Some(params), None, None) .await; self @@ -711,57 +511,28 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn edit_all_radarr_indexer_settings( - &mut self, - params: Option, - ) -> Result { + async fn edit_all_radarr_indexer_settings(&mut self, params: IndexerSettings) -> Result { info!("Updating Radarr indexer settings"); - let event = RadarrEvent::EditAllIndexerSettings(None); + let event = RadarrEvent::EditAllIndexerSettings(params.clone()); - let body = if let Some(indexer_settings) = params { - indexer_settings - } else { - self - .app - .lock() - .await - .data - .radarr_data - .indexer_settings - .as_ref() - .unwrap() - .clone() - }; - - debug!("Indexer settings body: {body:?}"); + debug!("Indexer settings body: {params:?}"); let request_props = self - .request_props_from(event, RequestMethod::Put, Some(body), None, None) + .request_props_from(event, RequestMethod::Put, Some(params), None, None) .await; - let resp = self + self .handle_request::(request_props, |_, _| {}) - .await; - - self.app.lock().await.data.radarr_data.indexer_settings = None; - - resp + .await } - async fn edit_collection( - &mut self, - edit_collection_params: Option, - ) -> Result<()> { + async fn edit_collection(&mut self, edit_collection_params: EditCollectionParams) -> Result<()> { info!("Editing Radarr collection"); let detail_event = RadarrEvent::GetCollections; - let event = RadarrEvent::EditCollection(None); + let event = RadarrEvent::EditCollection(edit_collection_params.clone()); info!("Fetching collection details"); + let collection_id = edit_collection_params.collection_id; - let collection_id = if let Some(ref params) = edit_collection_params { - params.collection_id - } else { - self.extract_collection_id().await - }; let request_props = self .request_props_from( detail_event, @@ -782,79 +553,47 @@ impl<'a, 'b> Network<'a, 'b> { info!("Constructing edit collection body"); - let mut detailed_collection_body: Value = serde_json::from_str(&response).unwrap(); - let (monitored, minimum_availability, quality_profile_id, root_folder_path, search_on_add) = - if let Some(params) = edit_collection_params { - let monitored = params.monitored.unwrap_or_else(|| { - detailed_collection_body["monitored"] - .as_bool() - .expect("Unable to deserialize 'monitored' bool") - }); - let minimum_availability = params - .minimum_availability - .unwrap_or_else(|| { - serde_json::from_value(detailed_collection_body["minimumAvailability"].clone()) - .expect("Unable to deserialize 'minimumAvailability'") - }) - .to_string(); - let quality_profile_id = params.quality_profile_id.unwrap_or_else(|| { + let mut detailed_collection_body: Value = serde_json::from_str(&response)?; + let (monitored, minimum_availability, quality_profile_id, root_folder_path, search_on_add) = { + let monitored = edit_collection_params.monitored.unwrap_or_else(|| { + detailed_collection_body["monitored"] + .as_bool() + .expect("Unable to deserialize 'monitored' bool") + }); + let minimum_availability = edit_collection_params + .minimum_availability + .unwrap_or_else(|| { + serde_json::from_value(detailed_collection_body["minimumAvailability"].clone()) + .expect("Unable to deserialize 'minimumAvailability'") + }) + .to_string(); + let quality_profile_id = edit_collection_params + .quality_profile_id + .unwrap_or_else(|| { detailed_collection_body["qualityProfileId"] .as_i64() .expect("Unable to deserialize 'qualityProfileId'") }); - let root_folder_path = params.root_folder_path.unwrap_or_else(|| { - detailed_collection_body["rootFolderPath"] - .as_str() - .expect("Unable to deserialize 'rootFolderPath'") - .to_owned() - }); - let search_on_add = params.search_on_add.unwrap_or_else(|| { - detailed_collection_body["searchOnAdd"] - .as_bool() - .expect("Unable to deserialize 'searchOnAdd'") - }); + let root_folder_path = edit_collection_params.root_folder_path.unwrap_or_else(|| { + detailed_collection_body["rootFolderPath"] + .as_str() + .expect("Unable to deserialize 'rootFolderPath'") + .to_owned() + }); + let search_on_add = edit_collection_params.search_on_add.unwrap_or_else(|| { + detailed_collection_body["searchOnAdd"] + .as_bool() + .expect("Unable to deserialize 'searchOnAdd'") + }); - ( - monitored, - minimum_availability, - quality_profile_id, - root_folder_path, - search_on_add, - ) - } else { - let mut app = self.app.lock().await; - let EditCollectionModal { - path, - search_on_add, - minimum_availability_list, - monitored, - quality_profile_list, - } = app.data.radarr_data.edit_collection_modal.as_ref().unwrap(); - let quality_profile = quality_profile_list.current_selection(); - let quality_profile_id = *app - .data - .radarr_data - .quality_profile_map - .iter() - .filter(|(_, value)| *value == quality_profile) - .map(|(key, _)| key) - .next() - .unwrap(); - - let root_folder_path: String = path.text.clone(); - let monitored = monitored.unwrap_or_default(); - let search_on_add = search_on_add.unwrap_or_default(); - let minimum_availability = minimum_availability_list.current_selection().to_string(); - app.data.radarr_data.edit_collection_modal = None; - - ( - monitored, - minimum_availability, - quality_profile_id, - root_folder_path, - search_on_add, - ) - }; + ( + monitored, + minimum_availability, + quality_profile_id, + root_folder_path, + search_on_add, + ) + }; *detailed_collection_body.get_mut("monitored").unwrap() = json!(monitored); *detailed_collection_body @@ -885,23 +624,17 @@ impl<'a, 'b> Network<'a, 'b> { async fn edit_radarr_indexer( &mut self, - edit_indexer_params: Option, + mut edit_indexer_params: EditIndexerParams, ) -> Result<()> { let detail_event = RadarrEvent::GetIndexers; - let event = RadarrEvent::EditIndexer(None); - let id = if let Some(ref params) = edit_indexer_params { - params.indexer_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .indexers - .current_selection() - .id - }; + let event = RadarrEvent::EditIndexer(edit_indexer_params.clone()); + let id = edit_indexer_params.indexer_id; + if let Some(tag_input_string) = edit_indexer_params.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_radarr_tag_ids_vec(tag_input_string.clone()) + .await; + edit_indexer_params.tags = Some(tag_ids_vec); + } info!("Updating Radarr indexer with ID: {id}"); info!("Fetching indexer details for indexer with ID: {id}"); @@ -926,7 +659,7 @@ impl<'a, 'b> Network<'a, 'b> { info!("Constructing edit indexer body"); - let mut detailed_indexer_body: Value = serde_json::from_str(&response).unwrap(); + let mut detailed_indexer_body: Value = serde_json::from_str(&response)?; let ( name, @@ -938,7 +671,7 @@ impl<'a, 'b> Network<'a, 'b> { seed_ratio, tags, priority, - ) = if let Some(params) = edit_indexer_params { + ) = { let priority = detailed_indexer_body["priority"] .as_i64() .expect("Unable to deserialize 'priority'"); @@ -947,28 +680,28 @@ impl<'a, 'b> Network<'a, 'b> { .unwrap() .iter() .find(|field| field["name"] == "seedCriteria.seedRatio"); - let name = params.name.unwrap_or( + let name = edit_indexer_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( + let enable_rss = edit_indexer_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( + let enable_automatic_search = edit_indexer_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( + let enable_interactive_search = edit_indexer_params.enable_interactive_search.unwrap_or( detailed_indexer_body["enableInteractiveSearch"] .as_bool() .expect("Unable to deserialize 'enableInteractiveSearch'"), ); - let url = params.url.unwrap_or( + let url = edit_indexer_params.url.unwrap_or( detailed_indexer_body["fields"] .as_array() .expect("Unable to deserialize 'fields'") @@ -981,7 +714,7 @@ impl<'a, 'b> Network<'a, 'b> { .expect("Unable to deserialize 'baseUrl value'") .to_owned(), ); - let api_key = params.api_key.unwrap_or( + let api_key = edit_indexer_params.api_key.unwrap_or( detailed_indexer_body["fields"] .as_array() .expect("Unable to deserialize 'fields'") @@ -994,7 +727,7 @@ impl<'a, 'b> Network<'a, 'b> { .expect("Unable to deserialize 'apiKey value'") .to_owned(), ); - let seed_ratio = params.seed_ratio.unwrap_or_else(|| { + let seed_ratio = edit_indexer_params.seed_ratio.unwrap_or_else(|| { if let Some(seed_ratio_field) = seed_ratio_field_option { return seed_ratio_field .get("value") @@ -1006,10 +739,10 @@ impl<'a, 'b> Network<'a, 'b> { String::new() }); - let tags = if params.clear_tags { + let tags = if edit_indexer_params.clear_tags { vec![] } else { - params.tags.unwrap_or( + edit_indexer_params.tags.unwrap_or( detailed_indexer_body["tags"] .as_array() .expect("Unable to deserialize 'tags'") @@ -1018,7 +751,7 @@ impl<'a, 'b> Network<'a, 'b> { .collect(), ) }; - let priority = params.priority.unwrap_or(priority); + let priority = edit_indexer_params.priority.unwrap_or(priority); ( name, @@ -1031,51 +764,6 @@ impl<'a, 'b> Network<'a, 'b> { tags, priority, ) - } else { - let tags = self - .app - .lock() - .await - .data - .radarr_data - .edit_indexer_modal - .as_ref() - .unwrap() - .tags - .text - .clone(); - let tag_ids_vec = self.extract_and_add_radarr_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, - priority, - .. - } = app.data.radarr_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.radarr_data.edit_indexer_modal = None; - - params }; *detailed_indexer_body.get_mut("name").unwrap() = json!(name); @@ -1130,7 +818,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Put, Some(detailed_indexer_body), Some(format!("/{id}")), - None, + Some("forceSave=true".to_owned()), ) .await; @@ -1139,16 +827,18 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn edit_movie(&mut self, edit_movie_params: Option) -> Result<()> { + async fn edit_movie(&mut self, mut edit_movie_params: EditMovieParams) -> Result<()> { info!("Editing Radarr movie"); - let detail_event = RadarrEvent::GetMovieDetails(None); - let event = RadarrEvent::EditMovie(None); + let movie_id = edit_movie_params.movie_id; + let detail_event = RadarrEvent::GetMovieDetails(movie_id); + let event = RadarrEvent::EditMovie(edit_movie_params.clone()); + if let Some(tag_input_string) = edit_movie_params.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_radarr_tag_ids_vec(tag_input_string.clone()) + .await; + edit_movie_params.tags = Some(tag_ids_vec); + } - let (movie_id, _) = if let Some(ref params) = edit_movie_params { - self.extract_movie_id(Some(params.movie_id)).await - } else { - self.extract_movie_id(None).await - }; info!("Fetching movie details for movie with ID: {movie_id}"); let request_props = self @@ -1171,101 +861,53 @@ impl<'a, 'b> Network<'a, 'b> { info!("Constructing edit movie body"); - let mut detailed_movie_body: Value = serde_json::from_str(&response).unwrap(); - let (monitored, minimum_availability, quality_profile_id, root_folder_path, tags) = - if let Some(params) = edit_movie_params { - let monitored = params.monitored.unwrap_or( - detailed_movie_body["monitored"] - .as_bool() - .expect("Unable to deserialize 'monitored'"), - ); - let minimum_availability = params - .minimum_availability - .unwrap_or_else(|| { - serde_json::from_value(detailed_movie_body["minimumAvailability"].clone()) - .expect("Unable to deserialize 'minimumAvailability'") - }) - .to_string(); - let quality_profile_id = params.quality_profile_id.unwrap_or_else(|| { - detailed_movie_body["qualityProfileId"] - .as_i64() - .expect("Unable to deserialize 'qualityProfileId'") - }); - let root_folder_path = params.root_folder_path.unwrap_or_else(|| { - detailed_movie_body["path"] - .as_str() - .expect("Unable to deserialize 'path'") - .to_owned() - }); - let tags = if params.clear_tags { - vec![] - } else { - params.tags.unwrap_or( - detailed_movie_body["tags"] - .as_array() - .expect("Unable to deserialize 'tags'") - .iter() - .map(|item| item.as_i64().expect("Unable to deserialize tag ID")) - .collect(), - ) - }; - - ( - monitored, - minimum_availability, - quality_profile_id, - root_folder_path, - tags, - ) + let mut detailed_movie_body: Value = serde_json::from_str(&response)?; + let (monitored, minimum_availability, quality_profile_id, root_folder_path, tags) = { + let monitored = edit_movie_params.monitored.unwrap_or( + detailed_movie_body["monitored"] + .as_bool() + .expect("Unable to deserialize 'monitored'"), + ); + let minimum_availability = edit_movie_params + .minimum_availability + .unwrap_or_else(|| { + serde_json::from_value(detailed_movie_body["minimumAvailability"].clone()) + .expect("Unable to deserialize 'minimumAvailability'") + }) + .to_string(); + let quality_profile_id = edit_movie_params.quality_profile_id.unwrap_or_else(|| { + detailed_movie_body["qualityProfileId"] + .as_i64() + .expect("Unable to deserialize 'qualityProfileId'") + }); + let root_folder_path = edit_movie_params.root_folder_path.unwrap_or_else(|| { + detailed_movie_body["path"] + .as_str() + .expect("Unable to deserialize 'path'") + .to_owned() + }); + let tags = if edit_movie_params.clear_tags { + vec![] } else { - let tags = self - .app - .lock() - .await - .data - .radarr_data - .edit_movie_modal - .as_ref() - .unwrap() - .tags - .text - .clone(); - let tag_ids_vec = self.extract_and_add_radarr_tag_ids_vec(tags).await; - let mut app = self.app.lock().await; - - let params = { - let EditMovieModal { - monitored, - path, - minimum_availability_list, - quality_profile_list, - .. - } = app.data.radarr_data.edit_movie_modal.as_ref().unwrap(); - let quality_profile = quality_profile_list.current_selection(); - let quality_profile_id = *app - .data - .radarr_data - .quality_profile_map + edit_movie_params.tags.unwrap_or( + detailed_movie_body["tags"] + .as_array() + .expect("Unable to deserialize 'tags'") .iter() - .filter(|(_, value)| *value == quality_profile) - .map(|(key, _)| key) - .next() - .unwrap(); - - ( - monitored.unwrap_or_default(), - minimum_availability_list.current_selection().to_string(), - quality_profile_id, - path.text.clone(), - tag_ids_vec, - ) - }; - - app.data.radarr_data.edit_movie_modal = None; - - params + .map(|item| item.as_i64().expect("Unable to deserialize tag ID")) + .collect(), + ) }; + ( + monitored, + minimum_availability, + quality_profile_id, + root_folder_path, + tags, + ) + }; + *detailed_movie_body.get_mut("monitored").unwrap() = json!(monitored); *detailed_movie_body.get_mut("minimumAvailability").unwrap() = json!(minimum_availability); *detailed_movie_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id); @@ -1334,10 +976,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_credits(&mut self, movie_id: Option) -> Result> { + async fn get_credits(&mut self, movie_id: i64) -> Result> { info!("Fetching Radarr movie credits"); - let event = RadarrEvent::GetMovieCredits(None); - let (_, movie_id_param) = self.extract_movie_id(movie_id).await; + let event = RadarrEvent::GetMovieCredits(movie_id); let request_props = self .request_props_from( @@ -1345,7 +986,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(movie_id_param), + Some(format!("movieId={movie_id}")), ) .await; @@ -1480,14 +1121,11 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_radarr_logs(&mut self, events: Option) -> Result { + async fn get_radarr_logs(&mut self, events: u64) -> Result { info!("Fetching Radarr logs"); let event = RadarrEvent::GetLogs(events); - let params = format!( - "pageSize={}&sortDirection=descending&sortKey=time", - events.unwrap_or(500) - ); + let params = format!("pageSize={}&sortDirection=descending&sortKey=time", events); let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) .await; @@ -1527,19 +1165,18 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_movie_details(&mut self, movie_id: Option) -> Result { + async fn get_movie_details(&mut self, movie_id: i64) -> Result { info!("Fetching Radarr movie details"); - let event = RadarrEvent::GetMovieDetails(None); - let (id, _) = self.extract_movie_id(movie_id).await; + let event = RadarrEvent::GetMovieDetails(movie_id); - info!("Fetching movie details for movie with ID: {id}"); + info!("Fetching movie details for movie with ID: {movie_id}"); let request_props = self .request_props_from( event, RequestMethod::Get, None::<()>, - Some(format!("/{id}")), + Some(format!("/{movie_id}")), None, ) .await; @@ -1683,18 +1320,17 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_movie_history(&mut self, movie_id: Option) -> Result> { + async fn get_movie_history(&mut self, movie_id: i64) -> Result> { info!("Fetching Radarr movie history"); - let event = RadarrEvent::GetMovieHistory(None); + let event = RadarrEvent::GetMovieHistory(movie_id); - let (_, movie_id_param) = self.extract_movie_id(movie_id).await; let request_props = self .request_props_from( event, RequestMethod::Get, None::<()>, None, - Some(movie_id_param), + Some(format!("movieId={movie_id}")), ) .await; @@ -1778,10 +1414,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_movie_releases(&mut self, movie_id: Option) -> Result> { - let (id, movie_id_param) = self.extract_movie_id(movie_id).await; - info!("Fetching releases for movie with ID: {id}"); - let event = RadarrEvent::GetReleases(None); + async fn get_movie_releases(&mut self, movie_id: i64) -> Result> { + info!("Fetching releases for movie with ID: {movie_id}"); + let event = RadarrEvent::GetReleases(movie_id); let request_props = self .request_props_from( @@ -1789,7 +1424,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(movie_id_param), + Some(format!("movieId={movie_id}")), ) .await; @@ -1966,84 +1601,44 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn search_movie(&mut self, query: Option) -> Result> { + async fn search_movie(&mut self, query: String) -> Result> { info!("Searching for specific Radarr movie"); - let event = RadarrEvent::SearchNewMovie(None); - let search = if let Some(search_query) = query { - Ok(search_query.into()) - } else { - self - .app - .lock() - .await - .data - .radarr_data - .add_movie_search - .clone() - .ok_or(anyhow!("Encountered a race condition")) - }; + let event = RadarrEvent::SearchNewMovie(query.clone()); - match search { - Ok(search_string) => { - let request_props = self - .request_props_from( - event, - RequestMethod::Get, - None::<()>, - None, - Some(format!("term={}", encode(&search_string.text))), - ) - .await; + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + None, + Some(format!("term={}", encode(&query))), + ) + .await; - self - .handle_request::<(), Vec>(request_props, |movie_vec, mut app| { - if movie_vec.is_empty() { - app.pop_and_push_navigation_stack( - ActiveRadarrBlock::AddMovieEmptySearchResults.into(), - ); - } else if let Some(add_searched_movies) = - app.data.radarr_data.add_searched_movies.as_mut() - { - add_searched_movies.set_items(movie_vec); - } else { - let mut add_searched_movies = StatefulTable::default(); - add_searched_movies.set_items(movie_vec); - app.data.radarr_data.add_searched_movies = Some(add_searched_movies); - } - }) - .await - } - Err(e) => { - warn!( - "Encountered a race condition: {e}\n \ - This is most likely caused by the user trying to navigate between modals rapidly. \ - Ignoring search request." - ); - Ok(Vec::default()) - } - } + self + .handle_request::<(), Vec>(request_props, |movie_vec, mut app| { + if movie_vec.is_empty() { + app.pop_and_push_navigation_stack(ActiveRadarrBlock::AddMovieEmptySearchResults.into()); + } else if let Some(add_searched_movies) = app.data.radarr_data.add_searched_movies.as_mut() + { + add_searched_movies.set_items(movie_vec); + } else { + let mut add_searched_movies = StatefulTable::default(); + add_searched_movies.set_items(movie_vec); + app.data.radarr_data.add_searched_movies = Some(add_searched_movies); + } + }) + .await } - async fn start_radarr_task(&mut self, task: Option) -> Result { - let event = RadarrEvent::StartTask(None); - let task_name = if let Some(t_name) = task { - t_name - } else { - self - .app - .lock() - .await - .data - .radarr_data - .tasks - .current_selection() - .task_name - } - .to_string(); + async fn start_radarr_task(&mut self, task_name: RadarrTaskName) -> Result { + let event = RadarrEvent::StartTask(task_name); info!("Starting Radarr task: {task_name}"); - let body = CommandBody { name: task_name }; + let body = CommandBody { + name: task_name.to_string(), + }; let request_props = self .request_props_from(event, RequestMethod::Post, Some(body), None, None) @@ -2054,32 +1649,19 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn test_radarr_indexer(&mut self, indexer_id: Option) -> Result { + async fn test_radarr_indexer(&mut self, indexer_id: i64) -> Result { let detail_event = RadarrEvent::GetIndexers; - let event = RadarrEvent::TestIndexer(None); - let id = if let Some(i_id) = indexer_id { - i_id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .indexers - .current_selection() - .id - }; - info!("Testing Radarr indexer with ID: {id}"); + let event = RadarrEvent::TestIndexer(indexer_id); + info!("Testing Radarr indexer with ID: {indexer_id}"); - info!("Fetching indexer details for indexer with ID: {id}"); + info!("Fetching indexer details for indexer with ID: {indexer_id}"); let request_props = self .request_props_from( detail_event, RequestMethod::Get, None::<()>, - Some(format!("/{id}")), + Some(format!("/{indexer_id}")), None, ) .await; @@ -2162,13 +1744,12 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn trigger_automatic_movie_search(&mut self, movie_id: Option) -> Result { + async fn trigger_automatic_movie_search(&mut self, movie_id: i64) -> Result { let event = RadarrEvent::TriggerAutomaticSearch(movie_id); - let (id, _) = self.extract_movie_id(movie_id).await; - info!("Searching indexers for movie with ID: {id}"); + info!("Searching indexers for movie with ID: {movie_id}"); let body = MovieCommandBody { name: "MoviesSearch".to_owned(), - movie_ids: vec![id], + movie_ids: vec![movie_id], }; let request_props = self @@ -2197,13 +1778,12 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn update_and_scan_movie(&mut self, movie_id: Option) -> Result { - let (id, _) = self.extract_movie_id(movie_id).await; - let event = RadarrEvent::UpdateAndScan(None); - info!("Updating and scanning movie with ID: {id}"); + async fn update_and_scan_movie(&mut self, movie_id: i64) -> Result { + let event = RadarrEvent::UpdateAndScan(movie_id); + info!("Updating and scanning movie with ID: {movie_id}"); let body = MovieCommandBody { name: "RefreshMovie".to_owned(), - movie_ids: vec![id], + movie_ids: vec![movie_id], }; let request_props = self @@ -2276,35 +1856,6 @@ impl<'a, 'b> Network<'a, 'b> { }) .collect() } - - async fn extract_movie_id(&mut self, movie_id: Option) -> (i64, String) { - let movie_id = if let Some(id) = movie_id { - id - } else { - self - .app - .lock() - .await - .data - .radarr_data - .movies - .current_selection() - .id - }; - (movie_id, format!("movieId={movie_id}")) - } - - async fn extract_collection_id(&mut self) -> i64 { - self - .app - .lock() - .await - .data - .radarr_data - .collections - .current_selection() - .id - } } fn get_movie_status(has_file: bool, downloads_vec: &[DownloadRecord], movie_id: i64) -> String { diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index fa2b5a0..ed1b898 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -3,32 +3,31 @@ mod test { use std::sync::Arc; use bimap::BiMap; - use chrono::{DateTime, Utc}; - use mockito::{Matcher, Server}; + use chrono::DateTime; + use mockito::Matcher; use pretty_assertions::{assert_eq, assert_str_eq}; use reqwest::Client; use rstest::rstest; use serde_json::{json, Number, Value}; - use strum::IntoEnumIterator; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; - use crate::app::ServarrConfig; + use super::super::*; use crate::models::radarr_models::{ - BlocklistItem, BlocklistItemMovie, CollectionMovie, MediaInfo, MinimumAvailability, - MovieCollection, MovieFile, MovieMonitor, Rating, RatingsList, + AddMovieOptions, BlocklistItem, BlocklistItemMovie, CollectionMovie, EditCollectionParams, + EditMovieParams, IndexerSettings, MediaInfo, MinimumAvailability, MovieCollection, MovieFile, + RadarrTaskName, Rating, RatingsList, }; + use crate::models::servarr_data::radarr::modals::EditMovieModal; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::{ - HostConfig, IndexerField, Language, Quality, QualityWrapper, + EditIndexerParams, HostConfig, IndexerField, Language, Quality, QualityWrapper, }; use crate::models::stateful_table::SortOption; use crate::models::HorizontallyScrollableText; use crate::network::network_tests::test_utils::mock_servarr_api; use crate::App; - use super::super::*; - const MOVIE_JSON: &str = r#"{ "id": 1, "title": "Test", @@ -117,11 +116,11 @@ mod test { #[rstest] fn test_resource_movie( #[values( - RadarrEvent::AddMovie(None), - RadarrEvent::EditMovie(None), + RadarrEvent::AddMovie(AddMovieBody::default()), + RadarrEvent::EditMovie(EditMovieParams::default()), RadarrEvent::GetMovies, - RadarrEvent::GetMovieDetails(None), - RadarrEvent::DeleteMovie(None) + RadarrEvent::GetMovieDetails(0), + RadarrEvent::DeleteMovie(DeleteMovieParams::default()) )] event: RadarrEvent, ) { @@ -130,14 +129,23 @@ mod test { #[rstest] fn test_resource_collection( - #[values(RadarrEvent::GetCollections, RadarrEvent::EditCollection(None))] event: RadarrEvent, + #[values( + RadarrEvent::GetCollections, + RadarrEvent::EditCollection(EditCollectionParams::default()) + )] + event: RadarrEvent, ) { assert_str_eq!(event.resource(), "/collection"); } #[rstest] fn test_resource_indexer( - #[values(RadarrEvent::GetIndexers, RadarrEvent::DeleteIndexer(None))] event: RadarrEvent, + #[values( + RadarrEvent::GetIndexers, + RadarrEvent::DeleteIndexer(0), + RadarrEvent::EditIndexer(EditIndexerParams::default()) + )] + event: RadarrEvent, ) { assert_str_eq!(event.resource(), "/indexer"); } @@ -146,7 +154,7 @@ mod test { fn test_resource_all_indexer_settings( #[values( RadarrEvent::GetAllIndexerSettings, - RadarrEvent::EditAllIndexerSettings(None) + RadarrEvent::EditAllIndexerSettings(IndexerSettings::default()) )] event: RadarrEvent, ) { @@ -156,9 +164,9 @@ mod test { #[rstest] fn test_resource_root_folder( #[values( - RadarrEvent::AddRootFolder(None), + RadarrEvent::AddRootFolder(AddRootFolderBody::default()), RadarrEvent::GetRootFolders, - RadarrEvent::DeleteRootFolder(None) + RadarrEvent::DeleteRootFolder(0) )] event: RadarrEvent, ) { @@ -179,7 +187,10 @@ mod test { #[rstest] fn test_resource_release( - #[values(RadarrEvent::GetReleases(None), RadarrEvent::DownloadRelease(None))] + #[values( + RadarrEvent::GetReleases(0), + RadarrEvent::DownloadRelease(RadarrReleaseDownloadBody::default()) + )] event: RadarrEvent, ) { assert_str_eq!(event.resource(), "/release"); @@ -187,7 +198,7 @@ mod test { #[rstest] fn test_resource_queue( - #[values(RadarrEvent::GetDownloads, RadarrEvent::DeleteDownload(None))] event: RadarrEvent, + #[values(RadarrEvent::GetDownloads, RadarrEvent::DeleteDownload(0))] event: RadarrEvent, ) { assert_str_eq!(event.resource(), "/queue"); } @@ -202,10 +213,10 @@ mod test { #[rstest] fn test_resource_command( #[values( - RadarrEvent::StartTask(None), + RadarrEvent::StartTask(RadarrTaskName::default()), RadarrEvent::GetQueuedEvents, - RadarrEvent::TriggerAutomaticSearch(None), - RadarrEvent::UpdateAndScan(None), + RadarrEvent::TriggerAutomaticSearch(0), + RadarrEvent::UpdateAndScan(0), RadarrEvent::UpdateAllMovies, RadarrEvent::UpdateDownloads, RadarrEvent::UpdateCollections @@ -217,18 +228,18 @@ mod test { #[rstest] #[case(RadarrEvent::ClearBlocklist, "/blocklist/bulk")] - #[case(RadarrEvent::DeleteBlocklistItem(None), "/blocklist")] + #[case(RadarrEvent::DeleteBlocklistItem(1), "/blocklist")] #[case(RadarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")] - #[case(RadarrEvent::GetLogs(Some(500)), "/log")] - #[case(RadarrEvent::SearchNewMovie(None), "/movie/lookup")] - #[case(RadarrEvent::GetMovieCredits(None), "/credit")] - #[case(RadarrEvent::GetMovieHistory(None), "/history/movie")] + #[case(RadarrEvent::GetLogs(500), "/log")] + #[case(RadarrEvent::SearchNewMovie(String::new()), "/movie/lookup")] + #[case(RadarrEvent::GetMovieCredits(0), "/credit")] + #[case(RadarrEvent::GetMovieHistory(0), "/history/movie")] #[case(RadarrEvent::GetDiskSpace, "/diskspace")] #[case(RadarrEvent::GetQualityProfiles, "/qualityprofile")] #[case(RadarrEvent::GetStatus, "/system/status")] #[case(RadarrEvent::GetTasks, "/system/task")] #[case(RadarrEvent::GetUpdates, "/update")] - #[case(RadarrEvent::TestIndexer(None), "/indexer/test")] + #[case(RadarrEvent::TestIndexer(0), "/indexer/test")] #[case(RadarrEvent::TestAllIndexers, "/indexer/testall")] #[case(RadarrEvent::HealthCheck, "/health")] fn test_resource(#[case] event: RadarrEvent, #[case] expected_uri: String) { @@ -325,8 +336,7 @@ mod test { ) .await; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - let date_time = DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap()) - as DateTime; + let date_time = DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap()); if let RadarrSerdeable::SystemStatus(status) = network .handle_radarr_event(RadarrEvent::GetStatus) @@ -508,23 +518,16 @@ mod test { None, Some(release_json), None, - RadarrEvent::GetReleases(None), + RadarrEvent::GetReleases(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); app_arc.lock().await.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Releases(releases_vec) = network - .handle_radarr_event(RadarrEvent::GetReleases(None)) + .handle_radarr_event(RadarrEvent::GetReleases(1)) .await .unwrap() { @@ -568,22 +571,15 @@ mod test { None, Some(release_json), None, - RadarrEvent::GetReleases(None), + RadarrEvent::GetReleases(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::GetReleases(None)) + .handle_radarr_event(RadarrEvent::GetReleases(1)) .await .is_ok()); @@ -631,16 +627,15 @@ mod test { None, Some(add_movie_search_result_json), None, - RadarrEvent::SearchNewMovie(None), + RadarrEvent::SearchNewMovie("test term".into()), None, Some("term=test%20term"), ) .await; - app_arc.lock().await.data.radarr_data.add_movie_search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::AddMovieSearchResults(add_movie_search_results) = network - .handle_radarr_event(RadarrEvent::SearchNewMovie(None)) + .handle_radarr_event(RadarrEvent::SearchNewMovie("test term".into())) .await .unwrap() { @@ -668,51 +663,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_search_new_movie_event_uses_provided_query() { - let add_movie_search_result_json = json!([{ - "tmdbId": 1234, - "title": "Test", - "originalLanguage": { "id": 1, "name": "English" }, - "status": "released", - "overview": "New movie blah blah blah", - "genres": ["cool", "family", "fun"], - "year": 2023, - "runtime": 120, - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - }]); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(add_movie_search_result_json), - None, - RadarrEvent::SearchNewMovie(None), - None, - Some("term=test%20term"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::AddMovieSearchResults(add_movie_search_results) = network - .handle_radarr_event(RadarrEvent::SearchNewMovie(Some("test term".into()))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(add_movie_search_results, vec![add_movie_search_result()]); - } - } - #[tokio::test] async fn test_handle_start_radarr_task_event() { let response = json!({ "test": "test"}); @@ -723,25 +673,17 @@ mod test { })), Some(response.clone()), None, - RadarrEvent::StartTask(None), + RadarrEvent::StartTask(RadarrTaskName::ApplicationCheckUpdate), None, None, ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .tasks - .set_items(vec![RadarrTask { - task_name: RadarrTaskName::default(), - ..RadarrTask::default() - }]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Value(value) = network - .handle_radarr_event(RadarrEvent::StartTask(None)) + .handle_radarr_event(RadarrEvent::StartTask( + RadarrTaskName::ApplicationCheckUpdate, + )) .await .unwrap() { @@ -757,16 +699,15 @@ mod test { None, Some(json!([])), None, - RadarrEvent::SearchNewMovie(None), + RadarrEvent::SearchNewMovie("test term".into()), None, Some("term=test%20term"), ) .await; - app_arc.lock().await.data.radarr_data.add_movie_search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::SearchNewMovie(None)) + .handle_radarr_event(RadarrEvent::SearchNewMovie("test term".into())) .await .is_ok()); @@ -784,84 +725,6 @@ mod test { ); } - #[tokio::test] - async fn test_handle_search_new_movie_event_no_panic_on_race_condition() { - let resource = format!( - "{}?term=test%20term", - RadarrEvent::SearchNewMovie(None).resource() - ); - let mut server = Server::new_async().await; - let mut async_server = server - .mock( - &RequestMethod::Get.to_string().to_uppercase(), - format!("/api/v3{resource}").as_str(), - ) - .match_header("X-Api-Key", "test1234"); - async_server = async_server.expect_at_most(0).create_async().await; - - let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); - let port = Some( - server.host_with_port().split(':').collect::>()[1] - .parse() - .unwrap(), - ); - let mut app = App::default(); - let radarr_config = ServarrConfig { - host, - port, - api_token: "test1234".to_owned(), - ..ServarrConfig::default() - }; - app.config.radarr = Some(radarr_config); - let app_arc = Arc::new(Mutex::new(app)); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::SearchNewMovie(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .add_searched_movies - .is_none()); - assert_eq!( - app_arc.lock().await.get_current_route(), - ActiveRadarrBlock::Movies.into() - ); - } - - #[tokio::test] - async fn test_handle_start_radarr_task_event_uses_provided_task_name() { - let response = json!({ "test": "test"}); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "ApplicationCheckUpdate" - })), - Some(response.clone()), - None, - RadarrEvent::StartTask(None), - None, - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::Value(value) = network - .handle_radarr_event(RadarrEvent::StartTask(Some(RadarrTaskName::default()))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(value, response); - } - } - #[tokio::test] async fn test_handle_test_radarr_indexer_event_error() { let indexer_details_json = json!({ @@ -906,7 +769,7 @@ mod test { let async_test_server = server .mock( "POST", - format!("/api/v3{}", RadarrEvent::TestIndexer(None).resource()).as_str(), + format!("/api/v3{}", RadarrEvent::TestIndexer(1).resource()).as_str(), ) .with_status(400) .match_header("X-Api-Key", "test1234") @@ -914,17 +777,10 @@ mod test { .with_body(response_json.to_string()) .create_async() .await; - app_arc - .lock() - .await - .data - .radarr_data - .indexers - .set_items(vec![indexer()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Value(value) = network - .handle_radarr_event(RadarrEvent::TestIndexer(None)) + .handle_radarr_event(RadarrEvent::TestIndexer(1)) .await .unwrap() { @@ -975,7 +831,7 @@ mod test { let async_test_server = server .mock( "POST", - format!("/api/v3{}", RadarrEvent::TestIndexer(None).resource()).as_str(), + format!("/api/v3{}", RadarrEvent::TestIndexer(1).resource()).as_str(), ) .with_status(200) .match_header("X-Api-Key", "test1234") @@ -983,17 +839,10 @@ mod test { .with_body("{}") .create_async() .await; - app_arc - .lock() - .await - .data - .radarr_data - .indexers - .set_items(vec![indexer()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Value(value) = network - .handle_radarr_event(RadarrEvent::TestIndexer(None)) + .handle_radarr_event(RadarrEvent::TestIndexer(1)) .await .unwrap() { @@ -1007,64 +856,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_test_radarr_indexer_event_success_uses_provided_id() { - let indexer_details_json = json!({ - "enableRss": true, - "enableAutomaticSearch": true, - "enableInteractiveSearch": true, - "name": "Test Indexer", - "fields": [ - { - "name": "baseUrl", - "value": "https://test.com", - }, - { - "name": "apiKey", - "value": "", - }, - { - "name": "seedCriteria.seedRatio", - "value": "1.2", - }, - ], - "tags": [1], - "id": 1 - }); - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(indexer_details_json.clone()), - None, - RadarrEvent::GetIndexers, - Some("/1"), - None, - ) - .await; - let async_test_server = server - .mock( - "POST", - format!("/api/v3{}", RadarrEvent::TestIndexer(None).resource()).as_str(), - ) - .with_status(200) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(indexer_details_json.clone())) - .with_body("{}") - .create_async() - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::Value(value) = network - .handle_radarr_event(RadarrEvent::TestIndexer(Some(1))) - .await - .unwrap() - { - async_details_server.assert_async().await; - async_test_server.assert_async().await; - assert_eq!(value, json!({})); - } - } - #[tokio::test] async fn test_handle_test_all_radarr_indexers_event() { let indexers = vec![ @@ -1172,39 +963,7 @@ mod test { })), Some(json!({})), None, - RadarrEvent::TriggerAutomaticSearch(None), - None, - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::TriggerAutomaticSearch(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_trigger_automatic_movie_search_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "MoviesSearch", - "movieIds": [ 1 ] - })), - Some(json!({})), - None, - RadarrEvent::TriggerAutomaticSearch(None), + RadarrEvent::TriggerAutomaticSearch(1), None, None, ) @@ -1212,7 +971,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::TriggerAutomaticSearch(Some(1))) + .handle_radarr_event(RadarrEvent::TriggerAutomaticSearch(1)) .await .is_ok()); @@ -1229,39 +988,7 @@ mod test { })), Some(json!({})), None, - RadarrEvent::UpdateAndScan(None), - None, - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::UpdateAndScan(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_and_scan_movie_event_uses_provied_movie_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshMovie", - "movieIds": [ 1 ] - })), - Some(json!({})), - None, - RadarrEvent::UpdateAndScan(None), + RadarrEvent::UpdateAndScan(1), None, None, ) @@ -1269,7 +996,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::UpdateAndScan(Some(1))) + .handle_radarr_event(RadarrEvent::UpdateAndScan(1)) .await .is_ok()); @@ -1357,24 +1084,17 @@ mod test { None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), None, - RadarrEvent::GetMovieDetails(None), + RadarrEvent::GetMovieDetails(1), Some("/1"), None, ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); app_arc.lock().await.data.radarr_data.quality_profile_map = BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Movie(movie) = network - .handle_radarr_event(RadarrEvent::GetMovieDetails(None)) + .handle_radarr_event(RadarrEvent::GetMovieDetails(1)) .await .unwrap() { @@ -1444,31 +1164,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_movie_details_event_uses_provided_id() { - let response: Movie = serde_json::from_str(MOVIE_JSON).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(MOVIE_JSON).unwrap()), - None, - RadarrEvent::GetMovieDetails(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::Movie(movie) = network - .handle_radarr_event(RadarrEvent::GetMovieDetails(Some(1))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(movie, response); - } - } - #[tokio::test] async fn test_handle_get_movie_details_event_empty_options_give_correct_defaults() { let movie_json_with_missing_fields = json!({ @@ -1499,24 +1194,17 @@ mod test { None, Some(movie_json_with_missing_fields), None, - RadarrEvent::GetMovieDetails(None), + RadarrEvent::GetMovieDetails(1), Some("/1"), None, ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); app_arc.lock().await.data.radarr_data.quality_profile_map = BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::GetMovieDetails(None)) + .handle_radarr_event(RadarrEvent::GetMovieDetails(1)) .await .is_ok()); @@ -1572,23 +1260,16 @@ mod test { None, Some(movie_history_item_json), None, - RadarrEvent::GetMovieHistory(None), + RadarrEvent::GetMovieHistory(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); app_arc.lock().await.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::MovieHistoryItems(history) = network - .handle_radarr_event(RadarrEvent::GetMovieHistory(None)) + .handle_radarr_event(RadarrEvent::GetMovieHistory(1)) .await .unwrap() { @@ -1610,39 +1291,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_movie_history_event_uses_provided_id() { - let movie_history_item_json = json!([{ - "sourceTitle": "Test", - "quality": { "quality": { "name": "HD - 1080p" }}, - "languages": [ { "id": 1, "name": "English" } ], - "date": "2022-12-30T07:37:56Z", - "eventType": "grabbed" - }]); - let response: Vec = - serde_json::from_value(movie_history_item_json.clone()).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(movie_history_item_json), - None, - RadarrEvent::GetMovieHistory(None), - None, - Some("movieId=1"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::MovieHistoryItems(history) = network - .handle_radarr_event(RadarrEvent::GetMovieHistory(Some(1))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(history, response); - } - } - #[tokio::test] async fn test_handle_get_movie_history_event_empty_movie_details_modal() { let movie_history_item_json = json!([{ @@ -1657,22 +1305,15 @@ mod test { None, Some(movie_history_item_json), None, - RadarrEvent::GetMovieHistory(None), + RadarrEvent::GetMovieHistory(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::GetMovieHistory(None)) + .handle_radarr_event(RadarrEvent::GetMovieHistory(1)) .await .is_ok()); @@ -2502,7 +2143,7 @@ mod test { None, Some(logs_response_json), None, - RadarrEvent::GetLogs(None), + RadarrEvent::GetLogs(500), None, Some("pageSize=500&sortDirection=descending&sortKey=time"), ) @@ -2510,75 +2151,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::LogResponse(logs) = network - .handle_radarr_event(RadarrEvent::GetLogs(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.radarr_data.logs.items, - expected_logs - ); - assert!(app_arc - .lock() - .await - .data - .radarr_data - .logs - .current_selection() - .text - .contains("INFO")); - assert_eq!(logs, response); - } - } - - #[tokio::test] - async fn test_handle_get_radarr_logs_event_uses_provided_events() { - let expected_logs = vec![ - HorizontallyScrollableText::from( - "2023-05-20 21:29:16 UTC|FATAL|RadarrError|Some.Big.Bad.Exception|test exception", - ), - HorizontallyScrollableText::from("2023-05-20 21:29:16 UTC|INFO|TestLogger|test message"), - ]; - let logs_response_json = json!({ - "page": 1, - "pageSize": 1000, - "sortKey": "time", - "sortDirection": "descending", - "totalRecords": 2, - "records": [ - { - "time": "2023-05-20T21:29:16Z", - "level": "info", - "logger": "TestLogger", - "message": "test message", - "id": 1 - }, - { - "time": "2023-05-20T21:29:16Z", - "level": "fatal", - "logger": "RadarrError", - "exception": "test exception", - "exceptionType": "Some.Big.Bad.Exception", - "id": 2 - } - ] - }); - let response: LogResponse = serde_json::from_value(logs_response_json.clone()).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(logs_response_json), - None, - RadarrEvent::GetLogs(Some(1000)), - None, - Some("pageSize=1000&sortDirection=descending&sortKey=time"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::LogResponse(logs) = network - .handle_radarr_event(RadarrEvent::GetLogs(Some(1000))) + .handle_radarr_event(RadarrEvent::GetLogs(500)) .await .unwrap() { @@ -2974,23 +2547,16 @@ mod test { None, Some(credits_json), None, - RadarrEvent::GetMovieCredits(None), + RadarrEvent::GetMovieCredits(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); app_arc.lock().await.data.radarr_data.movie_details_modal = Some(MovieDetailsModal::default()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let RadarrSerdeable::Credits(credits) = network - .handle_radarr_event(RadarrEvent::GetMovieCredits(None)) + .handle_radarr_event(RadarrEvent::GetMovieCredits(1)) .await .unwrap() { @@ -3004,44 +2570,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_movie_credits_event_uses_provided_id() { - let credits_json = json!([ - { - "personName": "Madison Clarke", - "character": "Johnny Blaze", - "type": "cast", - }, - { - "personName": "Alex Clarke", - "department": "Music", - "job": "Composition", - "type": "crew", - } - ]); - let response: Vec = serde_json::from_value(credits_json.clone()).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(credits_json), - None, - RadarrEvent::GetMovieCredits(None), - None, - Some("movieId=1"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let RadarrSerdeable::Credits(credits) = network - .handle_radarr_event(RadarrEvent::GetMovieCredits(Some(1))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(credits, response); - } - } - #[tokio::test] async fn test_handle_get_movie_credits_event_empty_movie_details_modal() { let credits_json = json!([ @@ -3062,22 +2590,15 @@ mod test { None, Some(credits_json), None, - RadarrEvent::GetMovieCredits(None), + RadarrEvent::GetMovieCredits(1), None, Some("movieId=1"), ) .await; - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::GetMovieCredits(None)) + .handle_radarr_event(RadarrEvent::GetMovieCredits(1)) .await .is_ok()); @@ -3091,61 +2612,29 @@ mod test { #[tokio::test] async fn test_handle_delete_movie_event() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteMovie(None), - Some("/1"), - Some("deleteFiles=true&addImportExclusion=true"), - ) - .await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.movies.set_items(vec![movie()]); - app.data.radarr_data.delete_movie_files = true; - app.data.radarr_data.add_list_exclusion = true; - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DeleteMovie(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(!app_arc.lock().await.data.radarr_data.delete_movie_files); - assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion); - } - - #[tokio::test] - async fn test_handle_delete_movie_event_use_provided_params() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteMovie(None), - Some("/1"), - Some("deleteFiles=true&addImportExclusion=true"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); let delete_movie_params = DeleteMovieParams { id: 1, delete_movie_files: true, add_list_exclusion: true, }; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Delete, + None, + None, + None, + RadarrEvent::DeleteMovie(delete_movie_params.clone()), + Some("/1"), + Some("deleteFiles=true&addImportExclusion=true"), + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DeleteMovie(Some(delete_movie_params))) + .handle_radarr_event(RadarrEvent::DeleteMovie(delete_movie_params)) .await .is_ok()); async_server.assert_async().await; - assert!(!app_arc.lock().await.data.radarr_data.delete_movie_files); - assert!(!app_arc.lock().await.data.radarr_data.add_list_exclusion); } #[tokio::test] @@ -3199,36 +2688,7 @@ mod test { None, None, None, - RadarrEvent::DeleteBlocklistItem(None), - Some("/1"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .blocklist - .set_items(vec![blocklist_item()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DeleteBlocklistItem(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_blocklist_item_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteBlocklistItem(None), + RadarrEvent::DeleteBlocklistItem(1), Some("/1"), None, ) @@ -3236,7 +2696,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DeleteBlocklistItem(Some(1))) + .handle_radarr_event(RadarrEvent::DeleteBlocklistItem(1)) .await .is_ok()); @@ -3250,36 +2710,7 @@ mod test { None, None, None, - RadarrEvent::DeleteDownload(None), - Some("/1"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .downloads - .set_items(vec![download_record()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DeleteDownload(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_radarr_download_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteDownload(None), + RadarrEvent::DeleteDownload(1), Some("/1"), None, ) @@ -3287,7 +2718,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DeleteDownload(Some(1))) + .handle_radarr_event(RadarrEvent::DeleteDownload(1)) .await .is_ok()); @@ -3301,36 +2732,7 @@ mod test { None, None, None, - RadarrEvent::DeleteIndexer(None), - Some("/1"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .indexers - .set_items(vec![indexer()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DeleteIndexer(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_radarr_indexer_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteIndexer(None), + RadarrEvent::DeleteIndexer(1), Some("/1"), None, ) @@ -3338,7 +2740,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DeleteIndexer(Some(1))) + .handle_radarr_event(RadarrEvent::DeleteIndexer(1)) .await .is_ok()); @@ -3352,36 +2754,7 @@ mod test { None, None, None, - RadarrEvent::DeleteRootFolder(None), - Some("/1"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .radarr_data - .root_folders - .set_items(vec![root_folder()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DeleteRootFolder(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_radarr_root_folder_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - RadarrEvent::DeleteRootFolder(None), + RadarrEvent::DeleteRootFolder(1), Some("/1"), None, ) @@ -3389,16 +2762,15 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DeleteRootFolder(Some(1))) + .handle_radarr_event(RadarrEvent::DeleteRootFolder(1)) .await .is_ok()); async_server.assert_async().await; } - #[rstest] #[tokio::test] - async fn test_handle_add_movie_event(#[values(true, false)] movie_details_context: bool) { + async fn test_handle_add_movie_event() { let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Post, Some(json!({ @@ -3416,104 +2788,14 @@ mod test { })), Some(json!({})), None, - RadarrEvent::AddMovie(None), + RadarrEvent::AddMovie(AddMovieBody::default()), None, None, ) .await; - - { - let mut app = app_arc.lock().await; - let mut add_movie_modal = AddMovieModal { - tags: "usenet, testing".into(), - ..AddMovieModal::default() - }; - add_movie_modal.root_folder_list.set_items(vec![ - RootFolder { - id: 1, - path: "/nfs".to_owned(), - accessible: true, - free_space: 219902325555200, - unmapped_folders: None, - }, - RootFolder { - id: 2, - path: "/nfs2".to_owned(), - accessible: true, - free_space: 21990232555520, - unmapped_folders: None, - }, - ]); - add_movie_modal.root_folder_list.state.select(Some(1)); - add_movie_modal - .quality_profile_list - .set_items(vec!["HD - 1080p".to_owned()]); - add_movie_modal - .monitor_list - .set_items(Vec::from_iter(MovieMonitor::iter())); - add_movie_modal - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.add_movie_modal = Some(add_movie_modal); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - if movie_details_context { - app - .data - .radarr_data - .collection_movies - .set_items(vec![collection_movie()]); - app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into()); - } else { - let mut add_searched_movies = StatefulTable::default(); - add_searched_movies.set_items(vec![add_movie_search_result()]); - app.data.radarr_data.add_searched_movies = Some(add_searched_movies); - } - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::AddMovie(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .add_movie_modal - .is_none()); - } - - #[tokio::test] - async fn test_handle_add_movie_event_uses_provided_body() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "tmdbId": 1234, - "title": "Test", - "rootFolderPath": "/nfs2", - "minimumAvailability": "announced", - "monitored": true, - "qualityProfileId": 2222, - "tags": [1, 2], - "addOptions": { - "monitor": "movieOnly", - "searchForMovie": true - } - })), - Some(json!({})), - None, - RadarrEvent::AddMovie(None), - None, - None, - ) - .await; - let body = AddMovieBody { + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let add_movie_body = AddMovieBody { tmdb_id: 1234, title: "Test".to_owned(), root_folder_path: "/nfs2".to_owned(), @@ -3521,6 +2803,7 @@ mod test { monitored: true, quality_profile_id: 2222, tags: vec![1, 2], + tag_input_string: Some("usenet, testing".into()), add_options: AddMovieOptions { monitor: "movieOnly".to_owned(), search_for_movie: true, @@ -3530,26 +2813,19 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::AddMovie(Some(body))) + .handle_radarr_event(RadarrEvent::AddMovie(add_movie_body)) .await .is_ok()); async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .add_movie_modal - .is_none()); } #[tokio::test] - async fn test_handle_add_movie_event_reuse_existing_table_if_search_already_performed() { + async fn test_handle_add_movie_event_does_not_overwrite_tags_field_if_tag_input_string_is_none() { let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Post, Some(json!({ - "tmdbId": 5678, + "tmdbId": 1234, "title": "Test", "rootFolderPath": "/nfs2", "minimumAvailability": "announced", @@ -3563,86 +2839,35 @@ mod test { })), Some(json!({})), None, - RadarrEvent::AddMovie(None), + RadarrEvent::AddMovie(AddMovieBody::default()), None, None, ) .await; - - { - let mut app = app_arc.lock().await; - let mut add_movie_modal = AddMovieModal { - tags: "usenet, testing".into(), - ..AddMovieModal::default() - }; - add_movie_modal.root_folder_list.set_items(vec![ - RootFolder { - id: 1, - path: "/nfs".to_owned(), - accessible: true, - free_space: 219902325555200, - unmapped_folders: None, - }, - RootFolder { - id: 2, - path: "/nfs2".to_owned(), - accessible: true, - free_space: 21990232555520, - unmapped_folders: None, - }, - ]); - add_movie_modal.root_folder_list.state.select(Some(1)); - add_movie_modal - .quality_profile_list - .set_items(vec!["HD - 1080p".to_owned()]); - add_movie_modal - .monitor_list - .set_items(Vec::from_iter(MovieMonitor::iter())); - add_movie_modal - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.add_movie_modal = Some(add_movie_modal); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - let secondary_search_result = AddMovieSearchResult { - tmdb_id: 5678, - ..add_movie_search_result() - }; - let mut add_searched_movies = StatefulTable::default(); - add_searched_movies.set_items(vec![add_movie_search_result(), secondary_search_result]); - add_searched_movies.scroll_to_bottom(); - app.data.radarr_data.add_searched_movies = Some(add_searched_movies); - } + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let add_movie_body = AddMovieBody { + tmdb_id: 1234, + title: "Test".to_owned(), + root_folder_path: "/nfs2".to_owned(), + minimum_availability: "announced".to_owned(), + monitored: true, + quality_profile_id: 2222, + tags: vec![1, 2], + tag_input_string: None, + add_options: AddMovieOptions { + monitor: "movieOnly".to_owned(), + search_for_movie: true, + }, + }; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::AddMovie(None)) + .handle_radarr_event(RadarrEvent::AddMovie(add_movie_body)) .await .is_ok()); async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .add_movie_modal - .is_none()); - assert_eq!( - app_arc - .lock() - .await - .data - .radarr_data - .add_searched_movies - .as_ref() - .unwrap() - .current_selection() - .tmdb_id, - 5678 - ); } #[tokio::test] @@ -3654,60 +2879,22 @@ mod test { })), Some(json!({})), None, - RadarrEvent::AddRootFolder(None), + RadarrEvent::AddRootFolder(AddRootFolderBody::default()), None, None, ) .await; - - app_arc.lock().await.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); + let add_root_folder_body = AddRootFolderBody { + path: "/nfs/test".to_owned(), + }; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::AddRootFolder(None)) + .handle_radarr_event(RadarrEvent::AddRootFolder(add_root_folder_body)) .await .is_ok()); async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .edit_root_folder - .is_none()); - } - - #[tokio::test] - async fn test_handle_add_radarr_root_folder_event_uses_provided_path() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "path": "/test/test" - })), - Some(json!({})), - None, - RadarrEvent::AddRootFolder(None), - None, - None, - ) - .await; - - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::AddRootFolder(Some("/test/test".to_owned()))) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .edit_root_folder - .is_none()); } #[tokio::test] @@ -3728,60 +2915,15 @@ mod test { Some(indexer_settings_json), None, None, - RadarrEvent::EditAllIndexerSettings(None), + RadarrEvent::EditAllIndexerSettings(indexer_settings()), None, None, ) .await; - - app_arc.lock().await.data.radarr_data.indexer_settings = Some(indexer_settings()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditAllIndexerSettings(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .radarr_data - .indexer_settings - .is_none()); - } - - #[tokio::test] - async fn test_handle_edit_all_radarr_indexer_settings_event_uses_provided_settings() { - let indexer_settings_json = json!({ - "minimumAge": 0, - "maximumSize": 0, - "retention": 0, - "rssSyncInterval": 60, - "preferIndexerFlags": false, - "availabilityDelay": 0, - "allowHardcodedSubs": true, - "whitelistedHardcodedSubs": "", - "id": 1 - }); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Put, - Some(indexer_settings_json), - None, - None, - RadarrEvent::EditAllIndexerSettings(None), - None, - None, - ) - .await; - - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::EditAllIndexerSettings( - Some(indexer_settings()) - )) + .handle_radarr_event(RadarrEvent::EditAllIndexerSettings(indexer_settings())) .await .is_ok()); @@ -3827,132 +2969,6 @@ mod test { *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); *expected_body.get_mut("rootFolderPath").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("searchOnAdd").unwrap() = json!(false); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(detailed_collection_body), - None, - RadarrEvent::GetCollections, - Some("/123"), - None, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!( - "/api/v3{}/123", - RadarrEvent::EditCollection(None).resource() - ) - .as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; - { - let mut app = app_arc.lock().await; - let mut edit_collection_modal = EditCollectionModal { - path: "/nfs/Test Path".into(), - monitored: Some(false), - search_on_add: Some(false), - ..EditCollectionModal::default() - }; - edit_collection_modal - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - edit_collection_modal - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.edit_collection_modal = Some(edit_collection_modal); - app.data.radarr_data.collections.set_items(vec![Collection { - monitored: false, - search_on_add: false, - ..collection() - }]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::EditCollection(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.radarr_data.edit_collection_modal.is_none()); - } - - #[tokio::test] - async fn test_handle_edit_collection_event_uses_provided_parameters() { - let detailed_collection_body = json!({ - "id": 123, - "title": "Test Collection", - "rootFolderPath": "/nfs/movies", - "searchOnAdd": true, - "monitored": true, - "minimumAvailability": "released", - "overview": "Collection blah blah blah", - "qualityProfileId": 2222, - "movies": [ - { - "title": "Test", - "overview": "Collection blah blah blah", - "year": 2023, - "runtime": 120, - "tmdbId": 1234, - "genres": ["cool", "family", "fun"], - "ratings": { - "imdb": { - "value": 9.9 - }, - "tmdb": { - "value": 9.9 - }, - "rottenTomatoes": { - "value": 9.9 - } - } - } - ] - }); - let mut expected_body = detailed_collection_body.clone(); - *expected_body.get_mut("monitored").unwrap() = json!(false); - *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); - *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); - *expected_body.get_mut("rootFolderPath").unwrap() = json!("/nfs/Test Path"); - *expected_body.get_mut("searchOnAdd").unwrap() = json!(false); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(detailed_collection_body), - None, - RadarrEvent::GetCollections, - Some("/123"), - None, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!( - "/api/v3{}/123", - RadarrEvent::EditCollection(None).resource() - ) - .as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; let edit_collection_params = EditCollectionParams { collection_id: 123, monitored: Some(false), @@ -3961,10 +2977,35 @@ mod test { root_folder_path: Some("/nfs/Test Path".to_owned()), search_on_add: Some(false), }; + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(detailed_collection_body), + None, + RadarrEvent::GetCollections, + Some("/123"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!( + "/api/v3{}/123", + RadarrEvent::EditCollection(edit_collection_params.clone()).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditCollection(Some(edit_collection_params))) + .handle_radarr_event(RadarrEvent::EditCollection(edit_collection_params)) .await .is_ok()); @@ -3973,7 +3014,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_collection_event_uses_provided_parameters_defaults_to_previous_values_when_none( + async fn test_handle_edit_collection_event_defaults_to_previous_values_when_no_params_are_provided( ) { let detailed_collection_body = json!({ "id": 123, @@ -4023,12 +3064,16 @@ mod test { None, ) .await; + let edit_collection_params = EditCollectionParams { + collection_id: 123, + ..EditCollectionParams::default() + }; let async_edit_server = server .mock( "PUT", format!( "/api/v3{}/123", - RadarrEvent::EditCollection(None).resource() + RadarrEvent::EditCollection(edit_collection_params).resource() ) .as_str(), ) @@ -4044,7 +3089,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditCollection(Some(edit_collection_params))) + .handle_radarr_event(RadarrEvent::EditCollection(edit_collection_params)) .await .is_ok()); @@ -4100,7 +3145,19 @@ mod test { "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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, @@ -4114,43 +3171,127 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditIndexer(None).resource()).as_str(), + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).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.radarr_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(), - priority: 0, - }; - app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal); - app.data.radarr_data.indexers.set_items(vec![indexer()]); - } + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditIndexer(None)) + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); async_details_server.assert_async().await; async_edit_server.assert_async().await; + } - let app = app_arc.lock().await; - assert!(app.data.radarr_data.edit_indexer_modal.is_none()); + #[tokio::test] + async fn test_handle_edit_radarr_indexer_event_does_not_overwrite_tags_vec_if_tag_input_string_is_none( + ) { + 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": 0, + "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(0), + ..EditIndexerParams::default() + }; + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(indexer_details_json), + None, + RadarrEvent::GetIndexers, + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; } #[tokio::test] @@ -4194,7 +3335,19 @@ mod test { "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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, @@ -4208,52 +3361,28 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditIndexer(None).resource()).as_str(), + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).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.radarr_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(), - priority: 0, - }; - app.data.radarr_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.radarr_data.indexers.set_items(vec![indexer]); - } + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditIndexer(None)) + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); async_details_server.assert_async().await; async_edit_server.assert_async().await; - - let app = app_arc.lock().await; - assert!(app.data.radarr_data.edit_indexer_modal.is_none()); } #[tokio::test] @@ -4304,123 +3433,6 @@ mod test { "tags": [1, 2], "id": 1 }); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(indexer_details_json), - None, - RadarrEvent::GetIndexers, - Some("/1"), - None, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!("/api/v3{}/1", RadarrEvent::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.radarr_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(), - priority: 0, - }; - app.data.radarr_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.radarr_data.indexers.set_items(vec![indexer]); - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::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.radarr_data.edit_indexer_modal.is_none()); - } - - #[tokio::test] - async fn test_handle_edit_radarr_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()), @@ -4430,11 +3442,10 @@ mod test { 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), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), ..EditIndexerParams::default() }; - let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, @@ -4448,17 +3459,23 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditIndexer(None).resource()).as_str(), + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_indexer_edit_body_json)) .create_async() .await; + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); @@ -4467,8 +3484,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_radarr_indexer_event_uses_provided_parameters_defaults_to_previous_values( - ) { + async fn test_handle_edit_radarr_indexer_event_defaults_to_previous_values() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -4510,7 +3526,11 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditIndexer(None).resource()).as_str(), + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") @@ -4520,7 +3540,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); @@ -4529,8 +3549,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_radarr_indexer_event_uses_provided_parameters_clears_tags_when_clear_tags_is_true( - ) { + async fn test_handle_edit_radarr_indexer_event_clears_tags_when_clear_tags_is_true() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -4596,7 +3615,11 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditIndexer(None).resource()).as_str(), + format!( + "/api/v3{}/1?forceSave=true", + RadarrEvent::EditIndexer(edit_indexer_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") @@ -4606,7 +3629,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_radarr_event(RadarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); @@ -4622,13 +3645,22 @@ mod test { *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("tags").unwrap() = json!([1, 2]); + let edit_movie_params = EditMovieParams { + movie_id: 1, + monitored: Some(false), + minimum_availability: Some(MinimumAvailability::Announced), + quality_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".into()), + ..EditMovieParams::default() + }; let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), None, - RadarrEvent::GetMovieDetails(None), + RadarrEvent::GetMovieDetails(1), Some("/1"), None, ) @@ -4636,80 +3668,38 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditMovie(None).resource()).as_str(), + format!( + "/api/v3{}/1", + RadarrEvent::EditMovie(edit_movie_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .create_async() .await; - { - let mut app = app_arc.lock().await; - app.data.radarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - let mut edit_movie = EditMovieModal { - tags: "usenet, testing".to_owned().into(), - path: "/nfs/Test Path".to_owned().into(), - monitored: Some(false), - ..EditMovieModal::default() - }; - edit_movie - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - edit_movie - .minimum_availability_list - .set_items(Vec::from_iter(MinimumAvailability::iter())); - app.data.radarr_data.edit_movie_modal = Some(edit_movie); - app.data.radarr_data.movies.set_items(vec![Movie { - monitored: false, - ..movie() - }]); - app.data.radarr_data.quality_profile_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); - } + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditMovie(None)) + .handle_radarr_event(RadarrEvent::EditMovie(edit_movie_params)) .await .is_ok()); async_details_server.assert_async().await; async_edit_server.assert_async().await; - - let app = app_arc.lock().await; - assert!(app.data.radarr_data.edit_movie_modal.is_none()); } #[tokio::test] - async fn test_handle_edit_movie_event_uses_provided_parameters() { + async fn test_handle_edit_movie_event_does_not_overwrite_tags_vec_if_tag_input_string_is_none() { let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); *expected_body.get_mut("monitored").unwrap() = json!(false); *expected_body.get_mut("minimumAvailability").unwrap() = json!("announced"); *expected_body.get_mut("qualityProfileId").unwrap() = json!(1111); *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("tags").unwrap() = json!([1, 2]); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(MOVIE_JSON).unwrap()), - None, - RadarrEvent::GetMovieDetails(None), - Some("/1"), - None, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!("/api/v3{}/1", RadarrEvent::EditMovie(None).resource()).as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; let edit_movie_params = EditMovieParams { movie_id: 1, monitored: Some(false), @@ -4719,10 +3709,36 @@ mod test { tags: Some(vec![1, 2]), ..EditMovieParams::default() }; + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(MOVIE_JSON).unwrap()), + None, + RadarrEvent::GetMovieDetails(1), + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!( + "/api/v3{}/1", + RadarrEvent::EditMovie(edit_movie_params.clone()).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + app_arc.lock().await.data.radarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditMovie(Some(edit_movie_params))) + .handle_radarr_event(RadarrEvent::EditMovie(edit_movie_params)) .await .is_ok()); @@ -4731,36 +3747,40 @@ mod test { } #[tokio::test] - async fn test_handle_edit_movie_event_uses_provided_parameters_defaults_to_previous_values() { + async fn test_handle_edit_movie_event_defaults_to_previous_values() { let expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), None, - RadarrEvent::GetMovieDetails(None), + RadarrEvent::GetMovieDetails(1), Some("/1"), None, ) .await; + let edit_movie_params = EditMovieParams { + movie_id: 1, + ..EditMovieParams::default() + }; let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditMovie(None).resource()).as_str(), + format!( + "/api/v3{}/1", + RadarrEvent::EditMovie(edit_movie_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .create_async() .await; - let edit_movie_params = EditMovieParams { - movie_id: 1, - ..EditMovieParams::default() - }; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditMovie(Some(edit_movie_params))) + .handle_radarr_event(RadarrEvent::EditMovie(edit_movie_params)) .await .is_ok()); @@ -4773,36 +3793,39 @@ mod test { ) { let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); *expected_body.get_mut("tags").unwrap() = json!([]); - let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), None, - RadarrEvent::GetMovieDetails(None), + RadarrEvent::GetMovieDetails(1), Some("/1"), None, ) .await; + let edit_movie_params = EditMovieParams { + movie_id: 1, + clear_tags: true, + ..EditMovieParams::default() + }; let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", RadarrEvent::EditMovie(None).resource()).as_str(), + format!( + "/api/v3{}/1", + RadarrEvent::EditMovie(edit_movie_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .create_async() .await; - let edit_movie_params = EditMovieParams { - movie_id: 1, - clear_tags: true, - ..EditMovieParams::default() - }; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::EditMovie(Some(edit_movie_params))) + .handle_radarr_event(RadarrEvent::EditMovie(edit_movie_params)) .await .is_ok()); @@ -4812,67 +3835,29 @@ mod test { #[tokio::test] async fn test_handle_download_radarr_release_event() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "guid": "1234", - "indexerId": 2, - "movieId": 1 - })), - Some(json!({})), - None, - RadarrEvent::DownloadRelease(None), - None, - None, - ) - .await; - let mut movie_details_modal = MovieDetailsModal::default(); - movie_details_modal - .movie_releases - .set_items(vec![release()]); - app_arc.lock().await.data.radarr_data.movie_details_modal = Some(movie_details_modal); - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![movie()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_radarr_event(RadarrEvent::DownloadRelease(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_download_radarr_release_event_uses_provided_params() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "guid": "1234", - "indexerId": 2, - "movieId": 1 - })), - Some(json!({})), - None, - RadarrEvent::DownloadRelease(None), - None, - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - let params = RadarrReleaseDownloadBody { + let expected_body = RadarrReleaseDownloadBody { guid: "1234".to_owned(), indexer_id: 2, movie_id: 1, }; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Post, + Some(json!({ + "guid": "1234", + "indexerId": 2, + "movieId": 1 + })), + Some(json!({})), + None, + RadarrEvent::DownloadRelease(expected_body.clone()), + None, + None, + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_radarr_event(RadarrEvent::DownloadRelease(Some(params))) + .handle_radarr_event(RadarrEvent::DownloadRelease(expected_body)) .await .is_ok()); @@ -4937,97 +3922,6 @@ mod test { ); } - #[tokio::test] - async fn test_extract_movie_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![Movie { - id: 1, - ..Movie::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, movie_id_param) = network.extract_movie_id(None).await; - - assert_eq!(id, 1); - assert_str_eq!(movie_id_param, "movieId=1"); - } - - #[tokio::test] - async fn test_extract_movie_id_uses_provided_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .movies - .set_items(vec![Movie { - id: 1, - ..Movie::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, movie_id_param) = network.extract_movie_id(Some(2)).await; - - assert_eq!(id, 2); - assert_str_eq!(movie_id_param, "movieId=2"); - } - - #[tokio::test] - async fn test_extract_movie_id_filtered_movies() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut filtered_movies = StatefulTable::default(); - filtered_movies.set_filtered_items(vec![Movie { - id: 1, - ..Movie::default() - }]); - app_arc.lock().await.data.radarr_data.movies = filtered_movies; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, movie_id_param) = network.extract_movie_id(None).await; - - assert_eq!(id, 1); - assert_str_eq!(movie_id_param, "movieId=1"); - } - - #[tokio::test] - async fn test_extract_collection_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .radarr_data - .collections - .set_items(vec![Collection { - id: 1, - ..Collection::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert_eq!(network.extract_collection_id().await, 1); - } - - #[tokio::test] - async fn test_extract_collection_id_filtered_collection() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut filtered_collections = StatefulTable::default(); - filtered_collections.set_filtered_items(vec![Collection { - id: 1, - ..Collection::default() - }]); - app_arc.lock().await.data.radarr_data.collections = filtered_collections; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert_eq!(network.extract_collection_id().await, 1); - } - #[test] fn test_get_movie_status_downloaded() { assert_str_eq!(get_movie_status(true, &[], 0), "Downloaded"); diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 2dc10be..8a9f06e 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use indoc::formatdoc; use log::{debug, info, warn}; use serde_json::{json, Value}; @@ -10,9 +10,9 @@ use crate::{ models::{ radarr_models::IndexerTestResult, servarr_data::{ - modals::{EditIndexerModal, IndexerTestResultModalItem}, + modals::IndexerTestResultModalItem, sonarr::{ - modals::{AddSeriesModal, EditSeriesModal, EpisodeDetailsModal, SeasonDetailsModal}, + modals::{EpisodeDetailsModal, SeasonDetailsModal}, sonarr_data::ActiveSonarrBlock, }, }, @@ -21,11 +21,10 @@ use crate::{ LogResponse, QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update, }, sonarr_models::{ - AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, BlocklistItem, BlocklistResponse, - DeleteSeriesParams, DownloadRecord, DownloadsResponse, EditSeriesParams, Episode, - EpisodeFile, IndexerSettings, Series, SonarrCommandBody, SonarrHistoryItem, - SonarrHistoryWrapper, SonarrRelease, SonarrReleaseDownloadBody, SonarrSerdeable, SonarrTask, - SonarrTaskName, SystemStatus, + AddSeriesBody, AddSeriesSearchResult, BlocklistItem, BlocklistResponse, DeleteSeriesParams, + DownloadRecord, DownloadsResponse, EditSeriesParams, Episode, EpisodeFile, IndexerSettings, + Series, SonarrCommandBody, SonarrHistoryItem, SonarrHistoryWrapper, SonarrRelease, + SonarrReleaseDownloadBody, SonarrSerdeable, SonarrTask, SonarrTaskName, SystemStatus, }, stateful_table::StatefulTable, HorizontallyScrollableText, Route, Scrollable, ScrollableText, @@ -39,43 +38,43 @@ mod sonarr_network_tests; #[derive(Debug, Eq, PartialEq, Clone)] pub enum SonarrEvent { - AddRootFolder(Option), - AddSeries(Option), + AddRootFolder(AddRootFolderBody), + AddSeries(AddSeriesBody), AddTag(String), ClearBlocklist, - DeleteBlocklistItem(Option), - DeleteDownload(Option), - DeleteEpisodeFile(Option), - DeleteIndexer(Option), - DeleteRootFolder(Option), - DeleteSeries(Option), + DeleteBlocklistItem(i64), + DeleteDownload(i64), + DeleteEpisodeFile(i64), + DeleteIndexer(i64), + DeleteRootFolder(i64), + DeleteSeries(DeleteSeriesParams), DeleteTag(i64), DownloadRelease(SonarrReleaseDownloadBody), - EditAllIndexerSettings(Option), - EditIndexer(Option), - EditSeries(Option), + EditAllIndexerSettings(IndexerSettings), + EditIndexer(EditIndexerParams), + EditSeries(EditSeriesParams), GetAllIndexerSettings, GetBlocklist, GetDownloads, - GetHistory(Option), + GetHistory(u64), GetHostConfig, GetIndexers, - GetEpisodeDetails(Option), - GetEpisodes(Option), - GetEpisodeFiles(Option), - GetEpisodeHistory(Option), + GetEpisodeDetails(i64), + GetEpisodes(i64), + GetEpisodeFiles(i64), + GetEpisodeHistory(i64), GetLanguageProfiles, - GetLogs(Option), + GetLogs(u64), GetDiskSpace, GetQualityProfiles, GetQueuedEvents, GetRootFolders, - GetEpisodeReleases(Option), - GetSeasonHistory(Option<(i64, i64)>), - GetSeasonReleases(Option<(i64, i64)>), + GetEpisodeReleases(i64), + GetSeasonHistory((i64, i64)), + GetSeasonReleases((i64, i64)), GetSecurityConfig, - GetSeriesDetails(Option), - GetSeriesHistory(Option), + GetSeriesDetails(i64), + GetSeriesHistory(i64), GetStatus, GetUpdates, GetTags, @@ -83,17 +82,17 @@ pub enum SonarrEvent { HealthCheck, ListSeries, MarkHistoryItemAsFailed(i64), - SearchNewSeries(Option), - StartTask(Option), - TestIndexer(Option), + SearchNewSeries(String), + StartTask(SonarrTaskName), + TestIndexer(i64), TestAllIndexers, - ToggleSeasonMonitoring(Option<(i64, i64)>), - ToggleEpisodeMonitoring(Option), - TriggerAutomaticEpisodeSearch(Option), - TriggerAutomaticSeasonSearch(Option<(i64, i64)>), - TriggerAutomaticSeriesSearch(Option), + ToggleSeasonMonitoring((i64, i64)), + ToggleEpisodeMonitoring(i64), + TriggerAutomaticEpisodeSearch(i64), + TriggerAutomaticSeasonSearch((i64, i64)), + TriggerAutomaticSeriesSearch(i64), UpdateAllSeries, - UpdateAndScanSeries(Option), + UpdateAndScanSeries(i64), UpdateDownloads, } @@ -357,31 +356,23 @@ impl<'a, 'b> Network<'a, 'b> { } } - async fn add_sonarr_root_folder(&mut self, root_folder: Option) -> Result { + async fn add_sonarr_root_folder( + &mut self, + add_root_folder_body: AddRootFolderBody, + ) -> Result { info!("Adding new root folder to Sonarr"); - let event = SonarrEvent::AddRootFolder(None); - let body = if let Some(path) = root_folder { - AddRootFolderBody { path } - } else { - let mut app = self.app.lock().await; - let path = app - .data - .sonarr_data - .edit_root_folder - .as_ref() - .unwrap() - .text - .clone(); + let event = SonarrEvent::AddRootFolder(add_root_folder_body.clone()); - app.data.sonarr_data.edit_root_folder = None; - - AddRootFolderBody { path } - }; - - debug!("Add root folder body: {body:?}"); + debug!("Add root folder body: {add_root_folder_body:?}"); let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .request_props_from( + event, + RequestMethod::Post, + Some(add_root_folder_body), + None, + None, + ) .await; self @@ -389,99 +380,26 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn add_sonarr_series( - &mut self, - add_series_body_option: Option, - ) -> Result { + async fn add_sonarr_series(&mut self, mut add_series_body: AddSeriesBody) -> Result { info!("Adding new series to Sonarr"); - let event = SonarrEvent::AddSeries(None); - let body = if let Some(add_series_body) = add_series_body_option { - add_series_body - } else { - let tags = self - .app - .lock() - .await - .data - .sonarr_data - .add_series_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 AddSeriesModal { - root_folder_list, - monitor_list, - quality_profile_list, - language_profile_list, - series_type_list, - use_season_folder, - .. - } = app.data.sonarr_data.add_series_modal.as_ref().unwrap(); - let season_folder = *use_season_folder; - let (tvdb_id, title) = { - let AddSeriesSearchResult { tvdb_id, title, .. } = app - .data - .sonarr_data - .add_searched_series - .as_ref() - .unwrap() - .current_selection() - .clone(); - (tvdb_id, title.text) - }; - let quality_profile = quality_profile_list.current_selection(); - let quality_profile_id = *app - .data - .sonarr_data - .quality_profile_map - .iter() - .filter(|(_, value)| *value == quality_profile) - .map(|(key, _)| key) - .next() - .unwrap(); - let language_profile = language_profile_list.current_selection(); - let language_profile_id = *app - .data - .sonarr_data - .language_profiles_map - .iter() - .filter(|(_, value)| *value == language_profile) - .map(|(key, _)| key) - .next() - .unwrap(); + let event = SonarrEvent::AddSeries(add_series_body.clone()); + if let Some(tag_input_string) = add_series_body.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_sonarr_tag_ids_vec(tag_input_string.clone()) + .await; + add_series_body.tags = tag_ids_vec; + } - let path = root_folder_list.current_selection().path.clone(); - let monitor = monitor_list.current_selection().to_string(); - let series_type = series_type_list.current_selection().to_string(); - - app.data.sonarr_data.add_series_modal = None; - - AddSeriesBody { - tvdb_id, - title, - monitored: true, - root_folder_path: path, - quality_profile_id, - language_profile_id, - series_type, - season_folder, - tags: tag_ids_vec, - add_options: AddSeriesOptions { - monitor, - search_for_cutoff_unmet_episodes: true, - search_for_missing_episodes: true, - }, - } - }; - - debug!("Add series body: {body:?}"); + debug!("Add series body: {add_series_body:?}"); let request_props = self - .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .request_props_from( + event, + RequestMethod::Post, + Some(add_series_body), + None, + None, + ) .await; self @@ -541,30 +459,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_sonarr_blocklist_item(&mut self, blocklist_item_id: Option) -> Result<()> { - let event = SonarrEvent::DeleteBlocklistItem(None); - let id = if let Some(b_id) = blocklist_item_id { - b_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .blocklist - .current_selection() - .id - }; - - info!("Deleting Sonarr blocklist item for item with id: {id}"); + async fn delete_sonarr_blocklist_item(&mut self, blocklist_item_id: i64) -> Result<()> { + let event = SonarrEvent::DeleteBlocklistItem(blocklist_item_id); + info!("Deleting Sonarr blocklist item for item with id: {blocklist_item_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{blocklist_item_id}")), None, ) .await; @@ -574,33 +478,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_sonarr_episode_file(&mut self, episode_file_id: Option) -> Result<()> { - let event = SonarrEvent::DeleteEpisodeFile(None); - let id = if let Some(ep_id) = episode_file_id { - ep_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .expect("Season details have not been loaded") - .episodes - .current_selection() - .episode_file_id - }; - - info!("Deleting Sonarr episode file for episode file with id: {id}"); + async fn delete_sonarr_episode_file(&mut self, episode_file_id: i64) -> Result<()> { + let event = SonarrEvent::DeleteEpisodeFile(episode_file_id); + info!("Deleting Sonarr episode file for episode file with id: {episode_file_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{episode_file_id}")), None, ) .await; @@ -610,30 +497,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_sonarr_download(&mut self, download_id: Option) -> Result<()> { - let event = SonarrEvent::DeleteDownload(None); - let id = if let Some(dl_id) = download_id { - dl_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .downloads - .current_selection() - .id - }; - - info!("Deleting Sonarr download for download with id: {id}"); + async fn delete_sonarr_download(&mut self, download_id: i64) -> Result<()> { + let event = SonarrEvent::DeleteDownload(download_id); + info!("Deleting Sonarr download for download with id: {download_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{download_id}")), None, ) .await; @@ -643,30 +516,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_sonarr_indexer(&mut self, indexer_id: Option) -> Result<()> { - let event = SonarrEvent::DeleteIndexer(None); - let id = if let Some(i_id) = indexer_id { - i_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .indexers - .current_selection() - .id - }; - - info!("Deleting Sonarr indexer for indexer with id: {id}"); + async fn delete_sonarr_indexer(&mut self, indexer_id: i64) -> Result<()> { + let event = SonarrEvent::DeleteIndexer(indexer_id); + info!("Deleting Sonarr indexer for indexer with id: {indexer_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{indexer_id}")), None, ) .await; @@ -676,30 +535,16 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_sonarr_root_folder(&mut self, root_folder_id: Option) -> Result<()> { - let event = SonarrEvent::DeleteRootFolder(None); - let id = if let Some(rf_id) = root_folder_id { - rf_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .root_folders - .current_selection() - .id - }; - - info!("Deleting Sonarr root folder for folder with id: {id}"); + async fn delete_sonarr_root_folder(&mut self, root_folder_id: i64) -> Result<()> { + let event = SonarrEvent::DeleteRootFolder(root_folder_id); + info!("Deleting Sonarr root folder for folder with id: {root_folder_id}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{id}")), + Some(format!("/{root_folder_id}")), None, ) .await; @@ -709,53 +554,31 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn delete_series( - &mut self, - delete_series_params: Option, - ) -> Result<()> { - let event = SonarrEvent::DeleteSeries(None); - let (series_id, delete_files, add_import_exclusion) = if let Some(params) = delete_series_params - { - ( - params.id, - params.delete_series_files, - params.add_list_exclusion, - ) - } else { - let (series_id, _) = self.extract_series_id(None).await; - let delete_files = self.app.lock().await.data.sonarr_data.delete_series_files; - let add_import_exclusion = self.app.lock().await.data.sonarr_data.add_list_exclusion; + async fn delete_series(&mut self, delete_series_params: DeleteSeriesParams) -> Result<()> { + let event = SonarrEvent::DeleteSeries(delete_series_params.clone()); + let DeleteSeriesParams { + id, + delete_series_files, + add_list_exclusion, + } = delete_series_params; - (series_id, delete_files, add_import_exclusion) - }; - - info!("Deleting Sonarr series with ID: {series_id} with deleteFiles={delete_files} and addImportExclusion={add_import_exclusion}"); + info!("Deleting Sonarr series with ID: {id} with deleteFiles={delete_series_files} and addImportExclusion={add_list_exclusion}"); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, - Some(format!("/{series_id}")), + Some(format!("/{id}")), Some(format!( - "deleteFiles={delete_files}&addImportExclusion={add_import_exclusion}" + "deleteFiles={delete_series_files}&addImportExclusion={add_list_exclusion}" )), ) .await; - let resp = self - .handle_request::<(), ()>(request_props, |_, _| ()) - .await; - self - .app - .lock() + .handle_request::<(), ()>(request_props, |_, _| ()) .await - .data - .sonarr_data - .reset_delete_series_preferences(); - - resp } async fn delete_sonarr_tag(&mut self, id: i64) -> Result<()> { @@ -799,64 +622,34 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn edit_all_sonarr_indexer_settings( - &mut self, - params: Option, - ) -> Result { + async fn edit_all_sonarr_indexer_settings(&mut self, params: IndexerSettings) -> Result { info!("Updating Sonarr indexer settings"); - let event = SonarrEvent::EditAllIndexerSettings(None); - - let body = if let Some(indexer_settings) = params { - indexer_settings - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .indexer_settings - .as_ref() - .unwrap() - .clone() - }; - - debug!("Indexer settings body: {body:?}"); + let event = SonarrEvent::EditAllIndexerSettings(params.clone()); + debug!("Indexer settings body: {params:?}"); let request_props = self - .request_props_from(event, RequestMethod::Put, Some(body), None, None) + .request_props_from(event, RequestMethod::Put, Some(params), None, None) .await; - let resp = self + self .handle_request::(request_props, |_, _| {}) - .await; - - self.app.lock().await.data.sonarr_data.indexer_settings = None; - - resp + .await } async fn edit_sonarr_indexer( &mut self, - edit_indexer_params: Option, + mut edit_indexer_params: EditIndexerParams, ) -> Result<()> { + if let Some(tag_input_string) = edit_indexer_params.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_sonarr_tag_ids_vec(tag_input_string.clone()) + .await; + edit_indexer_params.tags = Some(tag_ids_vec); + } 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 - }; + let event = SonarrEvent::EditIndexer(edit_indexer_params.clone()); + let id = edit_indexer_params.indexer_id; info!("Updating Sonarr indexer with ID: {id}"); - info!("Fetching indexer details for indexer with ID: {id}"); let request_props = self @@ -891,7 +684,7 @@ impl<'a, 'b> Network<'a, 'b> { seed_ratio, tags, priority, - ) = if let Some(params) = edit_indexer_params { + ) = { let priority = detailed_indexer_body["priority"] .as_i64() .expect("Unable to deserialize 'priority'"); @@ -900,28 +693,28 @@ impl<'a, 'b> Network<'a, 'b> { .unwrap() .iter() .find(|field| field["name"] == "seedCriteria.seedRatio"); - let name = params.name.unwrap_or( + let name = edit_indexer_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( + let enable_rss = edit_indexer_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( + let enable_automatic_search = edit_indexer_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( + let enable_interactive_search = edit_indexer_params.enable_interactive_search.unwrap_or( detailed_indexer_body["enableInteractiveSearch"] .as_bool() .expect("Unable to deserialize 'enableInteractiveSearch'"), ); - let url = params.url.unwrap_or( + let url = edit_indexer_params.url.unwrap_or( detailed_indexer_body["fields"] .as_array() .expect("Unable to deserialize 'fields'") @@ -934,7 +727,7 @@ impl<'a, 'b> Network<'a, 'b> { .expect("Unable to deserialize 'baseUrl value'") .to_owned(), ); - let api_key = params.api_key.unwrap_or( + let api_key = edit_indexer_params.api_key.unwrap_or( detailed_indexer_body["fields"] .as_array() .expect("Unable to deserialize 'fields'") @@ -947,7 +740,7 @@ impl<'a, 'b> Network<'a, 'b> { .expect("Unable to deserialize 'apiKey value'") .to_owned(), ); - let seed_ratio = params.seed_ratio.unwrap_or_else(|| { + let seed_ratio = edit_indexer_params.seed_ratio.unwrap_or_else(|| { if let Some(seed_ratio_field) = seed_ratio_field_option { return seed_ratio_field .get("value") @@ -959,10 +752,10 @@ impl<'a, 'b> Network<'a, 'b> { String::new() }); - let tags = if params.clear_tags { + let tags = if edit_indexer_params.clear_tags { vec![] } else { - params.tags.unwrap_or( + edit_indexer_params.tags.unwrap_or( detailed_indexer_body["tags"] .as_array() .expect("Unable to deserialize 'tags'") @@ -971,7 +764,7 @@ impl<'a, 'b> Network<'a, 'b> { .collect(), ) }; - let priority = params.priority.unwrap_or(priority); + let priority = edit_indexer_params.priority.unwrap_or(priority); ( name, @@ -984,51 +777,6 @@ impl<'a, 'b> Network<'a, 'b> { 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, - priority, - .. - } = 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); @@ -1092,19 +840,17 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn edit_sonarr_series( - &mut self, - edit_series_params: Option, - ) -> Result<()> { + async fn edit_sonarr_series(&mut self, mut edit_series_params: EditSeriesParams) -> Result<()> { info!("Editing Sonarr series"); - let detail_event = SonarrEvent::GetSeriesDetails(None); - let event = SonarrEvent::EditSeries(None); - - let (series_id, _) = if let Some(ref params) = edit_series_params { - self.extract_series_id(Some(params.series_id)).await - } else { - self.extract_series_id(None).await - }; + if let Some(tag_input_string) = edit_series_params.tag_input_string.as_ref() { + let tag_ids_vec = self + .extract_and_add_sonarr_tag_ids_vec(tag_input_string.clone()) + .await; + edit_series_params.tags = Some(tag_ids_vec); + } + let series_id = edit_series_params.series_id; + let detail_event = SonarrEvent::GetSeriesDetails(series_id); + let event = SonarrEvent::EditSeries(edit_series_params.clone()); info!("Fetching series details for series with ID: {series_id}"); let request_props = self @@ -1136,44 +882,44 @@ impl<'a, 'b> Network<'a, 'b> { language_profile_id, root_folder_path, tags, - ) = if let Some(params) = edit_series_params { - let monitored = params.monitored.unwrap_or( + ) = { + let monitored = edit_series_params.monitored.unwrap_or( detailed_series_body["monitored"] .as_bool() .expect("Unable to deserialize 'monitored'"), ); - let use_season_folders = params.use_season_folders.unwrap_or( + let use_season_folders = edit_series_params.use_season_folders.unwrap_or( detailed_series_body["seasonFolder"] .as_bool() .expect("Unable to deserialize 'season_folder'"), ); - let series_type = params + let series_type = edit_series_params .series_type .unwrap_or_else(|| { serde_json::from_value(detailed_series_body["seriesType"].clone()) .expect("Unable to deserialize 'seriesType'") }) .to_string(); - let quality_profile_id = params.quality_profile_id.unwrap_or_else(|| { + let quality_profile_id = edit_series_params.quality_profile_id.unwrap_or_else(|| { detailed_series_body["qualityProfileId"] .as_i64() .expect("Unable to deserialize 'qualityProfileId'") }); - let language_profile_id = params.language_profile_id.unwrap_or_else(|| { + let language_profile_id = edit_series_params.language_profile_id.unwrap_or_else(|| { detailed_series_body["languageProfileId"] .as_i64() .expect("Unable to deserialize 'languageProfileId'") }); - let root_folder_path = params.root_folder_path.unwrap_or_else(|| { + let root_folder_path = edit_series_params.root_folder_path.unwrap_or_else(|| { detailed_series_body["path"] .as_str() .expect("Unable to deserialize 'path'") .to_owned() }); - let tags = if params.clear_tags { + let tags = if edit_series_params.clear_tags { vec![] } else { - params.tags.unwrap_or( + edit_series_params.tags.unwrap_or( detailed_series_body["tags"] .as_array() .expect("Unable to deserialize 'tags'") @@ -1192,67 +938,6 @@ impl<'a, 'b> Network<'a, 'b> { root_folder_path, tags, ) - } else { - let tags = self - .app - .lock() - .await - .data - .sonarr_data - .edit_series_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 EditSeriesModal { - monitored, - use_season_folders, - path, - series_type_list, - quality_profile_list, - language_profile_list, - .. - } = app.data.sonarr_data.edit_series_modal.as_ref().unwrap(); - let quality_profile = quality_profile_list.current_selection(); - let quality_profile_id = *app - .data - .sonarr_data - .quality_profile_map - .iter() - .filter(|(_, value)| *value == quality_profile) - .map(|(key, _)| key) - .next() - .unwrap(); - let language_profile = language_profile_list.current_selection(); - let language_profile_id = *app - .data - .sonarr_data - .language_profiles_map - .iter() - .filter(|(_, value)| *value == language_profile) - .map(|(key, _)| key) - .next() - .unwrap(); - - ( - monitored.unwrap_or_default(), - use_season_folders.unwrap_or_default(), - series_type_list.current_selection().to_string(), - quality_profile_id, - language_profile_id, - path.text.clone(), - tag_ids_vec, - ) - }; - - app.data.sonarr_data.edit_series_modal = None; - - params }; *detailed_series_body.get_mut("monitored").unwrap() = json!(monitored); @@ -1282,86 +967,81 @@ impl<'a, 'b> Network<'a, 'b> { async fn toggle_sonarr_season_monitoring( &mut self, - series_id_season_number_tuple: Option<(i64, i64)>, + series_id_season_number_tuple: (i64, i64), ) -> Result<()> { - let detail_event = SonarrEvent::GetSeriesDetails(None); let event = SonarrEvent::ToggleSeasonMonitoring(series_id_season_number_tuple); - let (series_id, season_number) = - if let Some((series_id, season_number)) = series_id_season_number_tuple { - (Some(series_id), Some(season_number)) - } else { - (None, None) - }; + let (series_id, season_number) = series_id_season_number_tuple; - let (series_id, _) = self.extract_series_id(series_id).await; - if let Ok((season_number, _)) = self.extract_season_number(season_number).await { - info!("Toggling season monitoring for season {season_number} in series with ID: {series_id}"); - info!("Fetching series details for series with ID: {series_id}"); + let detail_event = SonarrEvent::GetSeriesDetails(series_id); + info!("Toggling season monitoring for season {season_number} in series with ID: {series_id}"); + info!("Fetching series details for series with ID: {series_id}"); - let request_props = self - .request_props_from( - detail_event, - RequestMethod::Get, - None::<()>, - Some(format!("/{series_id}")), - None, - ) - .await; + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{series_id}")), + None, + ) + .await; - let mut response = String::new(); + let mut response = String::new(); - self - .handle_request::<(), Value>(request_props, |detailed_series_body, _| { - response = detailed_series_body.to_string() - }) - .await?; + self + .handle_request::<(), Value>(request_props, |detailed_series_body, _| { + response = detailed_series_body.to_string() + }) + .await?; - info!("Constructing toggle season monitoring body"); + info!("Constructing toggle season monitoring body"); - let mut detailed_series_body: Value = - serde_json::from_str(&response).expect("Request for detailed series body was interrupted"); - let monitored = detailed_series_body - .get("seasons") - .unwrap() - .as_array() - .unwrap() - .iter() - .find(|season| season["seasonNumber"] == season_number) - .unwrap() - .get("monitored") - .unwrap() - .as_bool() - .unwrap(); + match serde_json::from_str::(&response) { + Ok(mut detailed_series_body) => { + let monitored = detailed_series_body + .get("seasons") + .unwrap() + .as_array() + .unwrap() + .iter() + .find(|season| season["seasonNumber"] == season_number) + .unwrap() + .get("monitored") + .unwrap() + .as_bool() + .unwrap(); - *detailed_series_body - .get_mut("seasons") - .unwrap() - .as_array_mut() - .unwrap() - .iter_mut() - .find(|season| season["seasonNumber"] == season_number) - .unwrap() - .get_mut("monitored") - .unwrap() = json!(!monitored); + *detailed_series_body + .get_mut("seasons") + .unwrap() + .as_array_mut() + .unwrap() + .iter_mut() + .find(|season| season["seasonNumber"] == season_number) + .unwrap() + .get_mut("monitored") + .unwrap() = json!(!monitored); - debug!("Toggle season monitoring body: {detailed_series_body:?}"); + debug!("Toggle season monitoring body: {detailed_series_body:?}"); - let request_props = self - .request_props_from( - event, - RequestMethod::Put, - Some(detailed_series_body), - Some(format!("/{series_id}")), - None, - ) - .await; + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_series_body), + Some(format!("/{series_id}")), + None, + ) + .await; - self - .handle_request::(request_props, |_, _| ()) - .await - } else { - warn!("Season number was not provided. Aborting..."); - Ok(()) + self + .handle_request::(request_props, |_, _| ()) + .await + } + Err(_) => { + warn!("Request for detailed series body was interrupted"); + Ok(()) + } } } @@ -1459,10 +1139,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_episodes(&mut self, series_id: Option) -> Result> { + async fn get_episodes(&mut self, series_id: i64) -> Result> { let event = SonarrEvent::GetEpisodes(series_id); - let (id, series_id_param) = self.extract_series_id(series_id).await; - info!("Fetching episodes for Sonarr series with ID: {id}"); + info!("Fetching episodes for Sonarr series with ID: {series_id}"); let request_props = self .request_props_from( @@ -1470,7 +1149,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(series_id_param), + Some(format!("seriesId={series_id}")), ) .await; @@ -1522,10 +1201,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_episode_files(&mut self, series_id: Option) -> Result> { + async fn get_episode_files(&mut self, series_id: i64) -> Result> { let event = SonarrEvent::GetEpisodeFiles(series_id); - let (id, series_id_param) = self.extract_series_id(series_id).await; - info!("Fetching episodes files for Sonarr series with ID: {id}"); + info!("Fetching episodes files for Sonarr series with ID: {series_id}"); let request_props = self .request_props_from( @@ -1533,7 +1211,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(series_id_param), + Some(format!("seriesId={series_id}")), ) .await; @@ -1555,15 +1233,12 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_sonarr_episode_history( - &mut self, - episode_id: Option, - ) -> Result { - let id = self.extract_episode_id(episode_id).await; - info!("Fetching Sonarr history for episode with ID: {id}"); + async fn get_sonarr_episode_history(&mut self, episode_id: i64) -> Result { + info!("Fetching Sonarr history for episode with ID: {episode_id}"); let event = SonarrEvent::GetEpisodeHistory(episode_id); - let params = format!("episodeId={id}&pageSize=1000&sortDirection=descending&sortKey=date",); + let params = + format!("episodeId={episode_id}&pageSize=1000&sortDirection=descending&sortKey=date"); let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) .await; @@ -1620,19 +1295,18 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_episode_details(&mut self, episode_id: Option) -> Result { + async fn get_episode_details(&mut self, episode_id: i64) -> Result { info!("Fetching Sonarr episode details"); - let event = SonarrEvent::GetEpisodeDetails(None); - let id = self.extract_episode_id(episode_id).await; + let event = SonarrEvent::GetEpisodeDetails(episode_id); - info!("Fetching episode details for episode with ID: {id}"); + info!("Fetching episode details for episode with ID: {episode_id}"); let request_props = self .request_props_from( event, RequestMethod::Get, None::<()>, - Some(format!("/{id}")), + Some(format!("/{episode_id}")), None, ) .await; @@ -1766,14 +1440,11 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_sonarr_history(&mut self, events: Option) -> Result { + async fn get_sonarr_history(&mut self, events: u64) -> Result { info!("Fetching all Sonarr history events"); let event = SonarrEvent::GetHistory(events); - let params = format!( - "pageSize={}&sortDirection=descending&sortKey=date", - events.unwrap_or(500) - ); + let params = format!("pageSize={}&sortDirection=descending&sortKey=date", events); let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) .await; @@ -1826,14 +1497,11 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_sonarr_logs(&mut self, events: Option) -> Result { + async fn get_sonarr_logs(&mut self, events: u64) -> Result { info!("Fetching Sonarr logs"); let event = SonarrEvent::GetLogs(events); - let params = format!( - "pageSize={}&sortDirection=descending&sortKey=time", - events.unwrap_or(500) - ); + let params = format!("pageSize={}&sortDirection=descending&sortKey=time", events); let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) .await; @@ -1940,11 +1608,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_episode_releases(&mut self, episode_id: Option) -> Result> { - let event = SonarrEvent::GetEpisodeReleases(None); - let id = self.extract_episode_id(episode_id).await; - - info!("Fetching releases for episode with ID: {id}"); + async fn get_episode_releases(&mut self, episode_id: i64) -> Result> { + let event = SonarrEvent::GetEpisodeReleases(episode_id); + info!("Fetching releases for episode with ID: {episode_id}"); let request_props = self .request_props_from( @@ -1952,7 +1618,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(format!("episodeId={id}")), + Some(format!("episodeId={episode_id}")), ) .await; @@ -1997,19 +1663,10 @@ impl<'a, 'b> Network<'a, 'b> { async fn get_season_releases( &mut self, - series_season_id_tuple: Option<(i64, i64)>, + series_season_id_tuple: (i64, i64), ) -> Result> { - let event = SonarrEvent::GetSeasonReleases(None); - let (series_id, season_number) = - if let Some((series_id, season_number)) = series_season_id_tuple { - (Some(series_id), Some(season_number)) - } else { - (None, None) - }; - - let (series_id, series_id_param) = self.extract_series_id(series_id).await; - let (season_number, season_number_param) = self.extract_season_number(season_number).await?; - + let event = SonarrEvent::GetSeasonReleases(series_season_id_tuple); + let (series_id, season_number) = series_season_id_tuple; info!("Fetching releases for series with ID: {series_id} and season number: {season_number}"); let request_props = self @@ -2018,7 +1675,10 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(format!("{}&{}", series_id_param, season_number_param)), + Some(format!( + "seriesId={}&seasonNumber={}", + series_id, season_number + )), ) .await; @@ -2047,22 +1707,13 @@ impl<'a, 'b> Network<'a, 'b> { async fn get_sonarr_season_history( &mut self, - series_season_id_tuple: Option<(i64, i64)>, + series_season_id_tuple: (i64, i64), ) -> Result> { - let event = SonarrEvent::GetSeasonHistory(None); - let (series_id, season_number) = - if let Some((series_id, season_number)) = series_season_id_tuple { - (Some(series_id), Some(season_number)) - } else { - (None, None) - }; - - let (series_id, series_id_param) = self.extract_series_id(series_id).await; - let (season_number, season_number_param) = self.extract_season_number(season_number).await?; - + let event = SonarrEvent::GetSeasonHistory(series_season_id_tuple); + let (series_id, season_number) = series_season_id_tuple; info!("Fetching history for series with ID: {series_id} and season number: {season_number}"); - let params = format!("{series_id_param}&{season_number_param}",); + let params = format!("seriesId={series_id}&seasonNumber={season_number}",); let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) .await; @@ -2108,9 +1759,8 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_series_details(&mut self, series_id: Option) -> Result { - let (id, _) = self.extract_series_id(series_id).await; - info!("Fetching details for Sonarr series with ID: {id}"); + async fn get_series_details(&mut self, series_id: i64) -> Result { + info!("Fetching details for Sonarr series with ID: {series_id}"); let event = SonarrEvent::GetSeriesDetails(series_id); let request_props = self @@ -2118,7 +1768,7 @@ impl<'a, 'b> Network<'a, 'b> { event, RequestMethod::Get, None::<()>, - Some(format!("/{id}")), + Some(format!("/{series_id}")), None, ) .await; @@ -2128,12 +1778,8 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_sonarr_series_history( - &mut self, - series_id: Option, - ) -> Result> { - let (id, series_id_param) = self.extract_series_id(series_id).await; - info!("Fetching Sonarr series history for series with ID: {id}"); + async fn get_sonarr_series_history(&mut self, series_id: i64) -> Result> { + info!("Fetching Sonarr series history for series with ID: {series_id}"); let event = SonarrEvent::GetSeriesHistory(series_id); let request_props = self @@ -2142,7 +1788,7 @@ impl<'a, 'b> Network<'a, 'b> { RequestMethod::Get, None::<()>, None, - Some(series_id_param), + Some(format!("seriesId={series_id}")), ) .await; @@ -2344,84 +1990,39 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn search_sonarr_series( - &mut self, - query: Option, - ) -> Result> { + async fn search_sonarr_series(&mut self, query: String) -> Result> { info!("Searching for specific Sonarr series"); - let event = SonarrEvent::SearchNewSeries(None); - let search = if let Some(search_query) = query { - Ok(search_query.into()) - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .add_series_search - .clone() - .ok_or(anyhow!("Encountered a race condition")) - }; + let event = SonarrEvent::SearchNewSeries(query.clone()); - match search { - Ok(search_string) => { - let request_props = self - .request_props_from( - event, - RequestMethod::Get, - None::<()>, - None, - Some(format!("term={}", encode(&search_string.text))), - ) - .await; + let request_props = self + .request_props_from( + event, + RequestMethod::Get, + None::<()>, + None, + Some(format!("term={}", encode(&query))), + ) + .await; - self - .handle_request::<(), Vec>(request_props, |series_vec, mut app| { - if series_vec.is_empty() { - app.pop_and_push_navigation_stack( - ActiveSonarrBlock::AddSeriesEmptySearchResults.into(), - ); - } else if let Some(add_searched_seriess) = - app.data.sonarr_data.add_searched_series.as_mut() - { - add_searched_seriess.set_items(series_vec); - } else { - let mut add_searched_seriess = StatefulTable::default(); - add_searched_seriess.set_items(series_vec); - app.data.sonarr_data.add_searched_series = Some(add_searched_seriess); - } - }) - .await - } - Err(e) => { - warn!( - "Encountered a race condition: {e}\n \ - This is most likely caused by the user trying to navigate between modals rapidly. \ - Ignoring search request." - ); - Ok(Vec::default()) - } - } + self + .handle_request::<(), Vec>(request_props, |series_vec, mut app| { + if series_vec.is_empty() { + app.pop_and_push_navigation_stack(ActiveSonarrBlock::AddSeriesEmptySearchResults.into()); + } else if let Some(add_searched_seriess) = app.data.sonarr_data.add_searched_series.as_mut() + { + add_searched_seriess.set_items(series_vec); + } else { + let mut add_searched_seriess = StatefulTable::default(); + add_searched_seriess.set_items(series_vec); + app.data.sonarr_data.add_searched_series = Some(add_searched_seriess); + } + }) + .await } - async fn start_sonarr_task(&mut self, task: Option) -> Result { - let event = SonarrEvent::StartTask(None); - let task_name = if let Some(t_name) = task { - t_name - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .tasks - .current_selection() - .task_name - } - .to_string(); - + async fn start_sonarr_task(&mut self, task: SonarrTaskName) -> Result { + let event = SonarrEvent::StartTask(task); + let task_name = task.to_string(); info!("Starting Sonarr task: {task_name}"); let body = CommandBody { name: task_name }; @@ -2435,32 +2036,19 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn test_sonarr_indexer(&mut self, indexer_id: Option) -> Result { + async fn test_sonarr_indexer(&mut self, indexer_id: i64) -> Result { let detail_event = SonarrEvent::GetIndexers; - let event = SonarrEvent::TestIndexer(None); - let id = if let Some(i_id) = indexer_id { - i_id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .indexers - .current_selection() - .id - }; - info!("Testing Sonarr indexer with ID: {id}"); + let event = SonarrEvent::TestIndexer(indexer_id); + info!("Testing Sonarr indexer with ID: {indexer_id}"); - info!("Fetching indexer details for indexer with ID: {id}"); + info!("Fetching indexer details for indexer with ID: {indexer_id}"); let request_props = self .request_props_from( detail_event, RequestMethod::Get, None::<()>, - Some(format!("/{id}")), + Some(format!("/{indexer_id}")), None, ) .await; @@ -2543,11 +2131,11 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn toggle_sonarr_episode_monitoring(&mut self, episode_id: Option) -> Result<()> { + async fn toggle_sonarr_episode_monitoring(&mut self, episode_id: i64) -> Result<()> { let event = SonarrEvent::ToggleEpisodeMonitoring(episode_id); - let detail_event = SonarrEvent::GetEpisodeDetails(None); + let detail_event = SonarrEvent::GetEpisodeDetails(0); - let (id, monitored) = if let Some(episode_id) = episode_id { + let monitored = { info!("Fetching episode details for episode id: {episode_id}"); let request_props = self .request_props_from( @@ -2571,24 +2159,13 @@ impl<'a, 'b> Network<'a, 'b> { }) .await?; - (episode_id, monitored) - } else { - let app = self.app.lock().await; - let current_selection = app - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .episodes - .current_selection(); - (current_selection.id, current_selection.monitored) + monitored }; - info!("Toggling monitoring for episode id: {id}"); + info!("Toggling monitoring for episode id: {episode_id}"); let body = MonitorEpisodeBody { - episode_ids: vec![id], + episode_ids: vec![episode_id], monitored: !monitored, }; @@ -2601,14 +2178,13 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn trigger_automatic_series_search(&mut self, series_id: Option) -> Result { + async fn trigger_automatic_series_search(&mut self, series_id: i64) -> Result { let event = SonarrEvent::TriggerAutomaticSeriesSearch(series_id); - let (id, _) = self.extract_series_id(series_id).await; - info!("Searching indexers for series with ID: {id}"); + info!("Searching indexers for series with ID: {series_id}"); let body = SonarrCommandBody { name: "SeriesSearch".to_owned(), - series_id: Some(id), + series_id: Some(series_id), ..SonarrCommandBody::default() }; @@ -2623,18 +2199,10 @@ impl<'a, 'b> Network<'a, 'b> { async fn trigger_automatic_season_search( &mut self, - series_season_id_tuple: Option<(i64, i64)>, + series_season_id_tuple: (i64, i64), ) -> Result { let event = SonarrEvent::TriggerAutomaticSeasonSearch(series_season_id_tuple); - let (series_id, season_number) = - if let Some((series_id, season_number)) = series_season_id_tuple { - (Some(series_id), Some(season_number)) - } else { - (None, None) - }; - - let (series_id, _) = self.extract_series_id(series_id).await; - let (season_number, _) = self.extract_season_number(season_number).await?; + let (series_id, season_number) = series_season_id_tuple; info!("Searching indexers for series with ID: {series_id} and season number: {season_number}"); let body = SonarrCommandBody { @@ -2653,14 +2221,13 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn trigger_automatic_episode_search(&mut self, episode_id: Option) -> Result { + async fn trigger_automatic_episode_search(&mut self, episode_id: i64) -> Result { let event = SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id); - let id = self.extract_episode_id(episode_id).await; - info!("Searching indexers for episode with ID: {id}"); + info!("Searching indexers for episode with ID: {episode_id}"); let body = SonarrCommandBody { name: "EpisodeSearch".to_owned(), - episode_ids: Some(vec![id]), + episode_ids: Some(vec![episode_id]), ..SonarrCommandBody::default() }; @@ -2690,13 +2257,12 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn update_and_scan_series(&mut self, series_id: Option) -> Result { - let (id, _) = self.extract_series_id(series_id).await; - let event = SonarrEvent::UpdateAndScanSeries(None); - info!("Updating and scanning series with ID: {id}"); + async fn update_and_scan_series(&mut self, series_id: i64) -> Result { + let event = SonarrEvent::UpdateAndScanSeries(series_id); + info!("Updating and scanning series with ID: {series_id}"); let body = SonarrCommandBody { name: "RefreshSeries".to_owned(), - series_id: Some(id), + series_id: Some(series_id), ..SonarrCommandBody::default() }; @@ -2754,63 +2320,6 @@ impl<'a, 'b> Network<'a, 'b> { }) .collect() } - - async fn extract_series_id(&mut self, series_id: Option) -> (i64, String) { - let series_id = if let Some(id) = series_id { - id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .series - .current_selection() - .id - }; - (series_id, format!("seriesId={series_id}")) - } - - async fn extract_season_number(&mut self, season_number: Option) -> Result<(i64, String)> { - if let Some(number) = season_number { - Ok((number, format!("seasonNumber={number}"))) - } else if !self.app.lock().await.data.sonarr_data.seasons.is_empty() { - let season_number = self - .app - .lock() - .await - .data - .sonarr_data - .seasons - .current_selection() - .season_number; - Ok((season_number, format!("seasonNumber={season_number}"))) - } else { - Err(anyhow!("No season number provided")) - } - } - - async fn extract_episode_id(&mut self, episode_id: Option) -> i64 { - let episode_id = if let Some(id) = episode_id { - id - } else { - self - .app - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .expect("Season details have not been loaded") - .episodes - .current_selection() - .id - }; - - episode_id - } } fn get_episode_status(has_file: bool, downloads_vec: &[DownloadRecord], episode_id: i64) -> String { diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index ee9425d..ef249f8 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -5,32 +5,31 @@ mod test { use bimap::BiMap; use chrono::DateTime; use indoc::formatdoc; - use mockito::{Matcher, Server}; + use mockito::Matcher; use pretty_assertions::{assert_eq, assert_str_eq}; use reqwest::Client; use rstest::rstest; use serde_json::json; use serde_json::{Number, Value}; - use strum::IntoEnumIterator; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; use crate::models::sonarr_models::{ AddSeriesBody, AddSeriesOptions, AddSeriesSearchResult, AddSeriesSearchResultStatistics, - DownloadStatus, EditSeriesParams, IndexerSettings, MonitorEpisodeBody, SeriesMonitor, - SonarrHistoryEventType, + DownloadStatus, EditSeriesParams, IndexerSettings, MonitorEpisodeBody, SonarrHistoryEventType, }; - use crate::app::{App, ServarrConfig}; + use crate::app::App; use crate::models::radarr_models::IndexerTestResult; - use crate::models::servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem}; + use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::sonarr::modals::{ - AddSeriesModal, EditSeriesModal, EpisodeDetailsModal, SeasonDetailsModal, + AddSeriesModal, EpisodeDetailsModal, SeasonDetailsModal, }; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_models::{ - DiskSpace, EditIndexerParams, HostConfig, Indexer, IndexerField, Language, LogResponse, - Quality, QualityProfile, QualityWrapper, QueueEvent, RootFolder, SecurityConfig, Tag, Update, + AddRootFolderBody, 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, @@ -42,7 +41,7 @@ mod test { use crate::models::sonarr_models::{SonarrTask, SystemStatus}; use crate::models::stateful_table::StatefulTable; use crate::models::{sonarr_models::SonarrSerdeable, stateful_table::SortOption}; - use crate::models::{HorizontallyScrollableText, Scrollable, ScrollableText}; + use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::network::sonarr_network::get_episode_status; use crate::{ @@ -141,7 +140,7 @@ mod test { fn test_resource_all_indexer_settings( #[values( SonarrEvent::GetAllIndexerSettings, - SonarrEvent::EditAllIndexerSettings(None) + SonarrEvent::EditAllIndexerSettings(IndexerSettings::default()) )] event: SonarrEvent, ) { @@ -150,8 +149,7 @@ mod test { #[rstest] fn test_resource_episode( - #[values(SonarrEvent::GetEpisodes(None), SonarrEvent::GetEpisodeDetails(None))] - event: SonarrEvent, + #[values(SonarrEvent::GetEpisodes(0), SonarrEvent::GetEpisodeDetails(0))] event: SonarrEvent, ) { assert_str_eq!(event.resource(), "/episode"); } @@ -159,12 +157,12 @@ mod test { #[rstest] fn test_resource_series( #[values( - SonarrEvent::AddSeries(None), + SonarrEvent::AddSeries(AddSeriesBody::default()), SonarrEvent::ListSeries, - SonarrEvent::GetSeriesDetails(None), - SonarrEvent::DeleteSeries(None), - SonarrEvent::EditSeries(None), - SonarrEvent::ToggleSeasonMonitoring(None) + SonarrEvent::GetSeriesDetails(0), + SonarrEvent::DeleteSeries(DeleteSeriesParams::default()), + SonarrEvent::EditSeries(EditSeriesParams::default()), + SonarrEvent::ToggleSeasonMonitoring((0, 0)) )] event: SonarrEvent, ) { @@ -194,12 +192,12 @@ mod test { fn test_resource_command( #[values( SonarrEvent::GetQueuedEvents, - SonarrEvent::StartTask(None), - SonarrEvent::TriggerAutomaticEpisodeSearch(None), - SonarrEvent::TriggerAutomaticSeasonSearch(None), - SonarrEvent::TriggerAutomaticSeriesSearch(None), + SonarrEvent::StartTask(SonarrTaskName::default()), + SonarrEvent::TriggerAutomaticEpisodeSearch(0), + SonarrEvent::TriggerAutomaticSeasonSearch((0, 0)), + SonarrEvent::TriggerAutomaticSeriesSearch(0), SonarrEvent::UpdateAllSeries, - SonarrEvent::UpdateAndScanSeries(None), + SonarrEvent::UpdateAndScanSeries(0), SonarrEvent::UpdateDownloads )] event: SonarrEvent, @@ -211,8 +209,8 @@ mod test { fn test_resource_indexer( #[values( SonarrEvent::GetIndexers, - SonarrEvent::DeleteIndexer(None), - SonarrEvent::EditIndexer(None) + SonarrEvent::DeleteIndexer(0), + SonarrEvent::EditIndexer(EditIndexerParams::default()) )] event: SonarrEvent, ) { @@ -221,8 +219,7 @@ mod test { #[rstest] fn test_resource_history( - #[values(SonarrEvent::GetHistory(None), SonarrEvent::GetEpisodeHistory(None))] - event: SonarrEvent, + #[values(SonarrEvent::GetHistory(0), SonarrEvent::GetEpisodeHistory(0))] event: SonarrEvent, ) { assert_str_eq!(event.resource(), "/history"); } @@ -230,8 +227,8 @@ mod test { #[rstest] fn test_resource_series_history( #[values( - SonarrEvent::GetSeriesHistory(None), - SonarrEvent::GetSeasonHistory(None) + SonarrEvent::GetSeriesHistory(0), + SonarrEvent::GetSeasonHistory((0, 0)) )] event: SonarrEvent, ) { @@ -240,7 +237,7 @@ mod test { #[rstest] fn test_resource_queue( - #[values(SonarrEvent::GetDownloads, SonarrEvent::DeleteDownload(None))] event: SonarrEvent, + #[values(SonarrEvent::GetDownloads, SonarrEvent::DeleteDownload(0))] event: SonarrEvent, ) { assert_str_eq!(event.resource(), "/queue"); } @@ -249,8 +246,8 @@ mod test { fn test_resource_root_folder( #[values( SonarrEvent::GetRootFolders, - SonarrEvent::DeleteRootFolder(None), - SonarrEvent::AddRootFolder(None) + SonarrEvent::DeleteRootFolder(0), + SonarrEvent::AddRootFolder(AddRootFolderBody::default()) )] event: SonarrEvent, ) { @@ -260,8 +257,8 @@ mod test { #[rstest] fn test_resource_release( #[values( - SonarrEvent::GetSeasonReleases(None), - SonarrEvent::GetEpisodeReleases(None) + SonarrEvent::GetSeasonReleases((0, 0)), + SonarrEvent::GetEpisodeReleases(0) )] event: SonarrEvent, ) { @@ -270,10 +267,7 @@ mod test { #[rstest] fn test_resource_episode_file( - #[values( - SonarrEvent::GetEpisodeFiles(None), - SonarrEvent::DeleteEpisodeFile(None) - )] + #[values(SonarrEvent::GetEpisodeFiles(0), SonarrEvent::DeleteEpisodeFile(0))] event: SonarrEvent, ) { assert_str_eq!(event.resource(), "/episodefile"); @@ -281,21 +275,21 @@ mod test { #[rstest] #[case(SonarrEvent::ClearBlocklist, "/blocklist/bulk")] - #[case(SonarrEvent::DeleteBlocklistItem(None), "/blocklist")] + #[case(SonarrEvent::DeleteBlocklistItem(0), "/blocklist")] #[case(SonarrEvent::HealthCheck, "/health")] #[case(SonarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")] #[case(SonarrEvent::GetDiskSpace, "/diskspace")] #[case(SonarrEvent::GetLanguageProfiles, "/language")] - #[case(SonarrEvent::GetLogs(Some(500)), "/log")] + #[case(SonarrEvent::GetLogs(500), "/log")] #[case(SonarrEvent::GetQualityProfiles, "/qualityprofile")] #[case(SonarrEvent::GetStatus, "/system/status")] #[case(SonarrEvent::GetTasks, "/system/task")] #[case(SonarrEvent::GetUpdates, "/update")] #[case(SonarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")] - #[case(SonarrEvent::SearchNewSeries(None), "/series/lookup")] - #[case(SonarrEvent::TestIndexer(None), "/indexer/test")] + #[case(SonarrEvent::SearchNewSeries(String::new()), "/series/lookup")] + #[case(SonarrEvent::TestIndexer(0), "/indexer/test")] #[case(SonarrEvent::TestAllIndexers, "/indexer/testall")] - #[case(SonarrEvent::ToggleEpisodeMonitoring(None), "/episode/monitor")] + #[case(SonarrEvent::ToggleEpisodeMonitoring(0), "/episode/monitor")] fn test_resource(#[case] event: SonarrEvent, #[case] expected_uri: String) { assert_str_eq!(event.resource(), expected_uri); } @@ -310,6 +304,9 @@ mod test { #[tokio::test] async fn test_handle_add_sonarr_root_folder_event() { + let expected_add_root_folder_body = AddRootFolderBody { + path: "/nfs/test".to_owned(), + }; let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Post, Some(json!({ @@ -317,49 +314,15 @@ mod test { })), Some(json!({})), None, - SonarrEvent::AddRootFolder(None), + SonarrEvent::AddRootFolder(expected_add_root_folder_body.clone()), None, None, ) .await; - - app_arc.lock().await.data.sonarr_data.edit_root_folder = Some("/nfs/test".into()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::AddRootFolder(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .edit_root_folder - .is_none()); - } - - #[tokio::test] - async fn test_handle_add_sonarr_root_folder_event_uses_provided_path() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "path": "/test/test" - })), - Some(json!({})), - None, - SonarrEvent::AddRootFolder(None), - None, - None, - ) - .await; - - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::AddRootFolder(Some("/test/test".to_owned()))) + .handle_sonarr_event(SonarrEvent::AddRootFolder(expected_add_root_folder_body)) .await .is_ok()); @@ -375,6 +338,23 @@ mod test { #[tokio::test] async fn test_handle_add_sonarr_series_event() { + let expected_add_series_body = AddSeriesBody { + tvdb_id: 1234, + title: "Test".to_owned(), + monitored: true, + root_folder_path: "/nfs2".to_owned(), + quality_profile_id: 2222, + language_profile_id: 2222, + series_type: "standard".to_owned(), + season_folder: true, + tags: Vec::new(), + tag_input_string: Some("usenet, testing".to_owned()), + add_options: AddSeriesOptions { + monitor: "all".to_owned(), + search_for_cutoff_unmet_episodes: true, + search_for_missing_episodes: true, + }, + }; let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Post, Some(json!({ @@ -395,103 +375,27 @@ mod test { })), Some(json!({})), None, - SonarrEvent::AddSeries(None), + SonarrEvent::AddSeries(expected_add_series_body.clone()), None, None, ) .await; - - { - let mut app = app_arc.lock().await; - let mut add_series_modal = AddSeriesModal { - use_season_folder: true, - tags: "usenet, testing".into(), - ..AddSeriesModal::default() - }; - add_series_modal.root_folder_list.set_items(vec![ - RootFolder { - id: 1, - path: "/nfs".to_owned(), - accessible: true, - free_space: 219902325555200, - unmapped_folders: None, - }, - RootFolder { - id: 2, - path: "/nfs2".to_owned(), - accessible: true, - free_space: 21990232555520, - unmapped_folders: None, - }, - ]); - add_series_modal.root_folder_list.state.select(Some(1)); - add_series_modal - .quality_profile_list - .set_items(vec!["HD - 1080p".to_owned()]); - add_series_modal - .language_profile_list - .set_items(vec!["English".to_owned()]); - add_series_modal - .monitor_list - .set_items(Vec::from_iter(SeriesMonitor::iter())); - add_series_modal - .series_type_list - .set_items(Vec::from_iter(SeriesType::iter())); - app.data.sonarr_data.add_series_modal = Some(add_series_modal); - app.data.sonarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - app.data.sonarr_data.language_profiles_map = BiMap::from_iter([(2222, "English".to_owned())]); - app.data.sonarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - let mut add_searched_series = StatefulTable::default(); - add_searched_series.set_items(vec![add_series_search_result()]); - app.data.sonarr_data.add_searched_series = Some(add_searched_series); - } + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::AddSeries(None)) + .handle_sonarr_event(SonarrEvent::AddSeries(expected_add_series_body)) .await .is_ok()); async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .add_series_modal - .is_none()); } #[tokio::test] - async fn test_handle_add_sonarr_series_event_uses_provided_body() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "tvdbId": 1234, - "title": "Test", - "monitored": true, - "rootFolderPath": "/nfs2", - "qualityProfileId": 2222, - "languageProfileId": 2222, - "seriesType": "standard", - "seasonFolder": true, - "tags": [1, 2], - "addOptions": { - "monitor": "standard", - "searchForCutoffUnmetEpisodes": true, - "searchForMissingEpisodes": true - } - })), - Some(json!({})), - None, - SonarrEvent::AddSeries(None), - None, - None, - ) - .await; - let body = AddSeriesBody { + async fn test_handle_add_sonarr_series_event_does_not_overwrite_tags_vec_when_tag_input_string_is_none( + ) { + let expected_add_series_body = AddSeriesBody { tvdb_id: 1234, title: "Test".to_owned(), monitored: true, @@ -501,36 +405,17 @@ mod test { series_type: "standard".to_owned(), season_folder: true, tags: vec![1, 2], + tag_input_string: None, add_options: AddSeriesOptions { - monitor: "standard".to_owned(), + monitor: "all".to_owned(), search_for_cutoff_unmet_episodes: true, search_for_missing_episodes: true, }, }; - - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::AddSeries(Some(body))) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .add_series_modal - .is_none()); - } - - #[tokio::test] - async fn test_handle_add_sonarr_series_event_reuse_existing_table_if_search_already_performed() { let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Post, Some(json!({ - "tvdbId": 5678, + "tvdbId": 1234, "title": "Test", "monitored": true, "rootFolderPath": "/nfs2", @@ -547,91 +432,19 @@ mod test { })), Some(json!({})), None, - SonarrEvent::AddSeries(None), + SonarrEvent::AddSeries(expected_add_series_body.clone()), None, None, ) .await; - - { - let mut app = app_arc.lock().await; - let mut add_series_modal = AddSeriesModal { - use_season_folder: true, - tags: "usenet, testing".into(), - ..AddSeriesModal::default() - }; - add_series_modal.root_folder_list.set_items(vec![ - RootFolder { - id: 1, - path: "/nfs".to_owned(), - accessible: true, - free_space: 219902325555200, - unmapped_folders: None, - }, - RootFolder { - id: 2, - path: "/nfs2".to_owned(), - accessible: true, - free_space: 21990232555520, - unmapped_folders: None, - }, - ]); - add_series_modal.root_folder_list.state.select(Some(1)); - add_series_modal - .quality_profile_list - .set_items(vec!["HD - 1080p".to_owned()]); - add_series_modal - .language_profile_list - .set_items(vec!["English".to_owned()]); - add_series_modal - .monitor_list - .set_items(Vec::from_iter(SeriesMonitor::iter())); - add_series_modal - .series_type_list - .set_items(Vec::from_iter(SeriesType::iter())); - app.data.sonarr_data.add_series_modal = Some(add_series_modal); - app.data.sonarr_data.quality_profile_map = - BiMap::from_iter([(2222, "HD - 1080p".to_owned())]); - app.data.sonarr_data.language_profiles_map = BiMap::from_iter([(2222, "English".to_owned())]); - app.data.sonarr_data.tags_map = - BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); - let secondary_search_result = AddSeriesSearchResult { - tvdb_id: 5678, - ..add_series_search_result() - }; - let mut add_searched_series = StatefulTable::default(); - add_searched_series.set_items(vec![add_series_search_result(), secondary_search_result]); - add_searched_series.scroll_to_bottom(); - app.data.sonarr_data.add_searched_series = Some(add_searched_series); - } let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::AddSeries(None)) + .handle_sonarr_event(SonarrEvent::AddSeries(expected_add_series_body)) .await .is_ok()); async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .add_series_modal - .is_none()); - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .add_searched_series - .as_ref() - .unwrap() - .current_selection() - .tvdb_id, - 5678 - ); } #[tokio::test] @@ -721,7 +534,7 @@ mod test { None, None, None, - SonarrEvent::DeleteBlocklistItem(None), + SonarrEvent::DeleteBlocklistItem(1), Some("/1"), None, ) @@ -736,7 +549,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteBlocklistItem(None)) + .handle_sonarr_event(SonarrEvent::DeleteBlocklistItem(1)) .await .is_ok()); @@ -750,76 +563,23 @@ mod test { None, None, None, - SonarrEvent::DeleteEpisodeFile(None), + SonarrEvent::DeleteEpisodeFile(1), Some("/1"), None, ) .await; app_arc.lock().await.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default()); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .episodes - .set_items(vec![episode()]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteEpisodeFile(None)) + .handle_sonarr_event(SonarrEvent::DeleteEpisodeFile(1)) .await .is_ok()); async_server.assert_async().await; } - #[tokio::test] - async fn test_handle_delete_sonarr_episode_file_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteEpisodeFile(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::DeleteEpisodeFile(Some(1))) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - #[should_panic(expected = "Season details have not been loaded")] - async fn test_handle_delete_sonarr_episode_file_event_empty_season_details_modal_panics() { - let (_async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteEpisodeFile(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - network - .handle_sonarr_event(SonarrEvent::DeleteEpisodeFile(None)) - .await - .unwrap(); - } - #[tokio::test] async fn test_handle_delete_sonarr_download_event() { let (async_server, app_arc, _server) = mock_servarr_api( @@ -827,7 +587,7 @@ mod test { None, None, None, - SonarrEvent::DeleteDownload(None), + SonarrEvent::DeleteDownload(1), Some("/1"), None, ) @@ -842,29 +602,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteDownload(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_sonarr_download_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteDownload(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::DeleteDownload(Some(1))) + .handle_sonarr_event(SonarrEvent::DeleteDownload(1)) .await .is_ok()); @@ -878,7 +616,7 @@ mod test { None, None, None, - SonarrEvent::DeleteIndexer(None), + SonarrEvent::DeleteIndexer(1), Some("/1"), None, ) @@ -893,29 +631,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteIndexer(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_sonarr_indexer_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteIndexer(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::DeleteIndexer(Some(1))) + .handle_sonarr_event(SonarrEvent::DeleteIndexer(1)) .await .is_ok()); @@ -929,36 +645,7 @@ mod test { None, None, None, - SonarrEvent::DeleteRootFolder(None), - Some("/1"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .sonarr_data - .root_folders - .set_items(vec![root_folder()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::DeleteRootFolder(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_delete_sonarr_root_folder_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteRootFolder(None), + SonarrEvent::DeleteRootFolder(1), Some("/1"), None, ) @@ -966,7 +653,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteRootFolder(Some(1))) + .handle_sonarr_event(SonarrEvent::DeleteRootFolder(1)) .await .is_ok()); @@ -975,61 +662,29 @@ mod test { #[tokio::test] async fn test_handle_delete_series_event() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteSeries(None), - Some("/1"), - Some("deleteFiles=true&addImportExclusion=true"), - ) - .await; - { - let mut app = app_arc.lock().await; - app.data.sonarr_data.series.set_items(vec![series()]); - app.data.sonarr_data.delete_series_files = true; - app.data.sonarr_data.add_list_exclusion = true; - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::DeleteSeries(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(!app_arc.lock().await.data.sonarr_data.delete_series_files); - assert!(!app_arc.lock().await.data.sonarr_data.add_list_exclusion); - } - - #[tokio::test] - async fn test_handle_delete_series_event_use_provided_params() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Delete, - None, - None, - None, - SonarrEvent::DeleteSeries(None), - Some("/1"), - Some("deleteFiles=true&addImportExclusion=true"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); let delete_series_params = DeleteSeriesParams { id: 1, delete_series_files: true, add_list_exclusion: true, }; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Delete, + None, + None, + None, + SonarrEvent::DeleteSeries(delete_series_params.clone()), + Some("/1"), + Some("deleteFiles=true&addImportExclusion=true"), + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::DeleteSeries(Some(delete_series_params))) + .handle_sonarr_event(SonarrEvent::DeleteSeries(delete_series_params)) .await .is_ok()); async_server.assert_async().await; - assert!(!app_arc.lock().await.data.sonarr_data.delete_series_files); - assert!(!app_arc.lock().await.data.sonarr_data.add_list_exclusion); } #[tokio::test] @@ -1100,45 +755,7 @@ mod test { Some(indexer_settings_json), None, None, - SonarrEvent::EditAllIndexerSettings(None), - None, - None, - ) - .await; - - app_arc.lock().await.data.sonarr_data.indexer_settings = Some(indexer_settings()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::EditAllIndexerSettings(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .indexer_settings - .is_none()); - } - - #[tokio::test] - async fn test_handle_edit_all_indexer_settings_event_uses_provided_settings() { - let indexer_settings_json = json!({ - "id": 1, - "minimumAge": 1, - "maximumSize": 12345, - "retention": 1, - "rssSyncInterval": 60 - }); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Put, - Some(indexer_settings_json), - None, - None, - SonarrEvent::EditAllIndexerSettings(None), + SonarrEvent::EditAllIndexerSettings(indexer_settings()), None, None, ) @@ -1147,9 +764,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditAllIndexerSettings( - Some(indexer_settings()) - )) + .handle_sonarr_event(SonarrEvent::EditAllIndexerSettings(indexer_settings())) .await .is_ok()); @@ -1158,6 +773,19 @@ mod test { #[tokio::test] async fn test_handle_edit_sonarr_indexer_event() { + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -1204,7 +832,6 @@ mod test { "tags": [1, 2], "id": 1 }); - let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, @@ -1220,7 +847,7 @@ mod test { "PUT", format!( "/api/v3{}/1?forceSave=true", - SonarrEvent::EditIndexer(None).resource() + SonarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource() ) .as_str(), ) @@ -1229,315 +856,23 @@ mod test { .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(), - priority: 0, - }; - app.data.sonarr_data.edit_indexer_modal = Some(edit_indexer_modal); - app.data.sonarr_data.indexers.set_items(vec![indexer()]); - } + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditIndexer(None)) + .handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params)) .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( + async fn test_handle_edit_sonarr_indexer_event_does_not_overwrite_tags_vec_if_tag_input_string_is_none( ) { - 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": 0, - "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?forceSave=true", - 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(), - priority: 0, - }; - 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": 0, - "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?forceSave=true", - 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(), - priority: 0, - }; - 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 { + let expected_edit_indexer_params = EditIndexerParams { indexer_id: 1, name: Some("Test Update".to_owned()), enable_rss: Some(false), @@ -1547,9 +882,144 @@ mod test { api_key: Some("test1234".to_owned()), seed_ratio: Some("1.3".to_owned()), tags: Some(vec![1, 2]), - priority: Some(25), + priority: Some(0), ..EditIndexerParams::default() }; + 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": 0, + "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?forceSave=true", + SonarrEvent::EditIndexer(expected_edit_indexer_params.clone()).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(expected_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_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details( + ) { + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; + 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": 0, + "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, @@ -1566,7 +1036,7 @@ mod test { "PUT", format!( "/api/v3{}/1?forceSave=true", - SonarrEvent::EditIndexer(None).resource() + SonarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource() ) .as_str(), ) @@ -1575,10 +1045,12 @@ mod test { .match_body(Matcher::Json(expected_indexer_edit_body_json)) .create_async() .await; + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params)) .await .is_ok()); @@ -1587,8 +1059,106 @@ mod test { } #[tokio::test] - async fn test_handle_edit_sonarr_indexer_event_uses_provided_parameters_defaults_to_previous_values( + async fn test_handle_edit_sonarr_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details( ) { + let expected_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()), + tag_input_string: Some("usenet, testing".to_owned()), + priority: Some(0), + ..EditIndexerParams::default() + }; + 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": 0, + "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?forceSave=true", + SonarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_indexer_edit_body_json)) + .create_async() + .await; + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditIndexer(expected_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_defaults_to_previous_values() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -1616,7 +1186,6 @@ mod test { indexer_id: 1, ..EditIndexerParams::default() }; - let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, @@ -1632,7 +1201,7 @@ mod test { "PUT", format!( "/api/v3{}/1?forceSave=true", - SonarrEvent::EditIndexer(None).resource() + SonarrEvent::EditIndexer(edit_indexer_params.clone()).resource() ) .as_str(), ) @@ -1644,7 +1213,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_sonarr_event(SonarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); @@ -1653,8 +1222,7 @@ mod test { } #[tokio::test] - async fn test_handle_edit_sonarr_indexer_event_uses_provided_parameters_clears_tags_when_clear_tags_is_true( - ) { + async fn test_handle_edit_sonarr_indexer_event_clears_tags_when_clear_tags_is_true() { let indexer_details_json = json!({ "enableRss": true, "enableAutomaticSearch": true, @@ -1722,7 +1290,7 @@ mod test { "PUT", format!( "/api/v3{}/1?forceSave=true", - SonarrEvent::EditIndexer(None).resource() + SonarrEvent::EditIndexer(edit_indexer_params.clone()).resource() ) .as_str(), ) @@ -1734,7 +1302,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditIndexer(Some(edit_indexer_params))) + .handle_sonarr_event(SonarrEvent::EditIndexer(edit_indexer_params)) .await .is_ok()); @@ -1752,13 +1320,24 @@ mod test { *expected_body.get_mut("languageProfileId").unwrap() = json!(1111); *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("tags").unwrap() = json!([1, 2]); + let edit_series_params = EditSeriesParams { + series_id: 1, + monitored: Some(false), + use_season_folders: Some(false), + series_type: Some(SeriesType::Standard), + quality_profile_id: Some(1111), + language_profile_id: Some(1111), + root_folder_path: Some("/nfs/Test Path".to_owned()), + tag_input_string: Some("usenet, testing".to_owned()), + ..EditSeriesParams::default() + }; let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, Some(serde_json::from_str(SERIES_JSON).unwrap()), None, - SonarrEvent::GetSeriesDetails(None), + SonarrEvent::GetSeriesDetails(1), Some("/1"), None, ) @@ -1766,60 +1345,33 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", SonarrEvent::EditSeries(None).resource()).as_str(), + format!( + "/api/v3{}/1", + SonarrEvent::EditSeries(edit_series_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .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 mut edit_series = EditSeriesModal { - tags: "usenet, testing".to_owned().into(), - path: "/nfs/Test Path".to_owned().into(), - monitored: Some(false), - use_season_folders: Some(false), - ..EditSeriesModal::default() - }; - edit_series - .quality_profile_list - .set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]); - edit_series - .language_profile_list - .set_items(vec!["Any".to_owned(), "English".to_owned()]); - edit_series - .series_type_list - .set_items(Vec::from_iter(SeriesType::iter())); - app.data.sonarr_data.edit_series_modal = Some(edit_series); - app.data.sonarr_data.series.set_items(vec![Series { - monitored: false, - season_folder: false, - ..series() - }]); - app.data.sonarr_data.quality_profile_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]); - app.data.sonarr_data.language_profiles_map = - BiMap::from_iter([(1111, "Any".to_owned()), (2222, "English".to_owned())]); - } + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditSeries(None)) + .handle_sonarr_event(SonarrEvent::EditSeries(edit_series_params)) .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_series_modal.is_none()); } #[tokio::test] - async fn test_handle_edit_series_event_uses_provided_parameters() { + async fn test_handle_edit_series_event_does_not_overwrite_tag_ids_vec_when_tag_input_string_is_none( + ) { let mut expected_body: Value = serde_json::from_str(SERIES_JSON).unwrap(); *expected_body.get_mut("monitored").unwrap() = json!(false); *expected_body.get_mut("seasonFolder").unwrap() = json!(false); @@ -1828,27 +1380,6 @@ mod test { *expected_body.get_mut("languageProfileId").unwrap() = json!(1111); *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("tags").unwrap() = json!([1, 2]); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(SERIES_JSON).unwrap()), - None, - SonarrEvent::GetSeriesDetails(None), - Some("/1"), - None, - ) - .await; - let async_edit_server = server - .mock( - "PUT", - format!("/api/v3{}/1", SonarrEvent::EditSeries(None).resource()).as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; let edit_series_params = EditSeriesParams { series_id: 1, monitored: Some(false), @@ -1860,26 +1391,13 @@ mod test { tags: Some(vec![1, 2]), ..EditSeriesParams::default() }; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - assert!(network - .handle_sonarr_event(SonarrEvent::EditSeries(Some(edit_series_params))) - .await - .is_ok()); - - async_details_server.assert_async().await; - async_edit_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_edit_series_event_uses_provided_parameters_defaults_to_previous_values() { - let expected_body: Value = serde_json::from_str(SERIES_JSON).unwrap(); let (async_details_server, app_arc, mut server) = mock_servarr_api( RequestMethod::Get, None, Some(serde_json::from_str(SERIES_JSON).unwrap()), None, - SonarrEvent::GetSeriesDetails(None), + SonarrEvent::GetSeriesDetails(1), Some("/1"), None, ) @@ -1887,21 +1405,23 @@ mod test { let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", SonarrEvent::EditSeries(None).resource()).as_str(), + format!( + "/api/v3{}/1", + SonarrEvent::EditSeries(edit_series_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .create_async() .await; - let edit_series_params = EditSeriesParams { - series_id: 1, - ..EditSeriesParams::default() - }; + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditSeries(Some(edit_series_params))) + .handle_sonarr_event(SonarrEvent::EditSeries(edit_series_params)) .await .is_ok()); @@ -1910,8 +1430,49 @@ mod test { } #[tokio::test] - async fn test_handle_edit_series_event_uses_provided_parameters_returns_empty_tags_vec_when_clear_tags_is_true( - ) { + async fn test_handle_edit_series_event_defaults_to_previous_values() { + let edit_series_params = EditSeriesParams { + series_id: 1, + ..EditSeriesParams::default() + }; + let expected_body: Value = serde_json::from_str(SERIES_JSON).unwrap(); + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(SERIES_JSON).unwrap()), + None, + SonarrEvent::GetSeriesDetails(1), + Some("/1"), + None, + ) + .await; + let async_edit_server = server + .mock( + "PUT", + format!( + "/api/v3{}/1", + SonarrEvent::EditSeries(edit_series_params.clone()).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_sonarr_event(SonarrEvent::EditSeries(edit_series_params)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_edit_server.assert_async().await; + } + + #[tokio::test] + async fn test_handle_edit_series_event_returns_empty_tags_vec_when_clear_tags_is_true() { let mut expected_body: Value = serde_json::from_str(SERIES_JSON).unwrap(); *expected_body.get_mut("tags").unwrap() = json!([]); @@ -1920,30 +1481,34 @@ mod test { None, Some(serde_json::from_str(SERIES_JSON).unwrap()), None, - SonarrEvent::GetSeriesDetails(None), + SonarrEvent::GetSeriesDetails(1), Some("/1"), None, ) .await; + let edit_series_params = EditSeriesParams { + series_id: 1, + clear_tags: true, + ..EditSeriesParams::default() + }; let async_edit_server = server .mock( "PUT", - format!("/api/v3{}/1", SonarrEvent::EditSeries(None).resource()).as_str(), + format!( + "/api/v3{}/1", + SonarrEvent::EditSeries(edit_series_params.clone()).resource() + ) + .as_str(), ) .with_status(202) .match_header("X-Api-Key", "test1234") .match_body(Matcher::Json(expected_body)) .create_async() .await; - let edit_series_params = EditSeriesParams { - series_id: 1, - clear_tags: true, - ..EditSeriesParams::default() - }; let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::EditSeries(Some(edit_series_params))) + .handle_sonarr_event(SonarrEvent::EditSeries(edit_series_params)) .await .is_ok()); @@ -2269,7 +1834,7 @@ mod test { None, Some(json!([episode_1, episode_2, episode_3])), None, - SonarrEvent::GetEpisodes(None), + SonarrEvent::GetEpisodes(1), None, Some("seriesId=1"), ) @@ -2311,7 +1876,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episodes(episodes) = network - .handle_sonarr_event(SonarrEvent::GetEpisodes(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodes(1)) .await .unwrap() { @@ -2376,7 +1941,7 @@ mod test { None, Some(json!([episode_1, episode_2, episode_3])), None, - SonarrEvent::GetEpisodes(None), + SonarrEvent::GetEpisodes(1), None, Some("seriesId=1"), ) @@ -2397,7 +1962,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episodes(episodes) = network - .handle_sonarr_event(SonarrEvent::GetEpisodes(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodes(1)) .await .unwrap() { @@ -2438,7 +2003,7 @@ mod test { None, Some(json!([episode()])), None, - SonarrEvent::GetEpisodes(None), + SonarrEvent::GetEpisodes(1), None, Some("seriesId=1"), ) @@ -2456,7 +2021,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episodes(episodes) = network - .handle_sonarr_event(SonarrEvent::GetEpisodes(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodes(1)) .await .unwrap() { @@ -2526,7 +2091,7 @@ mod test { None, Some(episodes_json), None, - SonarrEvent::GetEpisodes(None), + SonarrEvent::GetEpisodes(1), None, Some("seriesId=1"), ) @@ -2560,7 +2125,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episodes(episodes) = network - .handle_sonarr_event(SonarrEvent::GetEpisodes(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodes(1)) .await .unwrap() { @@ -2598,7 +2163,7 @@ mod test { None, Some(json!([episode_file()])), None, - SonarrEvent::GetEpisodeFiles(None), + SonarrEvent::GetEpisodeFiles(1), None, Some("seriesId=1"), ) @@ -2618,7 +2183,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::EpisodeFiles(episode_files) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeFiles(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeFiles(1)) .await .unwrap() { @@ -2647,7 +2212,7 @@ mod test { None, Some(json!([episode_file()])), None, - SonarrEvent::GetEpisodeFiles(None), + SonarrEvent::GetEpisodeFiles(1), None, Some("seriesId=1"), ) @@ -2665,7 +2230,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::EpisodeFiles(episode_files) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeFiles(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeFiles(1)) .await .unwrap() { @@ -2694,59 +2259,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_episode_files_event_uses_provided_series_id() { - let episode_file = EpisodeFile { - id: 2, - ..episode_file() - }; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(json!([episode_file.clone()])), - None, - SonarrEvent::GetEpisodeFiles(Some(2)), - None, - Some("seriesId=2"), - ) - .await; - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![Series { - id: 1, - ..Series::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::EpisodeFiles(episode_files) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeFiles(Some(2))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .episode_files - .items, - vec![episode_file.clone()] - ); - assert_eq!(episode_files, vec![episode_file]); - } - } - #[tokio::test] async fn test_handle_get_sonarr_host_config_event() { let host_config_response = json!({ @@ -2832,7 +2344,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetHistory(None), + SonarrEvent::GetHistory(500), None, Some("pageSize=500&sortDirection=descending&sortKey=date"), ) @@ -2862,78 +2374,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetHistory(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.sonarr_data.history.items, - expected_history_items - ); - assert!(app_arc.lock().await.data.sonarr_data.history.sort_asc); - assert_eq!(history, response); - } - } - - #[tokio::test] - async fn test_handle_get_sonarr_history_event_uses_provided_items() { - let history_json = json!({"records": [{ - "id": 123, - "sourceTitle": "z episode", - "episodeId": 1007, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }, - { - "id": 456, - "sourceTitle": "A Episode", - "episodeId": 2001, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }]}); - let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); - let expected_history_items = vec![ - SonarrHistoryItem { - id: 123, - episode_id: 1007, - source_title: "z episode".into(), - ..history_item() - }, - SonarrHistoryItem { - id: 456, - episode_id: 2001, - source_title: "A Episode".into(), - ..history_item() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(history_json), - None, - SonarrEvent::GetHistory(Some(1000)), - None, - Some("pageSize=1000&sortDirection=descending&sortKey=date"), - ) - .await; - app_arc.lock().await.data.sonarr_data.history.sort_asc = true; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetHistory(Some(1000))) + .handle_sonarr_event(SonarrEvent::GetHistory(500)) .await .unwrap() { @@ -2981,7 +2422,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetHistory(None), + SonarrEvent::GetHistory(500), None, Some("pageSize=500&sortDirection=descending&sortKey=date"), ) @@ -3011,7 +2452,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetHistory(None)) + .handle_sonarr_event(SonarrEvent::GetHistory(500)) .await .unwrap() { @@ -3081,73 +2522,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_episodes_event_uses_provided_series_id() { - let episodes_json = json!([ - { - "id": 2, - "seriesId": 2, - "tvdbId": 1234, - "episodeFileId": 2, - "seasonNumber": 2, - "episodeNumber": 2, - "title": "Something cool", - "airDateUtc": "2024-02-10T07:28:45Z", - "overview": "Okay so this one time at band camp...", - "hasFile": true, - "monitored": true - }, - { - "id": 1, - "seriesId": 2, - "tvdbId": 1234, - "episodeFileId": 1, - "seasonNumber": 1, - "episodeNumber": 1, - "title": "Something cool", - "airDateUtc": "2024-02-10T07:28:45Z", - "overview": "Okay so this one time at band camp...", - "hasFile": true, - "monitored": true - } - ]); - let episode_1 = Episode { - series_id: 2, - episode_file: None, - ..episode() - }; - let episode_2 = Episode { - id: 2, - episode_file_id: 2, - season_number: 2, - episode_number: 2, - series_id: 2, - episode_file: None, - ..episode() - }; - let expected_episodes = vec![episode_2.clone(), episode_1.clone()]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(episodes_json), - None, - SonarrEvent::GetEpisodes(None), - None, - Some("seriesId=2"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Episodes(episodes) = network - .handle_sonarr_event(SonarrEvent::GetEpisodes(Some(2))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(episodes, expected_episodes); - } - } - #[tokio::test] async fn test_handle_get_episode_details_event() { let response: Episode = serde_json::from_str(EPISODE_JSON).unwrap(); @@ -3156,7 +2530,7 @@ mod test { None, Some(serde_json::from_str(EPISODE_JSON).unwrap()), None, - SonarrEvent::GetEpisodeDetails(None), + SonarrEvent::GetEpisodeDetails(1), Some("/1"), None, ) @@ -3174,7 +2548,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episode(episode) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(1)) .await .unwrap() { @@ -3272,7 +2646,7 @@ mod test { None, Some(serde_json::from_str(EPISODE_JSON).unwrap()), None, - SonarrEvent::GetEpisodeDetails(None), + SonarrEvent::GetEpisodeDetails(1), Some("/1"), None, ) @@ -3287,7 +2661,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episode(episode) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(1)) .await .unwrap() { @@ -3361,34 +2735,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_get_episode_details_event_uses_provided_id() { - let response: Episode = serde_json::from_str(EPISODE_JSON).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(EPISODE_JSON).unwrap()), - None, - SonarrEvent::GetEpisodeDetails(None), - Some("/1"), - None, - ) - .await; - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![episode()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Episode(episode) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(Some(1))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(episode, response); - } - } - #[tokio::test] async fn test_handle_get_sonarr_episode_history_event() { let history_json = json!({"records": [{ @@ -3437,7 +2783,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetEpisodeHistory(None), + SonarrEvent::GetEpisodeHistory(1), None, Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), ) @@ -3479,137 +2825,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .episode_details_modal - .as_ref() - .unwrap() - .episode_history - .items, - expected_history_items - ); - assert!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .episode_details_modal - .as_ref() - .unwrap() - .episode_history - .sort_asc - ); - assert_eq!(history, response); - } - } - - #[tokio::test] - async fn test_handle_get_sonarr_episode_history_event_uses_provided_episode_id() { - let history_json = json!({"records": [{ - "id": 123, - "sourceTitle": "z episode", - "episodeId": 1007, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }, - { - "id": 456, - "sourceTitle": "A Episode", - "episodeId": 2001, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }]}); - let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); - let expected_history_items = vec![ - SonarrHistoryItem { - id: 123, - episode_id: 1007, - source_title: "z episode".into(), - ..history_item() - }, - SonarrHistoryItem { - id: 456, - episode_id: 2001, - source_title: "A Episode".into(), - ..history_item() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(history_json), - None, - SonarrEvent::GetEpisodeHistory(Some(2)), - None, - Some("episodeId=2&pageSize=1000&sortDirection=descending&sortKey=date"), - ) - .await; - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .episodes - .set_items(vec![episode()]); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .episode_details_modal = Some(EpisodeDetailsModal::default()); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .episode_details_modal - .as_mut() - .unwrap() - .episode_history - .sort_asc = true; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(Some(2))) + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(1)) .await .unwrap() { @@ -3697,7 +2913,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetEpisodeHistory(None), + SonarrEvent::GetEpisodeHistory(1), None, Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), ) @@ -3717,7 +2933,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(1)) .await .unwrap() { @@ -3805,7 +3021,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetEpisodeHistory(Some(1)), + SonarrEvent::GetEpisodeHistory(1), None, Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), ) @@ -3813,7 +3029,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryWrapper(history) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(Some(1))) + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(1)) .await .unwrap() { @@ -3861,7 +3077,7 @@ mod test { None, Some(serde_json::from_str(EPISODE_JSON).unwrap()), None, - SonarrEvent::GetEpisodeDetails(None), + SonarrEvent::GetEpisodeDetails(1), Some("/1"), None, ) @@ -3870,7 +3086,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Episode(episode) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(Some(1))) + .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(1)) .await .unwrap() { @@ -3879,28 +3095,6 @@ mod test { } } - #[tokio::test] - #[should_panic(expected = "Season details have not been loaded")] - async fn test_handle_get_episode_details_event_requires_season_details_modal_to_be_some_when_no_parameter_is_passed( - ) { - let (_async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(serde_json::from_str(EPISODE_JSON).unwrap()), - None, - SonarrEvent::GetEpisodeDetails(None), - Some("/1"), - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(None)) - .await - .unwrap(); - } - #[tokio::test] #[should_panic(expected = "Season details modal is empty")] async fn test_handle_get_episode_details_event_requires_season_details_modal_to_be_some_when_in_tui_mode( @@ -3910,7 +3104,7 @@ mod test { None, Some(serde_json::from_str(EPISODE_JSON).unwrap()), None, - SonarrEvent::GetEpisodeDetails(None), + SonarrEvent::GetEpisodeDetails(1), Some("/1"), None, ) @@ -3918,7 +3112,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); network - .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(Some(1))) + .handle_sonarr_event(SonarrEvent::GetEpisodeDetails(1)) .await .unwrap(); } @@ -3994,7 +3188,7 @@ mod test { None, Some(logs_response_json), None, - SonarrEvent::GetLogs(None), + SonarrEvent::GetLogs(500), None, Some("pageSize=500&sortDirection=descending&sortKey=time"), ) @@ -4002,75 +3196,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::LogResponse(logs) = network - .handle_sonarr_event(SonarrEvent::GetLogs(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc.lock().await.data.sonarr_data.logs.items, - expected_logs - ); - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .logs - .current_selection() - .text - .contains("INFO")); - assert_eq!(logs, response); - } - } - - #[tokio::test] - async fn test_handle_get_sonarr_logs_event_uses_provided_events() { - let expected_logs = vec![ - HorizontallyScrollableText::from( - "2023-05-20 21:29:16 UTC|FATAL|SonarrError|Some.Big.Bad.Exception|test exception", - ), - HorizontallyScrollableText::from("2023-05-20 21:29:16 UTC|INFO|TestLogger|test message"), - ]; - let logs_response_json = json!({ - "page": 1, - "pageSize": 1000, - "sortKey": "time", - "sortDirection": "descending", - "totalRecords": 2, - "records": [ - { - "time": "2023-05-20T21:29:16Z", - "level": "info", - "logger": "TestLogger", - "message": "test message", - "id": 1 - }, - { - "time": "2023-05-20T21:29:16Z", - "level": "fatal", - "logger": "SonarrError", - "exception": "test exception", - "exceptionType": "Some.Big.Bad.Exception", - "id": 2 - } - ] - }); - let response: LogResponse = serde_json::from_value(logs_response_json.clone()).unwrap(); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(logs_response_json), - None, - SonarrEvent::GetLogs(Some(1000)), - None, - Some("pageSize=1000&sortDirection=descending&sortKey=time"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::LogResponse(logs) = network - .handle_sonarr_event(SonarrEvent::GetLogs(Some(1000))) + .handle_sonarr_event(SonarrEvent::GetLogs(500)) .await .unwrap() { @@ -4234,7 +3360,7 @@ mod test { None, Some(release_json), None, - SonarrEvent::GetEpisodeReleases(None), + SonarrEvent::GetEpisodeReleases(1), None, Some("episodeId=1"), ) @@ -4254,7 +3380,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(None)) + .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(1)) .await .unwrap() { @@ -4301,7 +3427,7 @@ mod test { None, Some(release_json), None, - SonarrEvent::GetEpisodeReleases(None), + SonarrEvent::GetEpisodeReleases(1), None, Some("episodeId=1"), ) @@ -4312,110 +3438,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .episode_details_modal - .as_ref() - .unwrap() - .episode_releases - .items, - vec![release()] - ); - assert_eq!(releases_vec, vec![release()]); - } - } - - #[tokio::test] - #[should_panic(expected = "Season details have not been loaded")] - async fn test_handle_get_episode_releases_event_empty_season_details_modal_panics() { - let release_json = json!([{ - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }} - }]); - let (_async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(release_json), - None, - SonarrEvent::GetEpisodeReleases(None), - None, - Some("episodeId=1"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - network - .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(None)) - .await - .unwrap(); - } - - #[tokio::test] - async fn test_handle_get_episode_releases_event_uses_provided_series_id() { - let release_json = json!([{ - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }} - }]); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(release_json), - None, - SonarrEvent::GetEpisodeReleases(None), - None, - Some("episodeId=2"), - ) - .await; - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![episode()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .episode_details_modal = Some(EpisodeDetailsModal::default()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(Some(2))) + .handle_sonarr_event(SonarrEvent::GetEpisodeReleases(1)) .await .unwrap() { @@ -4488,7 +3511,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetSeasonHistory(None), + SonarrEvent::GetSeasonHistory((1, 1)), None, Some("seriesId=1&seasonNumber=1"), ) @@ -4522,123 +3545,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryItems(history) = network - .handle_sonarr_event(SonarrEvent::GetSeasonHistory(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .season_history - .items, - expected_history_items - ); - assert!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .season_history - .sort_asc - ); - assert_eq!(history, response); - } - } - - #[tokio::test] - async fn test_handle_get_sonarr_season_history_event_uses_provided_series_id_and_season_number() { - let history_json = json!([{ - "id": 123, - "sourceTitle": "z episode", - "episodeId": 1007, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }, - { - "id": 456, - "sourceTitle": "A Episode", - "episodeId": 2001, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }]); - let response: Vec = serde_json::from_value(history_json.clone()).unwrap(); - let expected_history_items = vec![ - SonarrHistoryItem { - id: 123, - episode_id: 1007, - source_title: "z episode".into(), - ..history_item() - }, - SonarrHistoryItem { - id: 456, - episode_id: 2001, - source_title: "A Episode".into(), - ..history_item() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(history_json), - None, - SonarrEvent::GetSeasonHistory(Some((2, 2))), - None, - Some("seriesId=2&seasonNumber=2"), - ) - .await; - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![season()]); - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_mut() - .unwrap() - .season_history - .sort_asc = true; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::SonarrHistoryItems(history) = network - .handle_sonarr_event(SonarrEvent::GetSeasonHistory(Some((2, 2)))) + .handle_sonarr_event(SonarrEvent::GetSeasonHistory((1, 1))) .await .unwrap() { @@ -4720,7 +3627,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetSeasonHistory(None), + SonarrEvent::GetSeasonHistory((1, 1)), None, Some("seriesId=1&seasonNumber=1"), ) @@ -4742,7 +3649,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryItems(history) = network - .handle_sonarr_event(SonarrEvent::GetSeasonHistory(None)) + .handle_sonarr_event(SonarrEvent::GetSeasonHistory((1, 1))) .await .unwrap() { @@ -4837,7 +3744,7 @@ mod test { None, Some(release_json), None, - SonarrEvent::GetSeasonReleases(None), + SonarrEvent::GetSeasonReleases((1, 1)), None, Some("seriesId=1&seasonNumber=1"), ) @@ -4861,7 +3768,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetSeasonReleases(None)) + .handle_sonarr_event(SonarrEvent::GetSeasonReleases((1, 1))) .await .unwrap() { @@ -4927,7 +3834,7 @@ mod test { None, Some(release_json), None, - SonarrEvent::GetSeasonReleases(None), + SonarrEvent::GetSeasonReleases((1, 1)), None, Some("seriesId=1&seasonNumber=1"), ) @@ -4949,7 +3856,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::GetSeasonReleases(None)) + .handle_sonarr_event(SonarrEvent::GetSeasonReleases((1, 1))) .await .is_ok()); @@ -4969,206 +3876,6 @@ mod test { ); } - #[tokio::test] - async fn test_handle_get_season_releases_event_uses_provided_series_id_and_season_number() { - let release_json = json!([ - { - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }}, - "fullSeason": true - }, - { - "guid": "4567", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }}, - } - ]); - let expected_filtered_sonarr_release = SonarrRelease { - full_season: true, - ..release() - }; - let expected_raw_sonarr_releases = vec![ - SonarrRelease { - full_season: true, - ..release() - }, - SonarrRelease { - guid: "4567".to_owned(), - ..release() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(release_json), - None, - SonarrEvent::GetSeasonReleases(None), - None, - Some("seriesId=2&seasonNumber=2"), - ) - .await; - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![season()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetSeasonReleases(Some((2, 2)))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .season_releases - .items, - vec![expected_filtered_sonarr_release] - ); - assert_eq!(releases_vec, expected_raw_sonarr_releases); - } - } - - #[tokio::test] - async fn test_handle_get_season_releases_event_filtered_series_and_filtered_seasons() { - let release_json = json!([ - { - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }}, - "fullSeason": true - }, - { - "guid": "4567", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }}, - } - ]); - let expected_filtered_sonarr_release = SonarrRelease { - full_season: true, - ..release() - }; - let expected_raw_sonarr_releases = vec![ - SonarrRelease { - full_season: true, - ..release() - }, - SonarrRelease { - guid: "4567".to_owned(), - ..release() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(release_json), - None, - SonarrEvent::GetSeasonReleases(None), - None, - Some("seriesId=1&seasonNumber=1"), - ) - .await; - let mut filtered_series = StatefulTable::default(); - filtered_series.set_items(vec![Series::default()]); - filtered_series.set_filtered_items(vec![Series { - id: 1, - ..Series::default() - }]); - app_arc.lock().await.data.sonarr_data.series = filtered_series; - let mut filtered_seasons = StatefulTable::default(); - filtered_seasons.set_items(vec![Season::default()]); - filtered_seasons.set_filtered_items(vec![Season { - season_number: 1, - ..Season::default() - }]); - app_arc.lock().await.data.sonarr_data.seasons = filtered_seasons; - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Releases(releases_vec) = network - .handle_sonarr_event(SonarrEvent::GetSeasonReleases(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .season_details_modal - .as_ref() - .unwrap() - .season_releases - .items, - vec![expected_filtered_sonarr_release] - ); - assert_eq!(releases_vec, expected_raw_sonarr_releases); - } - } - #[rstest] #[tokio::test] async fn test_handle_list_series_event(#[values(true, false)] use_custom_sorting: bool) { @@ -5258,7 +3965,7 @@ mod test { None, Some(serde_json::from_str(SERIES_JSON).unwrap()), None, - SonarrEvent::GetSeriesDetails(None), + SonarrEvent::GetSeriesDetails(1), Some("/1"), None, ) @@ -5273,44 +3980,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Series(series) = network - .handle_sonarr_event(SonarrEvent::GetSeriesDetails(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(series, expected_series); - } - } - - #[tokio::test] - async fn test_handle_get_series_details_event_uses_provided_series_id() { - let expected_series: Series = Series { - id: 2, - ..serde_json::from_str(SERIES_JSON).unwrap() - }; - let mut response: Value = serde_json::from_str(SERIES_JSON).unwrap(); - *response.get_mut("id").unwrap() = json!(2); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(response), - None, - SonarrEvent::GetSeriesDetails(Some(2)), - Some("/2"), - None, - ) - .await; - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Series(series) = network - .handle_sonarr_event(SonarrEvent::GetSeriesDetails(Some(2))) + .handle_sonarr_event(SonarrEvent::GetSeriesDetails(1)) .await .unwrap() { @@ -5370,7 +4040,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetSeriesHistory(None), + SonarrEvent::GetSeriesHistory(1), None, Some("seriesId=1"), ) @@ -5405,113 +4075,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryItems(history_items) = network - .handle_sonarr_event(SonarrEvent::GetSeriesHistory(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .series_history - .is_some()); - assert_eq!( - app_arc - .lock() - .await - .data - .sonarr_data - .series_history - .as_ref() - .unwrap() - .items, - expected_history_items - ); - assert!( - app_arc - .lock() - .await - .data - .sonarr_data - .series_history - .as_ref() - .unwrap() - .sort_asc - ); - assert_eq!(history_items, response); - } - } - - #[tokio::test] - async fn test_handle_get_sonarr_series_history_event_uses_provided_series_id() { - let history_json = json!([{ - "id": 123, - "sourceTitle": "z episode", - "episodeId": 1007, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }, - { - "id": 456, - "sourceTitle": "A Episode", - "episodeId": 2001, - "quality": { "quality": { "name": "Bluray-1080p" } }, - "languages": [{ "id": 1, "name": "English" }], - "date": "2024-02-10T07:28:45Z", - "eventType": "grabbed", - "data": { - "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", - "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" - } - }]); - let response: Vec = serde_json::from_value(history_json.clone()).unwrap(); - let expected_history_items = vec![ - SonarrHistoryItem { - id: 123, - episode_id: 1007, - source_title: "z episode".into(), - ..history_item() - }, - SonarrHistoryItem { - id: 456, - episode_id: 2001, - source_title: "A Episode".into(), - ..history_item() - }, - ]; - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(history_json), - None, - SonarrEvent::GetSeriesHistory(Some(2)), - None, - Some("seriesId=2"), - ) - .await; - app_arc.lock().await.data.sonarr_data.series_history = Some(StatefulTable { - sort_asc: true, - ..StatefulTable::default() - }); - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::SonarrHistoryItems(history_items) = network - .handle_sonarr_event(SonarrEvent::GetSeriesHistory(Some(2))) + .handle_sonarr_event(SonarrEvent::GetSeriesHistory(1)) .await .unwrap() { @@ -5598,7 +4162,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetSeriesHistory(None), + SonarrEvent::GetSeriesHistory(1), None, Some("seriesId=1"), ) @@ -5613,7 +4177,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryItems(history_items) = network - .handle_sonarr_event(SonarrEvent::GetSeriesHistory(None)) + .handle_sonarr_event(SonarrEvent::GetSeriesHistory(1)) .await .unwrap() { @@ -5686,7 +4250,7 @@ mod test { None, Some(history_json), None, - SonarrEvent::GetSeriesHistory(None), + SonarrEvent::GetSeriesHistory(1), None, Some("seriesId=1"), ) @@ -5721,7 +4285,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::SonarrHistoryItems(history_items) = network - .handle_sonarr_event(SonarrEvent::GetSeriesHistory(None)) + .handle_sonarr_event(SonarrEvent::GetSeriesHistory(1)) .await .unwrap() { @@ -6118,7 +4682,7 @@ mod test { None, Some(add_series_search_result_json), None, - SonarrEvent::SearchNewSeries(None), + SonarrEvent::SearchNewSeries("test term".into()), None, Some("term=test%20term"), ) @@ -6127,7 +4691,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::AddSeriesSearchResults(add_series_search_results) = network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) + .handle_sonarr_event(SonarrEvent::SearchNewSeries("test term".into())) .await .unwrap() { @@ -6155,43 +4719,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_search_new_series_event_uses_provided_query() { - let add_series_search_result_json = json!([{ - "tvdbId": 1234, - "title": "Test", - "status": "continuing", - "ended": false, - "overview": "New series blah blah blah", - "genres": ["cool", "family", "fun"], - "year": 2023, - "network": "Prime Video", - "runtime": 60, - "ratings": { "votes": 406744, "value": 8.4 }, - "statistics": { "seasonCount": 3 } - }]); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(add_series_search_result_json), - None, - SonarrEvent::SearchNewSeries(None), - None, - Some("term=test%20term"), - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::AddSeriesSearchResults(add_series_search_results) = network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(Some("test term".into()))) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(add_series_search_results, vec![add_series_search_result()]); - } - } - #[tokio::test] async fn test_handle_search_new_series_event_no_results() { let (async_server, app_arc, _server) = mock_servarr_api( @@ -6199,7 +4726,7 @@ mod test { None, Some(json!([])), None, - SonarrEvent::SearchNewSeries(None), + SonarrEvent::SearchNewSeries("test term".into()), None, Some("term=test%20term"), ) @@ -6208,7 +4735,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) + .handle_sonarr_event(SonarrEvent::SearchNewSeries("test term".into())) .await .is_ok()); @@ -6226,61 +4753,6 @@ mod test { ); } - #[tokio::test] - async fn test_handle_search_new_series_event_no_panic_on_race_condition() { - let resource = format!( - "{}?term=test%20term", - SonarrEvent::SearchNewSeries(None).resource() - ); - let mut server = Server::new_async().await; - let mut async_server = server - .mock( - &RequestMethod::Get.to_string().to_uppercase(), - format!("/api/v3{resource}").as_str(), - ) - .match_header("X-Api-Key", "test1234"); - async_server = async_server.expect_at_most(0).create_async().await; - - let host = Some(server.host_with_port().split(':').collect::>()[0].to_owned()); - let port = Some( - server.host_with_port().split(':').collect::>()[1] - .parse() - .unwrap(), - ); - let mut app = App::default(); - let sonarr_config = ServarrConfig { - host, - port, - api_token: "test1234".to_owned(), - ..ServarrConfig::default() - }; - app.config.sonarr = Some(sonarr_config); - let app_arc = Arc::new(Mutex::new(app)); - app_arc - .lock() - .await - .push_navigation_stack(ActiveSonarrBlock::Series.into()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::SearchNewSeries(None)) - .await - .is_ok()); - - async_server.assert_async().await; - assert!(app_arc - .lock() - .await - .data - .sonarr_data - .add_searched_series - .is_none()); - assert_eq!( - app_arc.lock().await.get_current_route(), - ActiveSonarrBlock::Series.into() - ); - } - #[tokio::test] async fn test_handle_start_sonarr_task_event() { let response = json!({ "test": "test"}); @@ -6291,7 +4763,7 @@ mod test { })), Some(response.clone()), None, - SonarrEvent::StartTask(None), + SonarrEvent::StartTask(SonarrTaskName::ApplicationUpdateCheck), None, None, ) @@ -6309,34 +4781,9 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Value(value) = network - .handle_sonarr_event(SonarrEvent::StartTask(None)) - .await - .unwrap() - { - async_server.assert_async().await; - assert_eq!(value, response); - } - } - - #[tokio::test] - async fn test_handle_start_sonarr_task_event_uses_provided_task_name() { - let response = json!({ "test": "test"}); - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "ApplicationUpdateCheck" - })), - Some(response.clone()), - None, - SonarrEvent::StartTask(None), - None, - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Value(value) = network - .handle_sonarr_event(SonarrEvent::StartTask(Some(SonarrTaskName::default()))) + .handle_sonarr_event(SonarrEvent::StartTask( + SonarrTaskName::ApplicationUpdateCheck, + )) .await .unwrap() { @@ -6389,7 +4836,7 @@ mod test { let async_test_server = server .mock( "POST", - format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(), + format!("/api/v3{}", SonarrEvent::TestIndexer(1).resource()).as_str(), ) .with_status(400) .match_header("X-Api-Key", "test1234") @@ -6407,7 +4854,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Value(value) = network - .handle_sonarr_event(SonarrEvent::TestIndexer(None)) + .handle_sonarr_event(SonarrEvent::TestIndexer(1)) .await .unwrap() { @@ -6458,7 +4905,7 @@ mod test { let async_test_server = server .mock( "POST", - format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(), + format!("/api/v3{}", SonarrEvent::TestIndexer(1).resource()).as_str(), ) .with_status(200) .match_header("X-Api-Key", "test1234") @@ -6476,7 +4923,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); if let SonarrSerdeable::Value(value) = network - .handle_sonarr_event(SonarrEvent::TestIndexer(None)) + .handle_sonarr_event(SonarrEvent::TestIndexer(1)) .await .unwrap() { @@ -6490,64 +4937,6 @@ mod test { } } - #[tokio::test] - async fn test_handle_test_sonarr_indexer_event_success_uses_provided_id() { - let indexer_details_json = json!({ - "enableRss": true, - "enableAutomaticSearch": true, - "enableInteractiveSearch": true, - "name": "Test Indexer", - "fields": [ - { - "name": "baseUrl", - "value": "https://test.com", - }, - { - "name": "apiKey", - "value": "", - }, - { - "name": "seedCriteria.seedRatio", - "value": "1.2", - }, - ], - "tags": [1], - "id": 1 - }); - 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_test_server = server - .mock( - "POST", - format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(), - ) - .with_status(200) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(indexer_details_json.clone())) - .with_body("{}") - .create_async() - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - if let SonarrSerdeable::Value(value) = network - .handle_sonarr_event(SonarrEvent::TestIndexer(Some(1))) - .await - .unwrap() - { - async_details_server.assert_async().await; - async_test_server.assert_async().await; - assert_eq!(value, json!({})); - } - } - #[tokio::test] async fn test_handle_test_all_sonarr_indexers_event() { let indexers = vec![ @@ -6647,39 +5036,6 @@ mod test { #[tokio::test] async fn test_handle_toggle_episode_monitoring_event() { - let expected_body = MonitorEpisodeBody { - episode_ids: vec![1], - monitored: false, - }; - - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Put, - Some(json!(expected_body)), - None, - None, - SonarrEvent::ToggleEpisodeMonitoring(None), - None, - None, - ) - .await; - { - let mut app = app_arc.lock().await; - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![episode()]); - app.data.sonarr_data.season_details_modal = Some(season_details_modal); - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::ToggleEpisodeMonitoring(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_toggle_episode_monitoring_event_uses_provided_episode_id() { let expected_body = MonitorEpisodeBody { episode_ids: vec![2], monitored: false, @@ -6691,7 +5047,7 @@ mod test { None, Some(json!(body)), None, - SonarrEvent::GetEpisodeDetails(None), + SonarrEvent::GetEpisodeDetails(2), Some("/2"), None, ) @@ -6701,7 +5057,7 @@ mod test { "PUT", format!( "/api/v3{}", - SonarrEvent::ToggleEpisodeMonitoring(None).resource() + SonarrEvent::ToggleEpisodeMonitoring(2).resource() ) .as_str(), ) @@ -6710,16 +5066,10 @@ mod test { .match_body(Matcher::Json(json!(expected_body))) .create_async() .await; - { - let mut app = app_arc.lock().await; - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![episode()]); - app.data.sonarr_data.season_details_modal = Some(season_details_modal); - } let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::ToggleEpisodeMonitoring(Some(2))) + .handle_sonarr_event(SonarrEvent::ToggleEpisodeMonitoring(2)) .await .is_ok()); @@ -6746,7 +5096,7 @@ mod test { None, Some(serde_json::from_str(SERIES_JSON).unwrap()), None, - SonarrEvent::GetSeriesDetails(None), + SonarrEvent::GetSeriesDetails(1), Some("/1"), None, ) @@ -6756,7 +5106,7 @@ mod test { "PUT", format!( "/api/v3{}/1", - SonarrEvent::ToggleSeasonMonitoring(None).resource() + SonarrEvent::ToggleSeasonMonitoring((1, 1)).resource() ) .as_str(), ) @@ -6773,72 +5123,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::ToggleSeasonMonitoring(None)) - .await - .is_ok()); - - async_details_server.assert_async().await; - async_toggle_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_toggle_season_monitoring_event_uses_provided_series_id_and_season_number() { - let mut detailed_response: Value = serde_json::from_str(SERIES_JSON).unwrap(); - *detailed_response - .get_mut("seasons") - .unwrap() - .as_array_mut() - .unwrap() - .iter_mut() - .find(|season| season["seasonNumber"] == 1) - .unwrap() - .get_mut("seasonNumber") - .unwrap() = json!(2); - let mut expected_body: Value = detailed_response.clone(); - *expected_body - .get_mut("seasons") - .unwrap() - .as_array_mut() - .unwrap() - .iter_mut() - .find(|season| season["seasonNumber"] == 2) - .unwrap() - .get_mut("monitored") - .unwrap() = json!(false); - - let (async_details_server, app_arc, mut server) = mock_servarr_api( - RequestMethod::Get, - None, - Some(detailed_response), - None, - SonarrEvent::GetSeriesDetails(None), - Some("/2"), - None, - ) - .await; - let async_toggle_server = server - .mock( - "PUT", - format!( - "/api/v3{}/2", - SonarrEvent::ToggleSeasonMonitoring(Some((2, 2))).resource() - ) - .as_str(), - ) - .with_status(202) - .match_header("X-Api-Key", "test1234") - .match_body(Matcher::Json(expected_body)) - .create_async() - .await; - { - let mut app = app_arc.lock().await; - app.data.sonarr_data.series.set_items(vec![series()]); - app.data.sonarr_data.seasons.set_items(vec![season()]); - } - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::ToggleSeasonMonitoring(Some((2, 2)))) + .handle_sonarr_event(SonarrEvent::ToggleSeasonMonitoring((1, 1))) .await .is_ok()); @@ -6856,35 +5141,7 @@ mod test { })), Some(json!({})), None, - SonarrEvent::TriggerAutomaticEpisodeSearch(None), - None, - None, - ) - .await; - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![episode()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticEpisodeSearch(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_trigger_automatic_episode_search_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "EpisodeSearch", - "episodeIds": [ 1 ] - })), - Some(json!({})), - None, - SonarrEvent::TriggerAutomaticEpisodeSearch(Some(1)), + SonarrEvent::TriggerAutomaticEpisodeSearch(1), None, None, ) @@ -6892,37 +5149,13 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticEpisodeSearch(Some(1))) + .handle_sonarr_event(SonarrEvent::TriggerAutomaticEpisodeSearch(1)) .await .is_ok()); async_server.assert_async().await; } - #[tokio::test] - #[should_panic(expected = "Season details have not been loaded")] - async fn test_handle_trigger_automatic_episode_search_event_empty_season_details_modal_panics() { - let (_async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "EpisodeSearch", - "episodeIds": [ 1 ] - })), - Some(json!({})), - None, - SonarrEvent::TriggerAutomaticEpisodeSearch(None), - None, - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticEpisodeSearch(None)) - .await - .unwrap(); - } - #[tokio::test] async fn test_handle_trigger_automatic_season_search_event() { let (async_server, app_arc, _server) = mock_servarr_api( @@ -6934,117 +5167,15 @@ mod test { })), Some(json!({})), None, - SonarrEvent::TriggerAutomaticSeasonSearch(None), + SonarrEvent::TriggerAutomaticSeasonSearch((1, 1)), None, None, ) .await; - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![season()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_trigger_automatic_season_search_event_uses_provided_series_id_and_season_number( - ) { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "SeasonSearch", - "seriesId": 2, - "seasonNumber": 2 - })), - Some(json!({})), - None, - SonarrEvent::TriggerAutomaticSeasonSearch(None), - None, - None, - ) - .await; - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![season()]); - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch(Some((2, 2)))) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_trigger_automatic_season_search_event_filtered_series_and_filtered_seasons() - { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "SeasonSearch", - "seriesId": 1, - "seasonNumber": 1 - })), - Some(json!({})), - None, - SonarrEvent::TriggerAutomaticSeasonSearch(None), - None, - None, - ) - .await; - let mut filtered_series = StatefulTable::default(); - filtered_series.set_items(vec![Series::default()]); - filtered_series.set_filtered_items(vec![Series { - id: 1, - ..Series::default() - }]); - app_arc.lock().await.data.sonarr_data.series = filtered_series; - let mut filtered_seasons = StatefulTable::default(); - filtered_seasons.set_items(vec![Season::default()]); - filtered_seasons.set_filtered_items(vec![Season { - season_number: 1, - ..Season::default() - }]); - app_arc.lock().await.data.sonarr_data.seasons = filtered_seasons; - app_arc.lock().await.data.sonarr_data.season_details_modal = - Some(SeasonDetailsModal::default()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch(None)) + .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeasonSearch((1, 1))) .await .is_ok()); @@ -7061,39 +5192,7 @@ mod test { })), Some(json!({})), None, - SonarrEvent::TriggerAutomaticSeriesSearch(None), - None, - None, - ) - .await; - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![series()]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeriesSearch(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_trigger_automatic_series_search_event_uses_provided_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "SeriesSearch", - "seriesId": 1 - })), - Some(json!({})), - None, - SonarrEvent::TriggerAutomaticSeriesSearch(None), + SonarrEvent::TriggerAutomaticSeriesSearch(1), None, None, ) @@ -7101,7 +5200,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeriesSearch(Some(1))) + .handle_sonarr_event(SonarrEvent::TriggerAutomaticSeriesSearch(1)) .await .is_ok()); @@ -7142,7 +5241,7 @@ mod test { })), Some(json!({})), None, - SonarrEvent::UpdateAndScanSeries(None), + SonarrEvent::UpdateAndScanSeries(1), None, None, ) @@ -7157,32 +5256,7 @@ mod test { let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); assert!(network - .handle_sonarr_event(SonarrEvent::UpdateAndScanSeries(None)) - .await - .is_ok()); - - async_server.assert_async().await; - } - - #[tokio::test] - async fn test_handle_update_and_scan_series_event_uses_provied_series_id() { - let (async_server, app_arc, _server) = mock_servarr_api( - RequestMethod::Post, - Some(json!({ - "name": "RefreshSeries", - "seriesId": 1 - })), - Some(json!({})), - None, - SonarrEvent::UpdateAndScanSeries(Some(1)), - None, - None, - ) - .await; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - assert!(network - .handle_sonarr_event(SonarrEvent::UpdateAndScanSeries(Some(1))) + .handle_sonarr_event(SonarrEvent::UpdateAndScanSeries(1)) .await .is_ok()); @@ -7271,197 +5345,6 @@ mod test { ); } - #[tokio::test] - async fn test_extract_series_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![Series { - id: 1, - ..Series::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, series_id_param) = network.extract_series_id(None).await; - - assert_eq!(id, 1); - assert_str_eq!(series_id_param, "seriesId=1"); - } - - #[tokio::test] - async fn test_extract_series_id_uses_provided_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .sonarr_data - .series - .set_items(vec![Series { - id: 1, - ..Series::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, series_id_param) = network.extract_series_id(Some(2)).await; - - assert_eq!(id, 2); - assert_str_eq!(series_id_param, "seriesId=2"); - } - - #[tokio::test] - async fn test_extract_series_id_filtered_series() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut filtered_series = StatefulTable::default(); - filtered_series.set_filtered_items(vec![Series { - id: 1, - ..Series::default() - }]); - app_arc.lock().await.data.sonarr_data.series = filtered_series; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, series_id_param) = network.extract_series_id(None).await; - - assert_eq!(id, 1); - assert_str_eq!(series_id_param, "seriesId=1"); - } - - #[tokio::test] - async fn test_extract_season_number() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![Season { - season_number: 1, - ..Season::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, season_number_param) = network.extract_season_number(None).await.unwrap(); - - assert_eq!(id, 1); - assert_str_eq!(season_number_param, "seasonNumber=1"); - } - - #[tokio::test] - async fn test_extract_season_number_uses_provided_season_number() { - let app_arc = Arc::new(Mutex::new(App::default())); - app_arc - .lock() - .await - .data - .sonarr_data - .seasons - .set_items(vec![Season { - season_number: 1, - ..Season::default() - }]); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - let (id, season_number_param) = network.extract_season_number(Some(2)).await.unwrap(); - - assert_eq!(id, 2); - assert_str_eq!(season_number_param, "seasonNumber=2"); - } - - #[tokio::test] - async fn test_extract_season_number_filtered_seasons() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut filtered_seasons = StatefulTable::default(); - filtered_seasons.set_items(vec![Season::default()]); - filtered_seasons.set_filtered_items(vec![Season { - season_number: 1, - ..Season::default() - }]); - app_arc.lock().await.data.sonarr_data.seasons = filtered_seasons; - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let (id, season_number_param) = network.extract_season_number(None).await.unwrap(); - - assert_eq!(id, 1); - assert_str_eq!(season_number_param, "seasonNumber=1"); - } - - #[tokio::test] - async fn test_extract_season_number_empty_seasons_table() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - let season_number = network.extract_season_number(None).await; - - assert!(season_number.is_err()); - } - - #[tokio::test] - async fn test_extract_episode_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![Episode { - id: 1, - ..Episode::default() - }]); - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - app_arc - .lock() - .await - .push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let id = network.extract_episode_id(None).await; - - assert_eq!(id, 1); - } - - #[tokio::test] - async fn test_extract_episode_id_uses_provided_id() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut season_details_modal = SeasonDetailsModal::default(); - season_details_modal.episodes.set_items(vec![Episode { - id: 1, - ..Episode::default() - }]); - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - app_arc - .lock() - .await - .push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let id = network.extract_episode_id(Some(2)).await; - - assert_eq!(id, 2); - } - - #[tokio::test] - async fn test_extract_episode_id_filtered_series() { - let app_arc = Arc::new(Mutex::new(App::default())); - let mut filtered_episodes = StatefulTable::default(); - filtered_episodes.set_filtered_items(vec![Episode { - id: 1, - ..Episode::default() - }]); - let season_details_modal = SeasonDetailsModal { - episodes: filtered_episodes, - ..SeasonDetailsModal::default() - }; - app_arc.lock().await.data.sonarr_data.season_details_modal = Some(season_details_modal); - app_arc - .lock() - .await - .push_navigation_stack(ActiveSonarrBlock::EpisodeDetails.into()); - let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); - - let id = network.extract_episode_id(None).await; - - assert_eq!(id, 1); - } - #[test] fn test_get_episode_status_downloaded() { assert_str_eq!(get_episode_status(true, &[], 0), "Downloaded");