#[cfg(test)] mod tests { mod sonarr_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::{ servarr_data::sonarr::{ modals::{EpisodeDetailsModal, SeasonDetailsModal}, sonarr_data::ActiveSonarrBlock, }, sonarr_models::{Season, Series, SonarrRelease}, }, network::{sonarr_network::SonarrEvent, NetworkEvent}, }; #[tokio::test] async fn test_dispatch_by_blocklist_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::Blocklist) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::ListSeries.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetBlocklist.into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetSeriesHistory(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_series_details_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.data.sonarr_data.series.set_items(vec![Series { seasons: Some(vec![Season::default()]), ..Series::default() }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesDetails) .await; assert!(!app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::ListSeries.into() ); assert!(!app.data.sonarr_data.seasons.items.is_empty()); assert_eq!(app.tick_count, 0); assert!(!app.data.sonarr_data.prompt_confirm); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodes(1).into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodeFiles(1).into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), 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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetSeasonReleases((1, 1)).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_manual_season_search_block_is_loading() { let mut app = App { is_loading: true, ..App::default() }; app .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch) .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_season_releases_non_empty() { let mut app = App::default(); let mut season_details_modal = SeasonDetailsModal::default(); season_details_modal .season_releases .set_items(vec![SonarrRelease::default()]); app.data.sonarr_data.season_details_modal = Some(season_details_modal); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch) .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_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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodeDetails(0).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodeDetails(0).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodeHistory(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_manual_episode_search_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); 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 .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetEpisodeReleases(1).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_manual_episode_search_block_is_loading() { let mut app = App { is_loading: true, ..App::default() }; app .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch) .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_episode_search_block_episode_releases_non_empty() { let mut app = App::default(); let mut episode_details_modal = EpisodeDetailsModal::default(); episode_details_modal .episode_releases .set_items(vec![SonarrRelease::default()]); let season_details_modal = SeasonDetailsModal { episode_details_modal: Some(episode_details_modal), ..SeasonDetailsModal::default() }; app.data.sonarr_data.season_details_modal = Some(season_details_modal); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualEpisodeSearch) .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_history_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::History) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetHistory(500).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_downloads_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::Downloads) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(!app.data.sonarr_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_sonarr_block(&ActiveSonarrBlock::RootFolders) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetRootFolders.into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_dispatch_by_series_block() { let (mut app, mut sync_network_rx) = construct_app_unit(); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::Series) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetQualityProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetLanguageProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTags.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::ListSeries.into() ); assert!(!app.data.sonarr_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_sonarr_block(&ActiveSonarrBlock::Indexers) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTags.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetIndexers.into() ); assert!(!app.data.sonarr_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_sonarr_block(&ActiveSonarrBlock::AllIndexerSettingsPrompt) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetAllIndexerSettings.into() ); assert!(!app.data.sonarr_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.sonarr_data.indexers.set_items(vec![Indexer { id: 1, ..Indexer::default() }]); app .dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::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_sonarr_block(&ActiveSonarrBlock::TestAllIndexers) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::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_sonarr_block(&ActiveSonarrBlock::System) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTasks.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetQueuedEvents.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetLogs(500).into() ); assert!(!app.data.sonarr_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_sonarr_block(&ActiveSonarrBlock::SystemUpdates) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetUpdates.into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[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) .await; assert!(app.is_loading); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::SearchNewSeries("test search".into()).into() ); assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!(app.tick_count, 0); } #[tokio::test] async fn test_check_for_sonarr_prompt_action_no_prompt_confirm() { let mut app = App::default(); app.data.sonarr_data.prompt_confirm = false; app.check_for_sonarr_prompt_action().await; assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.should_refresh); } #[tokio::test] async fn test_check_for_sonarr_prompt_action() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::GetStatus); app.check_for_sonarr_prompt_action().await; assert!(!app.data.sonarr_data.prompt_confirm); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetStatus.into() ); assert!(app.should_refresh); assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); } #[tokio::test] async fn test_sonarr_refresh_metadata() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.is_routing = true; app.refresh_sonarr_metadata().await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetQualityProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetLanguageProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTags.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetRootFolders.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDiskSpace.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetStatus.into() ); assert!(app.is_loading); } #[tokio::test] async fn test_sonarr_on_tick_first_render() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.is_first_render = true; app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetQualityProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetLanguageProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTags.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetRootFolders.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDiskSpace.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetStatus.into() ); assert!(app.is_loading); assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.is_first_render); } #[tokio::test] async fn test_sonarr_on_tick_routing() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.is_routing = true; app.should_refresh = true; app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(!app.data.sonarr_data.prompt_confirm); } #[tokio::test] async fn test_sonarr_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.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert!(app.cancellation_token.is_cancelled()); } #[tokio::test] async fn test_sonarr_on_tick_should_refresh() { let (mut app, mut sync_network_rx) = construct_app_unit(); app.should_refresh = true; app.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(app.should_refresh); assert!(!app.data.sonarr_data.prompt_confirm); } #[tokio::test] async fn test_sonarr_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.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(app.is_loading); assert!(app.should_refresh); assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.cancellation_token.is_cancelled()); } #[tokio::test] async fn test_sonarr_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.sonarr_on_tick(ActiveSonarrBlock::Downloads).await; assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetQualityProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetLanguageProfiles.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetTags.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetRootFolders.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), SonarrEvent::GetDownloads.into() ); assert!(app.is_loading); } #[tokio::test] async fn test_populate_seasons_table_unfiltered() { let mut app = App::default(); app.data.sonarr_data.series.set_items(vec![Series { seasons: Some(vec![Season::default()]), ..Series::default() }]); app.populate_seasons_table().await; assert!(!app.data.sonarr_data.seasons.items.is_empty()); assert_str_eq!( app.data.sonarr_data.seasons.items[0] .title .as_ref() .unwrap(), "Season 0" ); } #[tokio::test] async fn test_populate_seasons_table_filtered() { let mut app = App::default(); app.data.sonarr_data.series.set_filtered_items(vec![Series { seasons: Some(vec![Season::default()]), ..Series::default() }]); app.populate_seasons_table().await; assert!(!app.data.sonarr_data.seasons.items.is_empty()); assert_str_eq!( app.data.sonarr_data.seasons.items[0] .title .as_ref() .unwrap(), "Season 0" ); } #[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 { network_tx: Some(sync_network_tx), tick_count: 1, is_first_render: false, ..App::default() }; app.data.sonarr_data.prompt_confirm = true; (app, sync_network_rx) } } }