Merge pull request #27 from Dark-Alex-17/race-condition-refactor

Race condition refactor
This commit is contained in:
Alex Clarke
2024-12-18 17:38:13 -07:00
committed by GitHub
97 changed files with 5816 additions and 6819 deletions
+3
View File
@@ -4,6 +4,9 @@ set -e
echo "Running pre-push hook:" echo "Running pre-push hook:"
echo "Executing: cargo fmt"
cargo fmt
echo "Executing: make lint" echo "Executing: make lint"
make lint make lint
+3
View File
@@ -4,6 +4,9 @@ set -e
echo "Running pre-push hook:" echo "Running pre-push hook:"
echo "Executing: cargo fmt --check"
cargo fmt --check
echo "Executing: make lint" echo "Executing: make lint"
make lint make lint
+42 -7
View File
@@ -77,7 +77,9 @@ impl<'a> App<'a> {
} }
ActiveRadarrBlock::TestIndexer => { ActiveRadarrBlock::TestIndexer => {
self self
.dispatch_network_event(RadarrEvent::TestIndexer(None).into()) .dispatch_network_event(
RadarrEvent::TestIndexer(self.extract_radarr_indexer_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::TestAllIndexers => { ActiveRadarrBlock::TestAllIndexers => {
@@ -93,7 +95,7 @@ impl<'a> App<'a> {
.dispatch_network_event(RadarrEvent::GetQueuedEvents.into()) .dispatch_network_event(RadarrEvent::GetQueuedEvents.into())
.await; .await;
self self
.dispatch_network_event(RadarrEvent::GetLogs(None).into()) .dispatch_network_event(RadarrEvent::GetLogs(500).into())
.await; .await;
} }
ActiveRadarrBlock::SystemUpdates => { ActiveRadarrBlock::SystemUpdates => {
@@ -103,17 +105,23 @@ impl<'a> App<'a> {
} }
ActiveRadarrBlock::AddMovieSearchResults => { ActiveRadarrBlock::AddMovieSearchResults => {
self self
.dispatch_network_event(RadarrEvent::SearchNewMovie(None).into()) .dispatch_network_event(
RadarrEvent::SearchNewMovie(self.extract_movie_search_query().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => { ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
self self
.dispatch_network_event(RadarrEvent::GetMovieDetails(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieDetails(self.extract_movie_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::MovieHistory => { ActiveRadarrBlock::MovieHistory => {
self self
.dispatch_network_event(RadarrEvent::GetMovieHistory(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieHistory(self.extract_movie_id().await).into(),
)
.await; .await;
} }
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => { ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
@@ -123,7 +131,9 @@ impl<'a> App<'a> {
|| movie_details_modal.movie_crew.items.is_empty() => || movie_details_modal.movie_crew.items.is_empty() =>
{ {
self self
.dispatch_network_event(RadarrEvent::GetMovieCredits(None).into()) .dispatch_network_event(
RadarrEvent::GetMovieCredits(self.extract_movie_id().await).into(),
)
.await; .await;
} }
_ => (), _ => (),
@@ -132,7 +142,7 @@ impl<'a> App<'a> {
ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() { 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() => { Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => {
self self
.dispatch_network_event(RadarrEvent::GetReleases(None).into()) .dispatch_network_event(RadarrEvent::GetReleases(self.extract_movie_id().await).into())
.await; .await;
} }
_ => (), _ => (),
@@ -219,4 +229,29 @@ impl<'a> App<'a> {
.collection_movies .collection_movies
.set_items(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
}
} }
File diff suppressed because it is too large Load Diff
+78 -14
View File
@@ -38,30 +38,42 @@ impl<'a> App<'a> {
} }
ActiveSonarrBlock::SeriesHistory => { ActiveSonarrBlock::SeriesHistory => {
self self
.dispatch_network_event(SonarrEvent::GetSeriesHistory(None).into()) .dispatch_network_event(
SonarrEvent::GetSeriesHistory(self.extract_series_id().await).into(),
)
.await; .await;
} }
ActiveSonarrBlock::SeasonDetails => { ActiveSonarrBlock::SeasonDetails => {
self self
.dispatch_network_event(SonarrEvent::GetEpisodes(None).into()) .dispatch_network_event(SonarrEvent::GetEpisodes(self.extract_series_id().await).into())
.await; .await;
self self
.dispatch_network_event(SonarrEvent::GetEpisodeFiles(None).into()) .dispatch_network_event(
SonarrEvent::GetEpisodeFiles(self.extract_series_id().await).into(),
)
.await; .await;
self self
.dispatch_network_event(SonarrEvent::GetDownloads.into()) .dispatch_network_event(SonarrEvent::GetDownloads.into())
.await; .await;
} }
ActiveSonarrBlock::SeasonHistory => { ActiveSonarrBlock::SeasonHistory => {
self if !self.data.sonarr_data.seasons.is_empty() {
.dispatch_network_event(SonarrEvent::GetSeasonHistory(None).into()) self
.await; .dispatch_network_event(
SonarrEvent::GetSeasonHistory(self.extract_series_id_season_number_tuple().await)
.into(),
)
.await;
}
} }
ActiveSonarrBlock::ManualSeasonSearch => { ActiveSonarrBlock::ManualSeasonSearch => {
match self.data.sonarr_data.season_details_modal.as_ref() { match self.data.sonarr_data.season_details_modal.as_ref() {
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => { Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
self self
.dispatch_network_event(SonarrEvent::GetSeasonReleases(None).into()) .dispatch_network_event(
SonarrEvent::GetSeasonReleases(self.extract_series_id_season_number_tuple().await)
.into(),
)
.await; .await;
} }
_ => (), _ => (),
@@ -69,12 +81,16 @@ impl<'a> App<'a> {
} }
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => { ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
self self
.dispatch_network_event(SonarrEvent::GetEpisodeDetails(None).into()) .dispatch_network_event(
SonarrEvent::GetEpisodeDetails(self.extract_episode_id().await).into(),
)
.await; .await;
} }
ActiveSonarrBlock::EpisodeHistory => { ActiveSonarrBlock::EpisodeHistory => {
self self
.dispatch_network_event(SonarrEvent::GetEpisodeHistory(None).into()) .dispatch_network_event(
SonarrEvent::GetEpisodeHistory(self.extract_episode_id().await).into(),
)
.await; .await;
} }
ActiveSonarrBlock::ManualEpisodeSearch => { 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 let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() {
if episode_details_modal.episode_releases.is_empty() { if episode_details_modal.episode_releases.is_empty() {
self self
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into()) .dispatch_network_event(
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
)
.await; .await;
} }
} }
@@ -103,7 +121,7 @@ impl<'a> App<'a> {
} }
ActiveSonarrBlock::History => { ActiveSonarrBlock::History => {
self self
.dispatch_network_event(SonarrEvent::GetHistory(None).into()) .dispatch_network_event(SonarrEvent::GetHistory(500).into())
.await; .await;
} }
ActiveSonarrBlock::RootFolders => { ActiveSonarrBlock::RootFolders => {
@@ -126,7 +144,9 @@ impl<'a> App<'a> {
} }
ActiveSonarrBlock::TestIndexer => { ActiveSonarrBlock::TestIndexer => {
self self
.dispatch_network_event(SonarrEvent::TestIndexer(None).into()) .dispatch_network_event(
SonarrEvent::TestIndexer(self.extract_sonarr_indexer_id().await).into(),
)
.await; .await;
} }
ActiveSonarrBlock::TestAllIndexers => { ActiveSonarrBlock::TestAllIndexers => {
@@ -142,12 +162,14 @@ impl<'a> App<'a> {
.dispatch_network_event(SonarrEvent::GetQueuedEvents.into()) .dispatch_network_event(SonarrEvent::GetQueuedEvents.into())
.await; .await;
self self
.dispatch_network_event(SonarrEvent::GetLogs(None).into()) .dispatch_network_event(SonarrEvent::GetLogs(500).into())
.await; .await;
} }
ActiveSonarrBlock::AddSeriesSearchResults => { ActiveSonarrBlock::AddSeriesSearchResults => {
self self
.dispatch_network_event(SonarrEvent::SearchNewSeries(None).into()) .dispatch_network_event(
SonarrEvent::SearchNewSeries(self.extract_add_new_series_search_query().await).into(),
)
.await; .await;
} }
ActiveSonarrBlock::SystemUpdates => { ActiveSonarrBlock::SystemUpdates => {
@@ -242,4 +264,46 @@ impl<'a> App<'a> {
.collect(); .collect();
self.data.sonarr_data.seasons.set_items(seasons); 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
}
} }
+152 -14
View File
@@ -4,6 +4,9 @@ mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use tokio::sync::mpsc; 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::{ use crate::{
app::App, app::App,
models::{ models::{
@@ -40,6 +43,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_series_history_block() { async fn test_dispatch_by_series_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory) .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory)
@@ -48,7 +55,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeriesHistory(None).into() SonarrEvent::GetSeriesHistory(1).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -80,6 +87,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_season_details_block() { async fn test_dispatch_by_season_details_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.series.set_items(vec![Series {
id: 1,
..Series::default()
}]);
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails) .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails)
@@ -88,11 +99,11 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodes(None).into() SonarrEvent::GetEpisodes(1).into()
); );
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeFiles(None).into() SonarrEvent::GetEpisodeFiles(1).into()
); );
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
@@ -105,6 +116,14 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_season_history_block() { async fn test_dispatch_by_season_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); 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 app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory) .dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
@@ -113,16 +132,41 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeasonHistory(None).into() SonarrEvent::GetSeasonHistory((1, 1)).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); 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] #[tokio::test]
async fn test_dispatch_by_manual_season_search_block() { async fn test_dispatch_by_manual_season_search_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default()); 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 app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch) .dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
@@ -131,7 +175,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetSeasonReleases(None).into() SonarrEvent::GetSeasonReleases((1, 1)).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -174,6 +218,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_episode_details_block() { async fn test_dispatch_by_episode_details_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data = create_test_sonarr_data();
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails) .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails)
@@ -182,7 +227,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeDetails(None).into() SonarrEvent::GetEpisodeDetails(0).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -191,6 +236,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_episode_file_block() { async fn test_dispatch_by_episode_file_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data = create_test_sonarr_data();
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile) .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile)
@@ -199,7 +245,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeDetails(None).into() SonarrEvent::GetEpisodeDetails(0).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -208,6 +254,12 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_episode_history_block() { async fn test_dispatch_by_episode_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); 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 app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory) .dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory)
@@ -216,7 +268,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeHistory(None).into() SonarrEvent::GetEpisodeHistory(1).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -225,10 +277,14 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_manual_episode_search_block() { async fn test_dispatch_by_manual_episode_search_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); 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()), episode_details_modal: Some(EpisodeDetailsModal::default()),
..SeasonDetailsModal::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.data.sonarr_data.season_details_modal = Some(season_details_modal);
app app
@@ -238,7 +294,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetEpisodeReleases(None).into() SonarrEvent::GetEpisodeReleases(1).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -293,7 +349,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetHistory(None).into() SonarrEvent::GetHistory(500).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -403,6 +459,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_test_indexer_block() { async fn test_dispatch_by_test_indexer_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.indexers.set_items(vec![Indexer {
id: 1,
..Indexer::default()
}]);
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer) .dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer)
@@ -411,7 +471,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::TestIndexer(None).into() SonarrEvent::TestIndexer(1).into()
); );
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
} }
@@ -451,7 +511,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::GetLogs(None).into() SonarrEvent::GetLogs(500).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
@@ -477,6 +537,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_add_series_search_results_block() { async fn test_dispatch_by_add_series_search_results_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
app.data.sonarr_data.add_series_search = Some("test search".into());
app app
.dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults) .dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults)
@@ -485,7 +546,7 @@ mod tests {
assert!(app.is_loading); assert!(app.is_loading);
assert_eq!( assert_eq!(
sync_network_rx.recv().await.unwrap(), sync_network_rx.recv().await.unwrap(),
SonarrEvent::SearchNewSeries(None).into() SonarrEvent::SearchNewSeries("test search".into()).into()
); );
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0); 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<NetworkEvent>) { fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver<NetworkEvent>) {
let (sync_network_tx, sync_network_rx) = mpsc::channel::<NetworkEvent>(500); let (sync_network_tx, sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App { let mut app = App {
+8 -4
View File
@@ -4,6 +4,8 @@ use anyhow::Result;
use clap::{arg, command, ArgAction, Subcommand}; use clap::{arg, command, ArgAction, Subcommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::RadarrCommand;
use crate::models::servarr_models::AddRootFolderBody;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
@@ -11,8 +13,6 @@ use crate::{
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{radarr_network::RadarrEvent, NetworkTrait},
}; };
use super::RadarrCommand;
#[cfg(test)] #[cfg(test)]
#[path = "add_command_handler_tests.rs"] #[path = "add_command_handler_tests.rs"]
mod add_command_handler_tests; mod add_command_handler_tests;
@@ -125,6 +125,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
minimum_availability: minimum_availability.to_string(), minimum_availability: minimum_availability.to_string(),
monitored: !disable_monitoring, monitored: !disable_monitoring,
tags, tags,
tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: monitor.to_string(), monitor: monitor.to_string(),
search_for_movie: !no_search_for_movie, search_for_movie: !no_search_for_movie,
@@ -132,14 +133,17 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::AddMovie(Some(body)).into()) .handle_network_event(RadarrEvent::AddMovie(body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrAddCommand::RootFolder { root_folder_path } => { RadarrAddCommand::RootFolder { root_folder_path } => {
let add_root_folder_body = AddRootFolderBody {
path: root_folder_path,
};
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::AddRootFolder(Some(root_folder_path)).into()) .handle_network_event(RadarrEvent::AddRootFolder(add_root_folder_body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+7 -2
View File
@@ -368,6 +368,7 @@ mod tests {
use super::*; use super::*;
use mockall::predicate::eq; use mockall::predicate::eq;
use crate::models::servarr_models::AddRootFolderBody;
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@@ -381,6 +382,7 @@ mod tests {
minimum_availability: "released".to_owned(), minimum_availability: "released".to_owned(),
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: "movieAndCollection".to_owned(), monitor: "movieAndCollection".to_owned(),
search_for_movie: false, search_for_movie: false,
@@ -390,7 +392,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::AddMovie(Some(expected_add_movie_body)).into(), RadarrEvent::AddMovie(expected_add_movie_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -420,11 +422,14 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_handle_add_root_folder_command() { async fn test_handle_add_root_folder_command() {
let expected_root_folder_path = "/nfs/test".to_owned(); 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(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), RadarrEvent::AddRootFolder(expected_add_root_folder_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+5 -5
View File
@@ -89,21 +89,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => { RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) .handle_network_event(RadarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::Download { download_id } => { RadarrDeleteCommand::Download { download_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteDownload(Some(download_id)).into()) .handle_network_event(RadarrEvent::DeleteDownload(download_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::Indexer { indexer_id } => { RadarrDeleteCommand::Indexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteIndexer(Some(indexer_id)).into()) .handle_network_event(RadarrEvent::DeleteIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -119,14 +119,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteMovie(Some(delete_movie_params)).into()) .handle_network_event(RadarrEvent::DeleteMovie(delete_movie_params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrDeleteCommand::RootFolder { root_folder_id } => { RadarrDeleteCommand::RootFolder { root_folder_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) .handle_network_event(RadarrEvent::DeleteRootFolder(root_folder_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -268,7 +268,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -299,7 +299,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteDownload(Some(expected_download_id)).into(), RadarrEvent::DeleteDownload(expected_download_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -325,7 +325,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), RadarrEvent::DeleteIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -355,7 +355,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteMovie(Some(expected_delete_movie_params)).into(), RadarrEvent::DeleteMovie(expected_delete_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -385,7 +385,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), RadarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -4
View File
@@ -390,7 +390,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditAllIndexerSettings(Some(params)).into()) .handle_network_event(RadarrEvent::EditAllIndexerSettings(params).into())
.await?; .await?;
"All indexer settings updated".to_owned() "All indexer settings updated".to_owned()
} else { } else {
@@ -420,7 +420,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditCollection(Some(edit_collection_params)).into()) .handle_network_event(RadarrEvent::EditCollection(edit_collection_params).into())
.await?; .await?;
"Collection updated".to_owned() "Collection updated".to_owned()
} }
@@ -455,13 +455,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
api_key, api_key,
seed_ratio, seed_ratio,
tags: tag, tags: tag,
tag_input_string: None,
priority, priority,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditIndexer(Some(edit_indexer_params)).into()) .handle_network_event(RadarrEvent::EditIndexer(edit_indexer_params).into())
.await?; .await?;
"Indexer updated".to_owned() "Indexer updated".to_owned()
} }
@@ -483,12 +484,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
quality_profile_id, quality_profile_id,
root_folder_path, root_folder_path,
tags: tag, tags: tag,
tag_input_string: None,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(RadarrEvent::EditMovie(Some(edit_movie_params)).into()) .handle_network_event(RadarrEvent::EditMovie(edit_movie_params).into())
.await?; .await?;
"Movie Updated".to_owned() "Movie Updated".to_owned()
} }
+18 -12
View File
@@ -857,7 +857,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -928,7 +928,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1000,7 +1000,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1047,7 +1047,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1089,7 +1089,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1131,7 +1131,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(), RadarrEvent::EditCollection(expected_edit_collection_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1171,6 +1171,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1178,7 +1179,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1224,6 +1225,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1231,7 +1233,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1277,6 +1279,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -1284,7 +1287,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1327,13 +1330,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1369,13 +1373,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -1411,13 +1416,14 @@ mod tests {
quality_profile_id: Some(1), quality_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(), RadarrEvent::EditMovie(expected_edit_movie_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -90,14 +90,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan
RadarrGetCommand::MovieDetails { movie_id } => { RadarrGetCommand::MovieDetails { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieDetails(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieDetails(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrGetCommand::MovieHistory { movie_id } => { RadarrGetCommand::MovieHistory { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieHistory(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieHistory(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -182,7 +182,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieDetails(Some(expected_movie_id)).into(), RadarrEvent::GetMovieDetails(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -208,7 +208,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieHistory(Some(expected_movie_id)).into(), RadarrEvent::GetMovieHistory(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -131,7 +131,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
} => { } => {
let logs = self let logs = self
.network .network
.handle_network_event(RadarrEvent::GetLogs(Some(events)).into()) .handle_network_event(RadarrEvent::GetLogs(events).into())
.await?; .await?;
if output_in_log_format { if output_in_log_format {
@@ -152,7 +152,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
RadarrListCommand::MovieCredits { movie_id } => { RadarrListCommand::MovieCredits { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetMovieCredits(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetMovieCredits(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -163,7 +163,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), RadarrEvent::GetMovieCredits(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -189,7 +189,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetLogs(Some(expected_events)).into(), RadarrEvent::GetLogs(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -6
View File
@@ -209,7 +209,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
}; };
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::DownloadRelease(Some(params)).into()) .handle_network_event(RadarrEvent::DownloadRelease(params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? 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..."); println!("Searching for releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::GetReleases(Some(movie_id)).into()) .handle_network_event(RadarrEvent::GetReleases(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::SearchNewMovie { query } => { RadarrCommand::SearchNewMovie { query } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::SearchNewMovie(Some(query)).into()) .handle_network_event(RadarrEvent::SearchNewMovie(query).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::StartTask { task_name } => { RadarrCommand::StartTask { task_name } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::StartTask(Some(task_name)).into()) .handle_network_event(RadarrEvent::StartTask(task_name).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::TestIndexer { indexer_id } => { RadarrCommand::TestIndexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::TestIndexer(Some(indexer_id)).into()) .handle_network_event(RadarrEvent::TestIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -253,7 +253,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
RadarrCommand::TriggerAutomaticSearch { movie_id } => { RadarrCommand::TriggerAutomaticSearch { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::TriggerAutomaticSearch(Some(movie_id)).into()) .handle_network_event(RadarrEvent::TriggerAutomaticSearch(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+12 -12
View File
@@ -293,9 +293,9 @@ mod tests {
))) )))
}); });
let app_arc = Arc::new(Mutex::new(App::default())); 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() .handle()
.await; .await;
@@ -313,7 +313,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DownloadRelease(Some(expected_release_download_body)).into(), RadarrEvent::DownloadRelease(expected_release_download_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -342,7 +342,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetReleases(Some(expected_movie_id)).into(), RadarrEvent::GetReleases(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -367,7 +367,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::SearchNewMovie(Some(expected_search_query)).into(), RadarrEvent::SearchNewMovie(expected_search_query).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -394,7 +394,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::StartTask(Some(expected_task_name)).into(), RadarrEvent::StartTask(expected_task_name).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -421,7 +421,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::TestIndexer(Some(expected_indexer_id)).into(), RadarrEvent::TestIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -468,7 +468,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::TriggerAutomaticSearch(Some(expected_movie_id)).into(), RadarrEvent::TriggerAutomaticSearch(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -524,7 +524,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -584,7 +584,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -654,7 +654,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(), RadarrEvent::GetMovieCredits(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -680,7 +680,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), RadarrEvent::UpdateAndScan(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+1 -1
View File
@@ -88,7 +88,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrRefreshCommand>
RadarrRefreshCommand::Movie { movie_id } => { RadarrRefreshCommand::Movie { movie_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(RadarrEvent::UpdateAndScan(Some(movie_id)).into()) .handle_network_event(RadarrEvent::UpdateAndScan(movie_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -112,7 +112,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(), RadarrEvent::UpdateAndScan(expected_movie_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+8 -4
View File
@@ -4,6 +4,8 @@ use anyhow::Result;
use clap::{ArgAction, Subcommand}; use clap::{ArgAction, Subcommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::SonarrCommand;
use crate::models::servarr_models::AddRootFolderBody;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
@@ -11,8 +13,6 @@ use crate::{
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{sonarr_network::SonarrEvent, NetworkTrait},
}; };
use super::SonarrCommand;
#[cfg(test)] #[cfg(test)]
#[path = "add_command_handler_tests.rs"] #[path = "add_command_handler_tests.rs"]
mod add_command_handler_tests; mod add_command_handler_tests;
@@ -140,6 +140,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
series_type: series_type.to_string(), series_type: series_type.to_string(),
season_folder: !disable_season_folders, season_folder: !disable_season_folders,
tags, tags,
tag_input_string: None,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: monitor.to_string(), monitor: monitor.to_string(),
search_for_cutoff_unmet_episodes: !no_search_for_series, 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 let resp = self
.network .network
.handle_network_event(SonarrEvent::AddSeries(Some(body)).into()) .handle_network_event(SonarrEvent::AddSeries(body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrAddCommand::RootFolder { root_folder_path } => { SonarrAddCommand::RootFolder { root_folder_path } => {
let add_root_folder_body = AddRootFolderBody {
path: root_folder_path,
};
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::AddRootFolder(Some(root_folder_path)).into()) .handle_network_event(SonarrEvent::AddRootFolder(add_root_folder_body).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+7 -2
View File
@@ -469,17 +469,21 @@ mod tests {
use super::*; use super::*;
use mockall::predicate::eq; use mockall::predicate::eq;
use crate::models::servarr_models::AddRootFolderBody;
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[tokio::test] #[tokio::test]
async fn test_handle_add_root_folder_command() { async fn test_handle_add_root_folder_command() {
let expected_root_folder_path = "/nfs/test".to_owned(); 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(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(), SonarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -511,6 +515,7 @@ mod tests {
series_type: "anime".to_owned(), series_type: "anime".to_owned(),
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None,
season_folder: false, season_folder: false,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: "future".to_owned(), monitor: "future".to_owned(),
@@ -522,7 +527,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::AddSeries(Some(expected_add_series_body)).into(), SonarrEvent::AddSeries(expected_add_series_body).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+6 -6
View File
@@ -94,35 +94,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => { SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into()) .handle_network_event(SonarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::Download { download_id } => { SonarrDeleteCommand::Download { download_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteDownload(Some(download_id)).into()) .handle_network_event(SonarrEvent::DeleteDownload(download_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::EpisodeFile { episode_file_id } => { SonarrDeleteCommand::EpisodeFile { episode_file_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteEpisodeFile(Some(episode_file_id)).into()) .handle_network_event(SonarrEvent::DeleteEpisodeFile(episode_file_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::Indexer { indexer_id } => { SonarrDeleteCommand::Indexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteIndexer(Some(indexer_id)).into()) .handle_network_event(SonarrEvent::DeleteIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrDeleteCommand::RootFolder { root_folder_id } => { SonarrDeleteCommand::RootFolder { root_folder_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteRootFolder(Some(root_folder_id)).into()) .handle_network_event(SonarrEvent::DeleteRootFolder(root_folder_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -138,7 +138,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
}; };
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::DeleteSeries(Some(delete_series_params)).into()) .handle_network_event(SonarrEvent::DeleteSeries(delete_series_params).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+31 -5
View File
@@ -301,7 +301,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -332,7 +332,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteDownload(Some(expected_download_id)).into(), SonarrEvent::DeleteDownload(expected_download_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -351,6 +351,32 @@ mod tests {
assert!(result.is_ok()); 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::<NetworkEvent>(
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] #[tokio::test]
async fn test_handle_delete_indexer_command() { async fn test_handle_delete_indexer_command() {
let expected_indexer_id = 1; let expected_indexer_id = 1;
@@ -358,7 +384,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(), SonarrEvent::DeleteIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -384,7 +410,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(), SonarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -414,7 +440,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteSeries(Some(expected_delete_series_params)).into(), SonarrEvent::DeleteSeries(expected_delete_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+5 -3
View File
@@ -274,7 +274,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditAllIndexerSettings(Some(params)).into()) .handle_network_event(SonarrEvent::EditAllIndexerSettings(params).into())
.await?; .await?;
"All indexer settings updated".to_owned() "All indexer settings updated".to_owned()
} else { } else {
@@ -312,13 +312,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
api_key, api_key,
seed_ratio, seed_ratio,
tags: tag, tags: tag,
tag_input_string: None,
priority, priority,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditIndexer(Some(edit_indexer_params)).into()) .handle_network_event(SonarrEvent::EditIndexer(edit_indexer_params).into())
.await?; .await?;
"Indexer updated".to_owned() "Indexer updated".to_owned()
} }
@@ -347,12 +348,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
language_profile_id, language_profile_id,
root_folder_path, root_folder_path,
tags: tag, tags: tag,
tag_input_string: None,
clear_tags, clear_tags,
}; };
self self
.network .network
.handle_network_event(SonarrEvent::EditSeries(Some(edit_series_params)).into()) .handle_network_event(SonarrEvent::EditSeries(edit_series_params).into())
.await?; .await?;
"Series Updated".to_owned() "Series Updated".to_owned()
} }
+9 -5
View File
@@ -650,7 +650,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -689,6 +689,7 @@ mod tests {
api_key: Some("testKey".to_owned()), api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()), seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25), priority: Some(25),
clear_tags: false, clear_tags: false,
}; };
@@ -696,7 +697,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(), SonarrEvent::EditIndexer(expected_edit_indexer_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -741,13 +742,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -788,13 +790,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -835,13 +838,14 @@ mod tests {
language_profile_id: Some(1), language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()), root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]), tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false, clear_tags: false,
}; };
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(), SonarrEvent::EditSeries(expected_edit_series_params).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+2 -2
View File
@@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan
SonarrGetCommand::EpisodeDetails { episode_id } => { SonarrGetCommand::EpisodeDetails { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeDetails(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeDetails(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -104,7 +104,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrGetCommand> for SonarrGetCommandHan
SonarrGetCommand::SeriesDetails { series_id } => { SonarrGetCommand::SeriesDetails { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetSeriesDetails(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetSeriesDetails(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+2 -2
View File
@@ -160,7 +160,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeDetails(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeDetails(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -232,7 +232,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeriesDetails(Some(expected_series_id)).into(), SonarrEvent::GetSeriesDetails(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+7 -9
View File
@@ -163,28 +163,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
SonarrListCommand::Episodes { series_id } => { SonarrListCommand::Episodes { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodes(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetEpisodes(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::EpisodeFiles { series_id } => { SonarrListCommand::EpisodeFiles { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeFiles(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeFiles(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::EpisodeHistory { episode_id } => { SonarrListCommand::EpisodeHistory { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeHistory(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeHistory(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrListCommand::History { events: items } => { SonarrListCommand::History { events: items } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetHistory(Some(items)).into()) .handle_network_event(SonarrEvent::GetHistory(items).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -208,7 +208,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
} => { } => {
let logs = self let logs = self
.network .network
.handle_network_event(SonarrEvent::GetLogs(Some(events)).into()) .handle_network_event(SonarrEvent::GetLogs(events).into())
.await?; .await?;
if output_in_log_format { if output_in_log_format {
@@ -246,9 +246,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
} => { } => {
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(SonarrEvent::GetSeasonHistory((series_id, season_number)).into())
SonarrEvent::GetSeasonHistory(Some((series_id, season_number))).into(),
)
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -262,7 +260,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
SonarrListCommand::SeriesHistory { series_id } => { SonarrListCommand::SeriesHistory { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetSeriesHistory(Some(series_id)).into()) .handle_network_event(SonarrEvent::GetSeriesHistory(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
+7 -7
View File
@@ -329,7 +329,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodes(Some(expected_series_id)).into(), SonarrEvent::GetEpisodes(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -355,7 +355,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeFiles(Some(expected_series_id)).into(), SonarrEvent::GetEpisodeFiles(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -381,7 +381,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetHistory(Some(expected_events)).into(), SonarrEvent::GetHistory(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -407,7 +407,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetLogs(Some(expected_events)).into(), SonarrEvent::GetLogs(expected_events).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -435,7 +435,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeriesHistory(Some(expected_series_id)).into(), SonarrEvent::GetSeriesHistory(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -461,7 +461,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeHistory(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeHistory(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -488,7 +488,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeasonHistory(Some((expected_series_id, expected_season_number))).into(), SonarrEvent::GetSeasonHistory((expected_series_id, expected_season_number)).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -75,7 +75,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
println!("Searching for episode releases. This may take a minute..."); println!("Searching for episode releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::GetEpisodeReleases(Some(episode_id)).into()) .handle_network_event(SonarrEvent::GetEpisodeReleases(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? 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..."); println!("Searching for season releases. This may take a minute...");
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into())
SonarrEvent::GetSeasonReleases(Some((series_id, season_number))).into(),
)
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -130,7 +130,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeReleases(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -160,7 +160,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetSeasonReleases(Some((expected_series_id, expected_season_number))).into(), SonarrEvent::GetSeasonReleases((expected_series_id, expected_season_number)).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+5 -5
View File
@@ -245,21 +245,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
SonarrCommand::SearchNewSeries { query } => { SonarrCommand::SearchNewSeries { query } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::SearchNewSeries(Some(query)).into()) .handle_network_event(SonarrEvent::SearchNewSeries(query).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrCommand::StartTask { task_name } => { SonarrCommand::StartTask { task_name } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::StartTask(Some(task_name)).into()) .handle_network_event(SonarrEvent::StartTask(task_name).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
SonarrCommand::TestIndexer { indexer_id } => { SonarrCommand::TestIndexer { indexer_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TestIndexer(Some(indexer_id)).into()) .handle_network_event(SonarrEvent::TestIndexer(indexer_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -274,7 +274,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
SonarrCommand::ToggleEpisodeMonitoring { episode_id } => { SonarrCommand::ToggleEpisodeMonitoring { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::ToggleEpisodeMonitoring(Some(episode_id)).into()) .handle_network_event(SonarrEvent::ToggleEpisodeMonitoring(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -285,7 +285,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(
SonarrEvent::ToggleSeasonMonitoring(Some((series_id, season_number))).into(), SonarrEvent::ToggleSeasonMonitoring((series_id, season_number)).into(),
) )
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
+1 -1
View File
@@ -71,7 +71,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrRefreshCommand>
SonarrRefreshCommand::Series { series_id } => { SonarrRefreshCommand::Series { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::UpdateAndScanSeries(Some(series_id)).into()) .handle_network_event(SonarrEvent::UpdateAndScanSeries(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -119,7 +119,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), SonarrEvent::UpdateAndScanSeries(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
+10 -11
View File
@@ -346,7 +346,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(), SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -434,7 +434,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(), SonarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -470,7 +470,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::GetEpisodeReleases(Some(expected_episode_id)).into(), SonarrEvent::GetEpisodeReleases(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -498,7 +498,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -571,7 +571,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::UpdateAndScanSeries(Some(expected_series_id)).into(), SonarrEvent::UpdateAndScanSeries(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -597,7 +597,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::SearchNewSeries(Some(expected_search_query)).into(), SonarrEvent::SearchNewSeries(expected_search_query).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -624,7 +624,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::StartTask(Some(expected_task_name)).into(), SonarrEvent::StartTask(expected_task_name).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -651,7 +651,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TestIndexer(Some(expected_indexer_id)).into(), SonarrEvent::TestIndexer(expected_indexer_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -698,7 +698,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::ToggleEpisodeMonitoring(Some(expected_episode_id)).into(), SonarrEvent::ToggleEpisodeMonitoring(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -729,8 +729,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::ToggleSeasonMonitoring(Some((expected_series_id, expected_season_number))) SonarrEvent::ToggleSeasonMonitoring((expected_series_id, expected_season_number)).into(),
.into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -83,7 +83,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
SonarrTriggerAutomaticSearchCommand::Series { series_id } => { SonarrTriggerAutomaticSearchCommand::Series { series_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(Some(series_id)).into()) .handle_network_event(SonarrEvent::TriggerAutomaticSeriesSearch(series_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -94,7 +94,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
let resp = self let resp = self
.network .network
.handle_network_event( .handle_network_event(
SonarrEvent::TriggerAutomaticSeasonSearch(Some((series_id, season_number))).into(), SonarrEvent::TriggerAutomaticSeasonSearch((series_id, season_number)).into(),
) )
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
@@ -102,7 +102,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
SonarrTriggerAutomaticSearchCommand::Episode { episode_id } => { SonarrTriggerAutomaticSearchCommand::Episode { episode_id } => {
let resp = self let resp = self
.network .network
.handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(Some(episode_id)).into()) .handle_network_event(SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id).into())
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
@@ -166,7 +166,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticSeriesSearch(Some(expected_series_id)).into(), SonarrEvent::TriggerAutomaticSeriesSearch(expected_series_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -197,11 +197,8 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticSeasonSearch(Some(( SonarrEvent::TriggerAutomaticSeasonSearch((expected_series_id, expected_season_number))
expected_series_id, .into(),
expected_season_number,
)))
.into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -233,7 +230,7 @@ mod tests {
mock_network mock_network
.expect_handle_network_event() .expect_handle_network_event()
.with(eq::<NetworkEvent>( .with(eq::<NetworkEvent>(
SonarrEvent::TriggerAutomaticEpisodeSearch(Some(expected_episode_id)).into(), SonarrEvent::TriggerAutomaticEpisodeSearch(expected_episode_id).into(),
)) ))
.times(1) .times(1)
.returning(|_| { .returning(|_| {
@@ -160,7 +160,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
ActiveRadarrBlock::DeleteBlocklistItemPrompt, ActiveRadarrBlock::DeleteBlocklistItemPrompt,
RadarrEvent::DeleteBlocklistItem(None) RadarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
@@ -361,7 +361,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Blocklist,
ActiveRadarrBlock::DeleteBlocklistItemPrompt, ActiveRadarrBlock::DeleteBlocklistItemPrompt,
RadarrEvent::DeleteBlocklistItem(None) RadarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveRadarrBlock::Blocklist, 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] #[test]
fn test_blocklist_handler_not_ready_when_loading() { fn test_blocklist_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
+10 -4
View File
@@ -28,6 +28,10 @@ impl<'a, 'b> BlocklistHandler<'a, 'b> {
self.app.data.radarr_data.blocklist, self.app.data.radarr_data.blocklist,
BlocklistItem 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> { 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 { match self.active_radarr_block {
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
Some(RadarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -151,8 +156,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
Some(RadarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -2,6 +2,8 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -18,6 +20,51 @@ pub(super) struct EditCollectionHandler<'a, 'b> {
context: Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EDIT_COLLECTION_BLOCKS.contains(&active_block) 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() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::EditCollectionConfirmPrompt => { ActiveRadarrBlock::EditCollectionConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
Some(RadarrEvent::EditCollection(None)); self.build_edit_collection_params(),
));
self.app.should_refresh = true; self.app.should_refresh = true;
} }
@@ -310,7 +358,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
&& key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -7,8 +8,9 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; 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::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::modals::EditCollectionModal;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS,
@@ -519,7 +521,34 @@ mod tests {
#[test] #[test]
fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() { fn test_edit_collection_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); 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::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
@@ -545,9 +574,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditCollection(None)) Some(RadarrEvent::EditCollection(expected_edit_collection_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.edit_collection_modal.is_none());
} }
#[test] #[test]
@@ -919,7 +949,34 @@ mod tests {
#[test] #[test]
fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() { fn test_edit_collection_confirm_prompt_prompt_confirmation_confirm() {
let mut app = App::default(); 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::Collections.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.data.radarr_data.selected_block = app.data.radarr_data.selected_block =
@@ -944,9 +1001,10 @@ mod tests {
); );
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::EditCollection(None)) Some(RadarrEvent::EditCollection(expected_edit_collection_params))
); );
assert!(app.should_refresh); 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] #[test]
fn test_edit_collection_handler_is_not_ready_when_loading() { fn test_edit_collection_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -1,11 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler; 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::handlers::KeyEventHandler;
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
@@ -137,7 +139,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt, ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload(None) RadarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
@@ -154,7 +156,7 @@ mod tests {
.data .data
.radarr_data .radarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
@@ -336,7 +338,7 @@ mod tests {
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::DeleteDownloadPrompt, ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload(None) RadarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveRadarrBlock::Downloads, ActiveRadarrBlock::Downloads,
@@ -353,7 +355,7 @@ mod tests {
.data .data
.radarr_data .radarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.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] #[test]
fn test_downloads_handler_not_ready_when_loading() { fn test_downloads_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -27,6 +27,10 @@ impl<'a, 'b> DownloadsHandler<'a, 'b> {
self.app.data.radarr_data.downloads, self.app.data.radarr_data.downloads,
DownloadRecord 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> { 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 { match self.active_radarr_block {
ActiveRadarrBlock::DeleteDownloadPrompt => { ActiveRadarrBlock::DeleteDownloadPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -138,7 +143,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
ActiveRadarrBlock::DeleteDownloadPrompt => { ActiveRadarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
@@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; 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<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EDIT_INDEXER_BLOCKS.contains(&active_block) 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(); let selected_block = self.app.data.radarr_data.selected_block.get_active_block();
match selected_block { match selected_block {
ActiveRadarrBlock::EditIndexerConfirmPrompt => { ActiveRadarrBlock::EditIndexerConfirmPrompt => {
let radarr_data = &mut self.app.data.radarr_data; if self.app.data.radarr_data.prompt_confirm {
if radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action =
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer(None)); Some(RadarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
radarr_data.edit_indexer_modal = None; self.app.data.radarr_data.edit_indexer_modal = None;
} }
self.app.pop_navigation_stack(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -4,9 +4,12 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; 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; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -896,7 +899,32 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); .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; app.data.radarr_data.prompt_confirm = true;
EditIndexerHandler::with( EditIndexerHandler::with(
@@ -908,11 +936,11 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); 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!(app.should_refresh);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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::servarr_data::radarr::radarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use super::*; use super::*;
@@ -1709,7 +1737,32 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); .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( EditIndexerHandler::with(
DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
@@ -1720,11 +1773,11 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); 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!(app.should_refresh);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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] #[test]
fn test_edit_indexer_handler_is_not_ready_when_loading() { fn test_edit_indexer_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::IndexerSettings;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
@@ -19,6 +20,14 @@ pub(super) struct IndexerSettingsHandler<'a, 'b> {
_context: Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
INDEXER_SETTINGS_BLOCKS.contains(&active_block) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
@@ -166,12 +175,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
match self.app.data.radarr_data.selected_block.get_active_block() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::IndexerSettingsConfirmPrompt => { ActiveRadarrBlock::IndexerSettingsConfirmPrompt => {
let radarr_data = &mut self.app.data.radarr_data; if self.app.data.radarr_data.prompt_confirm {
if radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = Some(
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditAllIndexerSettings(None)); RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
);
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
radarr_data.indexer_settings = None; self.app.data.radarr_data.indexer_settings = None;
} }
self.app.pop_navigation_stack(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(
Some(RadarrEvent::EditAllIndexerSettings(None)); RadarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_body()),
);
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,11 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; 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::handlers::KeyEventHandler;
use crate::models::radarr_models::IndexerSettings; use crate::models::radarr_models::IndexerSettings;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
@@ -472,7 +474,7 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .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; app.data.radarr_data.prompt_confirm = true;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
@@ -486,9 +488,9 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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); assert!(app.should_refresh);
} }
@@ -858,7 +860,7 @@ mod tests {
} }
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use crate::{ use crate::{
models::{ models::{
@@ -937,7 +939,7 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .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( IndexerSettingsHandler::with(
DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
@@ -950,9 +952,9 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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); 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] #[test]
fn test_edit_indexer_settings_handler_not_ready_when_loading() { fn test_edit_indexer_settings_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -7,6 +8,7 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::indexers::IndexersHandler; use crate::handlers::radarr_handlers::indexers::IndexersHandler;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
@@ -123,17 +125,17 @@ mod tests {
} }
mod test_handle_submit { 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::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
}; };
use crate::models::servarr_models::{Indexer, IndexerField}; use crate::models::servarr_models::{Indexer, IndexerField};
use crate::network::radarr_network::RadarrEvent;
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serde_json::{Number, Value}; use serde_json::{Number, Value};
use crate::network::radarr_network::RadarrEvent;
use super::*; use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
@@ -239,11 +241,7 @@ mod tests {
#[test] #[test]
fn test_delete_indexer_prompt_confirm_submit() { fn test_delete_indexer_prompt_confirm_submit() {
let mut app = App::default(); let mut app = App::default();
app app.data.radarr_data.indexers.set_items(vec![indexer()]);
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
@@ -259,7 +257,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteIndexer(None)) Some(RadarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
} }
@@ -348,13 +346,13 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*;
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::indexer;
use crate::{ use crate::{
models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
network::radarr_network::RadarrEvent, network::radarr_network::RadarrEvent,
}; };
use super::*;
#[test] #[test]
fn test_refresh_indexers_key() { fn test_refresh_indexers_key() {
let mut app = App::default(); let mut app = App::default();
@@ -542,11 +540,7 @@ mod tests {
#[test] #[test]
fn test_delete_indexer_prompt_confirm() { fn test_delete_indexer_prompt_confirm() {
let mut app = App::default(); let mut app = App::default();
app app.data.radarr_data.indexers.set_items(vec![indexer()]);
.data
.radarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into());
@@ -561,7 +555,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteIndexer(None)) Some(RadarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); 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] #[test]
fn test_indexers_handler_not_ready_when_loading() { fn test_indexers_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
+8 -2
View File
@@ -33,6 +33,10 @@ pub(super) struct IndexersHandler<'a, 'b> {
impl<'a, 'b> IndexersHandler<'a, 'b> { impl<'a, 'b> IndexersHandler<'a, 'b> {
handle_table_events!(self, indexers, self.app.data.radarr_data.indexers, Indexer); 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> { 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) { fn handle_submit(&mut self) {
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
let indexer_id = self.extract_indexer_id();
let radarr_data = &mut self.app.data.radarr_data; let radarr_data = &mut self.app.data.radarr_data;
if radarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -189,7 +194,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
@@ -1,10 +1,14 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS,
}; };
use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; 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 .radarr_data
.add_searched_movies .add_searched_movies
.as_mut() .as_mut()
.unwrap(), .unwrap_or(&mut StatefulTable::default()),
AddMovieSearchResult 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> { 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() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::AddMovieConfirmPrompt => { ActiveRadarrBlock::AddMovieConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -461,7 +552,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
&& key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
} }
@@ -1,17 +1,24 @@
#[cfg(test)] #[cfg(test)]
mod tests { 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 pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::library::add_movie_handler::AddMovieHandler; 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::handlers::KeyEventHandler;
use crate::models::radarr_models::{AddMovieSearchResult, MinimumAvailability, MovieMonitor}; 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_data::radarr::radarr_data::{ActiveRadarrBlock, ADD_MOVIE_BLOCKS};
use crate::models::servarr_models::RootFolder; use crate::models::servarr_models::RootFolder;
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use bimap::BiMap;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -758,6 +765,9 @@ mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; 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::radarr_models::Movie;
use crate::models::servarr_data::radarr::modals::AddMovieModal; use crate::models::servarr_data::radarr::modals::AddMovieModal;
use crate::models::servarr_data::radarr::radarr_data::ADD_MOVIE_SELECTION_BLOCKS; 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); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
} }
#[test] #[rstest]
fn test_add_movie_confirm_prompt_prompt_confirmation_submit() { fn test_add_movie_confirm_prompt_prompt_confirmation_submit(
#[values(true, false)] movie_details_context: bool,
) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default()); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
@@ -982,21 +994,67 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, ADD_MOVIE_SELECTION_BLOCKS.len() - 1); .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( AddMovieHandler::with(
SUBMIT_KEY, SUBMIT_KEY,
&mut app, &mut app,
ActiveRadarrBlock::AddMoviePrompt, ActiveRadarrBlock::AddMoviePrompt,
None, context,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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] #[rstest]
@@ -1266,10 +1324,18 @@ mod tests {
} }
mod test_handle_key_char { mod test_handle_key_char {
use bimap::BiMap;
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*; use super::*;
use crate::{ use crate::{
handlers::radarr_handlers::radarr_handler_test_utils::utils::{
add_movie_body, add_movie_search_result, collection_movie,
},
models::{ models::{
servarr_data::radarr::{modals::AddMovieModal, radarr_data::ADD_MOVIE_SELECTION_BLOCKS}, servarr_data::radarr::{modals::AddMovieModal, radarr_data::ADD_MOVIE_SELECTION_BLOCKS},
stateful_table::StatefulTable,
BlockSelectionState, BlockSelectionState,
}, },
network::radarr_network::RadarrEvent, network::radarr_network::RadarrEvent,
@@ -1368,8 +1434,10 @@ mod tests {
); );
} }
#[test] #[rstest]
fn test_add_movie_confirm_prompt_prompt_confirmation_confirm() { fn test_add_movie_confirm_prompt_prompt_confirmation_confirm(
#[values(true, false)] movie_details_context: bool,
) {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default()); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
@@ -1380,21 +1448,67 @@ mod tests {
.radarr_data .radarr_data
.selected_block .selected_block
.set_index(0, ADD_MOVIE_SELECTION_BLOCKS.len() - 1); .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( AddMovieHandler::with(
DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
&mut app, &mut app,
ActiveRadarrBlock::AddMoviePrompt, ActiveRadarrBlock::AddMoviePrompt,
None, context,
) )
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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] #[test]
fn test_add_movie_handler_is_not_ready_when_loading() { fn test_add_movie_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -16,6 +17,21 @@ pub(super) struct DeleteMovieHandler<'a, 'b> {
_context: Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
DELETE_MOVIE_BLOCKS.contains(&active_block) 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() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::DeleteMovieConfirmPrompt => { ActiveRadarrBlock::DeleteMovieConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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; self.app.should_refresh = true;
} else { } else {
self.app.data.radarr_data.reset_delete_movie_preferences(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,12 +1,15 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::library::delete_movie_handler::DeleteMovieHandler; 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::handlers::KeyEventHandler;
use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -119,8 +122,14 @@ mod tests {
#[test] #[test]
fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() { fn test_delete_movie_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); 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::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.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.prompt_confirm = true;
app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = 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.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteMovie(None)) Some(RadarrEvent::DeleteMovie(expected_delete_movie_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert!(app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
assert!(app.data.radarr_data.add_list_exclusion); assert!(!app.data.radarr_data.add_list_exclusion);
} }
#[test] #[test]
@@ -212,6 +221,7 @@ mod tests {
mod test_handle_esc { mod test_handle_esc {
use super::*; use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
@@ -248,14 +258,21 @@ mod tests {
}, },
network::radarr_network::RadarrEvent, network::radarr_network::RadarrEvent,
}; };
use pretty_assertions::assert_eq;
use super::*; use super::*;
#[test] #[test]
fn test_delete_movie_confirm_prompt_prompt_confirm() { fn test_delete_movie_confirm_prompt_prompt_confirm() {
let mut app = App::default(); 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::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.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.delete_movie_files = true;
app.data.radarr_data.add_list_exclusion = true; app.data.radarr_data.add_list_exclusion = true;
app.data.radarr_data.selected_block = BlockSelectionState::new(DELETE_MOVIE_SELECTION_BLOCKS); 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.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteMovie(None)) Some(RadarrEvent::DeleteMovie(expected_delete_movie_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert!(app.data.radarr_data.delete_movie_files); assert!(!app.data.radarr_data.delete_movie_files);
assert!(app.data.radarr_data.add_list_exclusion); 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] #[test]
fn test_delete_movie_handler_not_ready_when_loading() { fn test_delete_movie_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -2,6 +2,8 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -18,6 +20,57 @@ pub(super) struct EditMovieHandler<'a, 'b> {
context: Option<ActiveRadarrBlock>, context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
EDIT_MOVIE_BLOCKS.contains(&active_block) 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() { match self.app.data.radarr_data.selected_block.get_active_block() {
ActiveRadarrBlock::EditMovieConfirmPrompt => { ActiveRadarrBlock::EditMovieConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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; self.app.should_refresh = true;
} }
@@ -333,7 +387,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
&& key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -7,8 +8,9 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHandler; 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::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::modals::EditMovieModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
@@ -641,7 +643,34 @@ mod tests {
#[test] #[test]
fn test_edit_movie_confirm_prompt_prompt_confirmation_submit() { fn test_edit_movie_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); 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::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.data.radarr_data.prompt_confirm = true; 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.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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); assert!(app.should_refresh);
} }
@@ -1053,7 +1082,34 @@ mod tests {
#[test] #[test]
fn test_edit_movie_confirm_prompt_prompt_confirm() { fn test_edit_movie_confirm_prompt_prompt_confirm() {
let mut app = App::default(); 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::Movies.into());
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.data.radarr_data.selected_block = BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); 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.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, 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); 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] #[test]
fn test_edit_movie_handler_is_not_ready_when_loading() { fn test_edit_movie_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -6,7 +6,9 @@ use crate::event::Key;
use crate::handle_table_events; use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS, ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS, MOVIE_DETAILS_BLOCKS,
}; };
@@ -79,6 +81,35 @@ impl<'a, 'b> MovieDetailsHandler<'a, 'b> {
.movie_crew, .movie_crew,
Credit 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> { 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 => { ActiveRadarrBlock::AutomaticallySearchMoviePrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::TriggerAutomaticSearch(None)); Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::UpdateAndScanPrompt => { ActiveRadarrBlock::UpdateAndScanPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -259,8 +291,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
} }
ActiveRadarrBlock::ManualSearchConfirmPrompt => { ActiveRadarrBlock::ManualSearchConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(
Some(RadarrEvent::DownloadRelease(None)); self.build_radarr_release_download_body(),
));
} }
self.app.pop_navigation_stack(); 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 = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::TriggerAutomaticSearch(None)); Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
@@ -2,7 +2,7 @@
mod tests { mod tests {
use std::cmp::Ordering; use std::cmp::Ordering;
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use serde_json::Number; use serde_json::Number;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -13,9 +13,10 @@ mod tests {
use crate::handlers::radarr_handlers::library::movie_details_handler::{ use crate::handlers::radarr_handlers::library::movie_details_handler::{
releases_sorting_options, MovieDetailsHandler, releases_sorting_options, MovieDetailsHandler,
}; };
use crate::handlers::radarr_handlers::radarr_handler_test_utils::utils::{movie, release};
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::RadarrRelease;
use crate::models::radarr_models::{Credit, MovieHistoryItem}; 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::modals::MovieDetailsModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::models::servarr_models::{Language, Quality, QualityWrapper}; use crate::models::servarr_models::{Language, Quality, QualityWrapper};
@@ -130,6 +131,7 @@ mod tests {
mod test_handle_home_end { mod test_handle_home_end {
use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use pretty_assertions::assert_eq;
use super::*; use super::*;
@@ -359,25 +361,31 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveRadarrBlock::AutomaticallySearchMoviePrompt, ActiveRadarrBlock::AutomaticallySearchMoviePrompt,
RadarrEvent::TriggerAutomaticSearch(None) RadarrEvent::TriggerAutomaticSearch(1)
)]
#[case(
ActiveRadarrBlock::UpdateAndScanPrompt,
RadarrEvent::UpdateAndScan(None)
)] )]
#[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan(1))]
#[case( #[case(
ActiveRadarrBlock::ManualSearchConfirmPrompt, 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( fn test_movie_info_prompt_confirm_submit(
#[case] prompt_block: ActiveRadarrBlock, #[case] prompt_block: ActiveRadarrBlock,
#[case] expected_action: RadarrEvent, #[case] expected_action: RadarrEvent,
) { ) {
let mut app = App::default(); 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()), movie_details: ScrollableText::with_string("test".to_owned()),
..MovieDetailsModal::default() ..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.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into()); app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
@@ -771,25 +779,32 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveRadarrBlock::AutomaticallySearchMoviePrompt, ActiveRadarrBlock::AutomaticallySearchMoviePrompt,
RadarrEvent::TriggerAutomaticSearch(None) RadarrEvent::TriggerAutomaticSearch(1)
)]
#[case(
ActiveRadarrBlock::UpdateAndScanPrompt,
RadarrEvent::UpdateAndScan(None)
)] )]
#[case(ActiveRadarrBlock::UpdateAndScanPrompt, RadarrEvent::UpdateAndScan(1))]
#[case( #[case(
ActiveRadarrBlock::ManualSearchConfirmPrompt, ActiveRadarrBlock::ManualSearchConfirmPrompt,
RadarrEvent::DownloadRelease(None) RadarrEvent::DownloadRelease(RadarrReleaseDownloadBody {
guid: "1234".to_owned(),
indexer_id: 2,
movie_id: 1,
})
)] )]
fn test_movie_info_prompt_confirm( fn test_movie_info_prompt_confirm(
#[case] prompt_block: ActiveRadarrBlock, #[case] prompt_block: ActiveRadarrBlock,
#[case] expected_action: RadarrEvent, #[case] expected_action: RadarrEvent,
) { ) {
let mut app = App::default(); 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()), movie_details: ScrollableText::with_string("test".to_owned()),
..MovieDetailsModal::default() ..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(ActiveRadarrBlock::MovieDetails.into());
app.push_navigation_stack(prompt_block.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] #[test]
fn test_releases_sorting_options_source() { fn test_releases_sorting_options_source() {
let expected_cmp_fn: fn(&RadarrRelease, &RadarrRelease) -> Ordering = let expected_cmp_fn: fn(&RadarrRelease, &RadarrRelease) -> Ordering =
@@ -1,6 +1,18 @@
#[cfg(test)] #[cfg(test)]
#[macro_use] #[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_export]
macro_rules! test_edit_movie_key { macro_rules! test_edit_movie_key {
($handler:ident, $block:expr, $context:expr) => { ($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<String> {
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<String> {
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,
},
}
}
} }
@@ -5,7 +5,7 @@ use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; 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_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::models::HorizontallyScrollableText;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; 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, self.app.data.radarr_data.root_folders,
RootFolder 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> { 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 => { ActiveRadarrBlock::DeleteRootFolderPrompt => {
if self.app.data.radarr_data.prompt_confirm { if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = 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(); self.app.pop_navigation_stack();
@@ -140,7 +166,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
.text .text
.is_empty() => .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.data.radarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -192,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = 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(); self.app.pop_navigation_stack();
} }
@@ -1,14 +1,16 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; 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::radarr_handlers::root_folders::RootFoldersHandler;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; 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::models::HorizontallyScrollableText;
mod test_handle_home_end { mod test_handle_home_end {
@@ -250,6 +252,9 @@ mod tests {
#[test] #[test]
fn test_add_root_folder_prompt_confirm_submit() { fn test_add_root_folder_prompt_confirm_submit() {
let mut app = App::default(); let mut app = App::default();
let expected_add_root_folder_body = AddRootFolderBody {
path: "Test".to_owned(),
};
app app
.data .data
.radarr_data .radarr_data
@@ -273,7 +278,7 @@ mod tests {
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::AddRootFolder(None)) Some(RadarrEvent::AddRootFolder(expected_add_root_folder_body))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -314,7 +319,7 @@ mod tests {
.data .data
.radarr_data .radarr_data
.root_folders .root_folders
.set_items(vec![RootFolder::default()]); .set_items(vec![root_folder()]);
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
@@ -330,7 +335,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteRootFolder(None)) Some(RadarrEvent::DeleteRootFolder(1))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -604,7 +609,7 @@ mod tests {
.data .data
.radarr_data .radarr_data
.root_folders .root_folders
.set_items(vec![RootFolder::default()]); .set_items(vec![root_folder()]);
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into());
@@ -619,7 +624,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::DeleteRootFolder(None)) Some(RadarrEvent::DeleteRootFolder(1))
); );
assert_eq!( assert_eq!(
app.get_current_route(), 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] #[test]
fn test_root_folders_handler_not_ready_when_loading() { fn test_root_folders_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -2,6 +2,7 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::stateful_list::StatefulList; use crate::models::stateful_list::StatefulList;
use crate::models::Scrollable; use crate::models::Scrollable;
@@ -18,6 +19,18 @@ pub(super) struct SystemDetailsHandler<'a, 'b> {
_context: Option<ActiveRadarrBlock>, _context: Option<ActiveRadarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler<'a, 'b> {
fn accepts(active_block: ActiveRadarrBlock) -> bool { fn accepts(active_block: ActiveRadarrBlock) -> bool {
SYSTEM_DETAILS_BLOCKS.contains(&active_block) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
@@ -137,7 +150,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
} }
ActiveRadarrBlock::SystemTaskStartConfirmPrompt => { ActiveRadarrBlock::SystemTaskStartConfirmPrompt => {
if self.app.data.radarr_data.prompt_confirm { 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(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.radarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
} }
@@ -1,6 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -8,7 +8,7 @@ mod tests {
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::KeyEventHandler; 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::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS, ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS,
}; };
@@ -16,6 +16,7 @@ mod tests {
use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::models::{HorizontallyScrollableText, ScrollableText};
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::models::{HorizontallyScrollableText, ScrollableText};
@@ -236,6 +237,7 @@ mod tests {
mod test_handle_home_end { mod test_handle_home_end {
use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::models::{HorizontallyScrollableText, ScrollableText};
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use pretty_assertions::assert_eq;
use super::*; use super::*;
@@ -676,6 +678,10 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned()); app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned());
app.data.radarr_data.prompt_confirm = true; 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::SystemTasks.into());
app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into());
@@ -690,7 +696,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::StartTask(None)) Some(RadarrEvent::StartTask(RadarrTaskName::default()))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -831,6 +837,7 @@ mod tests {
} }
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -894,6 +901,10 @@ mod tests {
fn test_system_tasks_start_task_prompt_confirm() { fn test_system_tasks_start_task_prompt_confirm() {
let mut app = App::default(); let mut app = App::default();
app.data.radarr_data.updates = ScrollableText::with_string("Test".to_owned()); 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::SystemTasks.into());
app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::SystemTaskStartConfirmPrompt.into());
@@ -908,7 +919,7 @@ mod tests {
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::StartTask(None)) Some(RadarrEvent::StartTask(RadarrTaskName::default()))
); );
assert_eq!( assert_eq!(
app.get_current_route(), 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] #[test]
fn test_system_details_handler_not_ready_when_loading() { fn test_system_details_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -161,7 +161,7 @@ mod tests {
#[case( #[case(
ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::Blocklist,
ActiveSonarrBlock::DeleteBlocklistItemPrompt, ActiveSonarrBlock::DeleteBlocklistItemPrompt,
SonarrEvent::DeleteBlocklistItem(None) SonarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::Blocklist,
@@ -361,7 +361,7 @@ mod tests {
#[case( #[case(
ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::Blocklist,
ActiveSonarrBlock::DeleteBlocklistItemPrompt, ActiveSonarrBlock::DeleteBlocklistItemPrompt,
SonarrEvent::DeleteBlocklistItem(None) SonarrEvent::DeleteBlocklistItem(3)
)] )]
#[case( #[case(
ActiveSonarrBlock::Blocklist, 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] #[test]
fn test_blocklist_handler_not_ready_when_loading() { fn test_blocklist_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
+10 -4
View File
@@ -28,6 +28,10 @@ impl<'a, 'b> BlocklistHandler<'a, 'b> {
self.app.data.sonarr_data.blocklist, self.app.data.sonarr_data.blocklist,
BlocklistItem 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> { 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 { match self.active_sonarr_block {
ActiveSonarrBlock::DeleteBlocklistItemPrompt => { ActiveSonarrBlock::DeleteBlocklistItemPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
Some(SonarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -151,8 +156,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
ActiveSonarrBlock::DeleteBlocklistItemPrompt => { ActiveSonarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
Some(SonarrEvent::DeleteBlocklistItem(None)); self.extract_blocklist_item_id(),
));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -1,11 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::downloads::DownloadsHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS};
use crate::models::sonarr_models::DownloadRecord; use crate::models::sonarr_models::DownloadRecord;
@@ -138,7 +140,7 @@ mod tests {
#[case( #[case(
ActiveSonarrBlock::Downloads, ActiveSonarrBlock::Downloads,
ActiveSonarrBlock::DeleteDownloadPrompt, ActiveSonarrBlock::DeleteDownloadPrompt,
SonarrEvent::DeleteDownload(None) SonarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveSonarrBlock::Downloads, ActiveSonarrBlock::Downloads,
@@ -155,7 +157,7 @@ mod tests {
.data .data
.sonarr_data .sonarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.prompt_confirm = true;
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
@@ -338,7 +340,7 @@ mod tests {
#[case( #[case(
ActiveSonarrBlock::Downloads, ActiveSonarrBlock::Downloads,
ActiveSonarrBlock::DeleteDownloadPrompt, ActiveSonarrBlock::DeleteDownloadPrompt,
SonarrEvent::DeleteDownload(None) SonarrEvent::DeleteDownload(1)
)] )]
#[case( #[case(
ActiveSonarrBlock::Downloads, ActiveSonarrBlock::Downloads,
@@ -355,7 +357,7 @@ mod tests {
.data .data
.sonarr_data .sonarr_data
.downloads .downloads
.set_items(vec![DownloadRecord::default()]); .set_items(vec![download_record()]);
app.push_navigation_stack(base_route.into()); app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.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] #[test]
fn test_downloads_handler_not_ready_when_loading() { fn test_downloads_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -27,6 +27,10 @@ impl<'a, 'b> DownloadsHandler<'a, 'b> {
self.app.data.sonarr_data.downloads, self.app.data.sonarr_data.downloads,
DownloadRecord 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> { 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 { match self.active_sonarr_block {
ActiveSonarrBlock::DeleteDownloadPrompt => { ActiveSonarrBlock::DeleteDownloadPrompt => {
if self.app.data.sonarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -138,7 +143,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
ActiveSonarrBlock::DeleteDownloadPrompt => { ActiveSonarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.sonarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
@@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; 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<ActiveSonarrBlock>, _context: Option<ActiveSonarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'a, 'b> {
fn accepts(active_block: ActiveSonarrBlock) -> bool { fn accepts(active_block: ActiveSonarrBlock) -> bool {
EDIT_INDEXER_BLOCKS.contains(&active_block) 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(); let selected_block = self.app.data.sonarr_data.selected_block.get_active_block();
match selected_block { match selected_block {
ActiveSonarrBlock::EditIndexerConfirmPrompt => { ActiveSonarrBlock::EditIndexerConfirmPrompt => {
let sonarr_data = &mut self.app.data.sonarr_data; if self.app.data.sonarr_data.prompt_confirm {
if sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action =
sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditIndexer(None)); Some(SonarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
sonarr_data.edit_indexer_modal = None; self.app.data.sonarr_data.edit_indexer_modal = None;
} }
self.app.pop_navigation_stack(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -4,9 +4,12 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::modals::EditIndexerModal; use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; 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; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -896,7 +899,32 @@ mod tests {
.sonarr_data .sonarr_data
.selected_block .selected_block
.set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); .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; app.data.sonarr_data.prompt_confirm = true;
EditIndexerHandler::with( EditIndexerHandler::with(
@@ -908,11 +936,11 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); 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!(app.should_refresh);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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::servarr_data::sonarr::sonarr_data::EDIT_INDEXER_TORRENT_SELECTION_BLOCKS;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use super::*; use super::*;
@@ -1709,7 +1737,32 @@ mod tests {
.sonarr_data .sonarr_data
.selected_block .selected_block
.set_index(0, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.len() - 1); .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( EditIndexerHandler::with(
DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
@@ -1720,11 +1773,11 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); 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!(app.should_refresh);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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] #[test]
fn test_edit_indexer_handler_is_not_ready_when_loading() { fn test_edit_indexer_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -6,6 +6,7 @@ use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::models::sonarr_models::IndexerSettings;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
#[cfg(test)] #[cfg(test)]
@@ -19,6 +20,23 @@ pub(super) struct IndexerSettingsHandler<'a, 'b> {
_context: Option<ActiveSonarrBlock>, _context: Option<ActiveSonarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandler<'a, 'b> {
fn accepts(active_block: ActiveSonarrBlock) -> bool { fn accepts(active_block: ActiveSonarrBlock) -> bool {
INDEXER_SETTINGS_BLOCKS.contains(&active_block) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
@@ -119,12 +137,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
ActiveSonarrBlock::AllIndexerSettingsPrompt => { ActiveSonarrBlock::AllIndexerSettingsPrompt => {
match self.app.data.sonarr_data.selected_block.get_active_block() { match self.app.data.sonarr_data.selected_block.get_active_block() {
ActiveSonarrBlock::IndexerSettingsConfirmPrompt => { ActiveSonarrBlock::IndexerSettingsConfirmPrompt => {
let sonarr_data = &mut self.app.data.sonarr_data; if self.app.data.sonarr_data.prompt_confirm {
if sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action = Some(
sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(None)); SonarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_params()),
);
self.app.should_refresh = true; self.app.should_refresh = true;
} else { } else {
sonarr_data.indexer_settings = None; self.app.data.sonarr_data.indexer_settings = None;
} }
self.app.pop_navigation_stack(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(
Some(SonarrEvent::EditAllIndexerSettings(None)); self.build_edit_indexer_settings_params(),
));
self.app.should_refresh = true; self.app.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,11 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
@@ -285,7 +287,7 @@ mod tests {
.sonarr_data .sonarr_data
.selected_block .selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .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; app.data.sonarr_data.prompt_confirm = true;
IndexerSettingsHandler::with( IndexerSettingsHandler::with(
@@ -299,9 +301,9 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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); assert!(app.should_refresh);
} }
@@ -468,11 +470,11 @@ mod tests {
mod test_handle_key_char { mod test_handle_key_char {
use crate::{ use crate::{
models::{ models::{
servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, servarr_data::sonarr::sonarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, BlockSelectionState,
sonarr_models::IndexerSettings, BlockSelectionState,
}, },
network::sonarr_network::SonarrEvent, network::sonarr_network::SonarrEvent,
}; };
use pretty_assertions::assert_eq;
use super::*; use super::*;
@@ -488,7 +490,7 @@ mod tests {
.sonarr_data .sonarr_data
.selected_block .selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); .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( IndexerSettingsHandler::with(
DEFAULT_KEYBINDINGS.confirm.key, DEFAULT_KEYBINDINGS.confirm.key,
@@ -501,9 +503,9 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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); 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] #[test]
fn test_edit_indexer_settings_handler_not_ready_when_loading() { fn test_edit_indexer_settings_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -1,5 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -7,6 +8,7 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::indexers::IndexersHandler; use crate::handlers::sonarr_handlers::indexers::IndexersHandler;
use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::indexer;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
@@ -245,11 +247,7 @@ mod tests {
#[test] #[test]
fn test_delete_indexer_prompt_confirm_submit() { fn test_delete_indexer_prompt_confirm_submit() {
let mut app = App::default(); let mut app = App::default();
app app.data.sonarr_data.indexers.set_items(vec![indexer()]);
.data
.sonarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into());
@@ -265,7 +263,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteIndexer(None)) Some(SonarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into());
} }
@@ -556,11 +554,7 @@ mod tests {
#[test] #[test]
fn test_delete_indexer_prompt_confirm() { fn test_delete_indexer_prompt_confirm() {
let mut app = App::default(); let mut app = App::default();
app app.data.sonarr_data.indexers.set_items(vec![indexer()]);
.data
.sonarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteIndexerPrompt.into());
@@ -575,7 +569,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteIndexer(None)) Some(SonarrEvent::DeleteIndexer(1))
); );
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); 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] #[test]
fn test_indexers_handler_not_ready_when_loading() { fn test_indexers_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
+9 -4
View File
@@ -33,6 +33,10 @@ pub(super) struct IndexersHandler<'a, 'b> {
impl<'a, 'b> IndexersHandler<'a, 'b> { impl<'a, 'b> IndexersHandler<'a, 'b> {
handle_table_events!(self, indexers, self.app.data.sonarr_data.indexers, Indexer); 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> { 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) { fn handle_submit(&mut self) {
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::DeleteIndexerPrompt => { ActiveSonarrBlock::DeleteIndexerPrompt => {
let sonarr_data = &mut self.app.data.sonarr_data; if self.app.data.sonarr_data.prompt_confirm {
if sonarr_data.prompt_confirm { self.app.data.sonarr_data.prompt_confirm_action =
sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteIndexer(None)); Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id()));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -189,7 +193,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
ActiveSonarrBlock::DeleteIndexerPrompt => { ActiveSonarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.sonarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
@@ -1,10 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, ADD_SERIES_BLOCKS, ADD_SERIES_SELECTION_BLOCKS, 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::models::{BlockSelectionState, Scrollable};
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; 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 .sonarr_data
.add_searched_series .add_searched_series
.as_mut() .as_mut()
.unwrap(), .unwrap_or(&mut StatefulTable::default()),
AddSeriesSearchResult 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> { 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() { match self.app.data.sonarr_data.selected_block.get_active_block() {
ActiveSonarrBlock::AddSeriesConfirmPrompt => { ActiveSonarrBlock::AddSeriesConfirmPrompt => {
if self.app.data.sonarr_data.prompt_confirm { 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(); self.app.pop_navigation_stack();
@@ -534,7 +618,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
&& key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
} }
@@ -1,16 +1,22 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::library::add_series_handler::AddSeriesHandler; 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::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_data::sonarr::sonarr_data::{ActiveSonarrBlock, ADD_SERIES_BLOCKS};
use crate::models::servarr_models::RootFolder; 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; use crate::models::HorizontallyScrollableText;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -366,6 +372,7 @@ mod tests {
} }
mod test_handle_home_end { mod test_handle_home_end {
use pretty_assertions::assert_eq;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -763,6 +770,7 @@ mod tests {
} }
mod test_handle_left_right_action { mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::models::servarr_data::sonarr::modals::AddSeriesModal; use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
@@ -1109,10 +1117,67 @@ mod tests {
#[test] #[test]
fn test_add_series_confirm_prompt_prompt_confirmation_submit() { fn test_add_series_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); 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::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.data.sonarr_data.prompt_confirm = true; 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.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS);
app app
.data .data
@@ -1131,9 +1196,9 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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] #[rstest]
@@ -1440,6 +1505,7 @@ mod tests {
}, },
network::sonarr_network::SonarrEvent, network::sonarr_network::SonarrEvent,
}; };
use pretty_assertions::assert_eq;
#[test] #[test]
fn test_add_series_search_input_backspace() { fn test_add_series_search_input_backspace() {
@@ -1553,7 +1619,64 @@ mod tests {
#[test] #[test]
fn test_add_series_confirm_prompt_prompt_confirmation_confirm() { fn test_add_series_confirm_prompt_prompt_confirmation_confirm() {
let mut app = App::default(); 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::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.data.sonarr_data.selected_block = BlockSelectionState::new(ADD_SERIES_SELECTION_BLOCKS); 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.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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] #[test]
fn test_add_series_handler_is_not_ready_when_loading() { fn test_add_series_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -1,9 +1,10 @@
use crate::models::sonarr_models::DeleteSeriesParams;
use crate::network::sonarr_network::SonarrEvent;
use crate::{ use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App}, app::{key_binding::DEFAULT_KEYBINDINGS, App},
event::Key, event::Key,
handlers::{handle_prompt_toggle, KeyEventHandler}, handlers::{handle_prompt_toggle, KeyEventHandler},
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}, models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS},
network::sonarr_network::SonarrEvent,
}; };
#[cfg(test)] #[cfg(test)]
@@ -17,6 +18,21 @@ pub(super) struct DeleteSeriesHandler<'a, 'b> {
_context: Option<ActiveSonarrBlock>, _context: Option<ActiveSonarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<'a, 'b> {
fn accepts(active_block: ActiveSonarrBlock) -> bool { fn accepts(active_block: ActiveSonarrBlock) -> bool {
DELETE_SERIES_BLOCKS.contains(&active_block) 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() { match self.app.data.sonarr_data.selected_block.get_active_block() {
ActiveSonarrBlock::DeleteSeriesConfirmPrompt => { ActiveSonarrBlock::DeleteSeriesConfirmPrompt => {
if self.app.data.sonarr_data.prompt_confirm { 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; self.app.should_refresh = true;
} else { } else {
self.app.data.sonarr_data.reset_delete_series_preferences(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,13 +1,16 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::library::delete_series_handler::DeleteSeriesHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}; 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 { mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -132,6 +135,12 @@ mod tests {
app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.prompt_confirm = true;
app.data.sonarr_data.delete_series_files = true; app.data.sonarr_data.delete_series_files = true;
app.data.sonarr_data.add_list_exclusion = 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 = app.data.sonarr_data.selected_block =
BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS);
app app
@@ -151,12 +160,12 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteSeries(None)) Some(SonarrEvent::DeleteSeries(expected_delete_series_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert!(app.data.sonarr_data.delete_series_files); assert!(!app.data.sonarr_data.delete_series_files);
assert!(app.data.sonarr_data.add_list_exclusion); assert!(!app.data.sonarr_data.add_list_exclusion);
} }
#[test] #[test]
@@ -222,6 +231,7 @@ mod tests {
mod test_handle_esc { mod test_handle_esc {
use super::*; use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key; const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
@@ -258,6 +268,7 @@ mod tests {
}, },
network::sonarr_network::SonarrEvent, network::sonarr_network::SonarrEvent,
}; };
use pretty_assertions::assert_eq;
use super::*; use super::*;
@@ -268,6 +279,12 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::DeleteSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteSeriesPrompt.into());
app.data.sonarr_data.delete_series_files = true; app.data.sonarr_data.delete_series_files = true;
app.data.sonarr_data.add_list_exclusion = 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 = app.data.sonarr_data.selected_block =
BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(DELETE_SERIES_SELECTION_BLOCKS);
app app
@@ -287,12 +304,12 @@ mod tests {
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteSeries(None)) Some(SonarrEvent::DeleteSeries(expected_delete_series_params))
); );
assert!(app.should_refresh); assert!(app.should_refresh);
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert!(app.data.sonarr_data.delete_series_files); assert!(!app.data.sonarr_data.delete_series_files);
assert!(app.data.sonarr_data.add_list_exclusion); 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] #[test]
fn test_delete_series_handler_not_ready_when_loading() { fn test_delete_series_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -2,7 +2,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; 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::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS};
use crate::models::sonarr_models::EditSeriesParams;
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
@@ -18,6 +20,78 @@ pub(super) struct EditSeriesHandler<'a, 'b> {
context: Option<ActiveSonarrBlock>, context: Option<ActiveSonarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a, 'b> {
fn accepts(active_block: ActiveSonarrBlock) -> bool { fn accepts(active_block: ActiveSonarrBlock) -> bool {
EDIT_SERIES_BLOCKS.contains(&active_block) 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() { match self.app.data.sonarr_data.selected_block.get_active_block() {
ActiveSonarrBlock::EditSeriesConfirmPrompt => { ActiveSonarrBlock::EditSeriesConfirmPrompt => {
if self.app.data.sonarr_data.prompt_confirm { 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; self.app.should_refresh = true;
} }
@@ -392,7 +467,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
&& key == DEFAULT_KEYBINDINGS.confirm.key && key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; 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.should_refresh = true;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -1,16 +1,18 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::library::edit_series_handler::EditSeriesHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::modals::EditSeriesModal; use crate::models::servarr_data::sonarr::modals::EditSeriesModal;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_SERIES_BLOCKS}; 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 { mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -243,6 +245,7 @@ mod tests {
} }
mod test_handle_home_end { mod test_handle_home_end {
use pretty_assertions::assert_eq;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -531,6 +534,7 @@ mod tests {
} }
mod test_handle_left_right_action { mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::models::servarr_data::sonarr::modals::EditSeriesModal; use crate::models::servarr_data::sonarr::modals::EditSeriesModal;
@@ -770,7 +774,43 @@ mod tests {
#[test] #[test]
fn test_edit_series_confirm_prompt_prompt_confirmation_submit() { fn test_edit_series_confirm_prompt_prompt_confirmation_submit() {
let mut app = App::default(); 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::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into());
app.data.sonarr_data.prompt_confirm = true; 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.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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); assert!(app.should_refresh);
} }
@@ -1135,6 +1175,7 @@ mod tests {
}, },
network::sonarr_network::SonarrEvent, network::sonarr_network::SonarrEvent,
}; };
use pretty_assertions::assert_eq;
#[test] #[test]
fn test_edit_series_path_input_backspace() { fn test_edit_series_path_input_backspace() {
@@ -1253,7 +1294,43 @@ mod tests {
#[test] #[test]
fn test_edit_series_confirm_prompt_prompt_confirm() { fn test_edit_series_confirm_prompt_prompt_confirm() {
let mut app = App::default(); 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::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into());
app.data.sonarr_data.selected_block = BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); 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.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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); 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] #[test]
fn test_edit_series_handler_is_not_ready_when_loading() { fn test_edit_series_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -53,6 +53,19 @@ impl<'a, 'b> EpisodeDetailsHandler<'a, 'b> {
.episode_releases, .episode_releases,
SonarrRelease 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> { 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 => { ActiveSonarrBlock::AutomaticallySearchEpisodePrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticEpisodeSearch(None)); SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
);
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -308,8 +322,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
if key == DEFAULT_KEYBINDINGS.confirm.key => if key == DEFAULT_KEYBINDINGS.confirm.key =>
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticEpisodeSearch(None)); SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
);
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -3,14 +3,16 @@ mod tests {
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler; 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::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::sonarr_test_utils::utils::create_test_sonarr_data;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS,
}; };
use crate::models::sonarr_models::SonarrReleaseDownloadBody; use crate::models::sonarr_models::SonarrReleaseDownloadBody;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -206,7 +208,7 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt, ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
SonarrEvent::TriggerAutomaticEpisodeSearch(None) SonarrEvent::TriggerAutomaticEpisodeSearch(1)
)] )]
fn test_episode_details_prompt_confirm_submit( fn test_episode_details_prompt_confirm_submit(
#[case] prompt_block: ActiveSonarrBlock, #[case] prompt_block: ActiveSonarrBlock,
@@ -221,6 +223,14 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data = create_test_sonarr_data(); 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.data.sonarr_data.prompt_confirm = true;
app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(active_sonarr_block.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
@@ -543,6 +553,14 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data = create_test_sonarr_data(); 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.data.sonarr_data.prompt_confirm = true;
app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(active_sonarr_block.into());
app.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.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.get_current_route(), active_sonarr_block.into());
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, 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] #[test]
fn test_episode_details_handler_is_not_ready_when_loading() { fn test_episode_details_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -65,6 +65,44 @@ impl<'a, 'b> SeasonDetailsHandler<'a, 'b> {
.season_releases, .season_releases,
SonarrRelease 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> { 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()), .push_navigation_stack(ActiveSonarrBlock::SeasonHistoryDetails.into()),
ActiveSonarrBlock::DeleteEpisodeFilePrompt => { ActiveSonarrBlock::DeleteEpisodeFilePrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
Some(SonarrEvent::DeleteEpisodeFile(None)); self.extract_episode_file_id(),
));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => { ActiveSonarrBlock::AutomaticallySearchSeasonPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticSeasonSearch(None)); SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
);
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -340,8 +380,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeasonDetails if self.key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => { 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 = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::ToggleEpisodeMonitoring(None)); SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()),
);
self self
.app .app
@@ -366,15 +407,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
if key == DEFAULT_KEYBINDINGS.confirm.key => if key == DEFAULT_KEYBINDINGS.confirm.key =>
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticSeasonSearch(None)); SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
);
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
Some(SonarrEvent::DeleteEpisodeFile(None)); self.extract_episode_file_id(),
));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -5,6 +5,7 @@ mod tests {
use crate::handlers::sonarr_handlers::library::season_details_handler::{ use crate::handlers::sonarr_handlers::library::season_details_handler::{
releases_sorting_options, SeasonDetailsHandler, releases_sorting_options, SeasonDetailsHandler,
}; };
use crate::handlers::sonarr_handlers::sonarr_handler_test_utils::utils::episode;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::modals::SeasonDetailsModal; use crate::models::servarr_data::sonarr::modals::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::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::servarr_models::{Language, Quality, QualityWrapper};
use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody}; use crate::models::sonarr_models::{SonarrRelease, SonarrReleaseDownloadBody};
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use serde_json::Number; use serde_json::Number;
use std::cmp::Ordering; use std::cmp::Ordering;
@@ -275,11 +276,11 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
SonarrEvent::TriggerAutomaticSeasonSearch(None) SonarrEvent::TriggerAutomaticSeasonSearch((0, 0))
)] )]
#[case( #[case(
ActiveSonarrBlock::DeleteEpisodeFilePrompt, ActiveSonarrBlock::DeleteEpisodeFilePrompt,
SonarrEvent::DeleteEpisodeFile(None) SonarrEvent::DeleteEpisodeFile(0)
)] )]
fn test_season_details_prompt_confirm_submit( fn test_season_details_prompt_confirm_submit(
#[case] prompt_block: ActiveSonarrBlock, #[case] prompt_block: ActiveSonarrBlock,
@@ -550,6 +551,14 @@ mod tests {
fn test_toggle_monitoring_key() { fn test_toggle_monitoring_key() {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data = create_test_sonarr_data(); 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.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into());
app.is_routing = false; app.is_routing = false;
@@ -569,7 +578,7 @@ mod tests {
assert!(app.is_routing); assert!(app.is_routing);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::ToggleEpisodeMonitoring(None)) Some(SonarrEvent::ToggleEpisodeMonitoring(1))
); );
} }
@@ -704,11 +713,11 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
SonarrEvent::TriggerAutomaticSeasonSearch(None) SonarrEvent::TriggerAutomaticSeasonSearch((0, 0))
)] )]
#[case( #[case(
ActiveSonarrBlock::DeleteEpisodeFilePrompt, ActiveSonarrBlock::DeleteEpisodeFilePrompt,
SonarrEvent::DeleteEpisodeFile(None) SonarrEvent::DeleteEpisodeFile(0)
)] )]
fn test_season_details_prompt_confirm_confirm_key( fn test_season_details_prompt_confirm_confirm_key(
#[case] prompt_block: ActiveSonarrBlock, #[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] #[test]
fn test_season_details_handler_is_not_ready_when_loading() { fn test_season_details_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -37,6 +37,23 @@ impl<'a, 'b> SeriesDetailsHandler<'a, 'b> {
.expect("Series history is undefined"), .expect("Series history is undefined"),
SonarrHistoryItem 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> { 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 => { ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticSeriesSearch(None)); SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()),
);
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -177,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => { ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::UpdateAndScanSeries(None)); Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id()));
} }
self.app.pop_navigation_stack(); 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 => { _ if key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::ToggleSeasonMonitoring(None)); SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()),
);
self self
.app .app
@@ -299,8 +318,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => { ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action = Some(
Some(SonarrEvent::TriggerAutomaticSeriesSearch(None)); SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()),
);
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
@@ -308,7 +328,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => { ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::UpdateAndScanSeries(None)); Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id()));
} }
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -4,6 +4,7 @@ mod tests {
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, SERIES_DETAILS_BLOCKS, ActiveSonarrBlock, SERIES_DETAILS_BLOCKS,
@@ -11,6 +12,7 @@ mod tests {
use crate::models::sonarr_models::Season; use crate::models::sonarr_models::Season;
use crate::models::sonarr_models::SonarrHistoryItem; use crate::models::sonarr_models::SonarrHistoryItem;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@@ -181,11 +183,11 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
SonarrEvent::TriggerAutomaticSeriesSearch(None) SonarrEvent::TriggerAutomaticSeriesSearch(1)
)] )]
#[case( #[case(
ActiveSonarrBlock::UpdateAndScanSeriesPrompt, ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
SonarrEvent::UpdateAndScanSeries(None) SonarrEvent::UpdateAndScanSeries(1)
)] )]
fn test_series_details_prompt_confirm_submit( fn test_series_details_prompt_confirm_submit(
#[case] prompt_block: ActiveSonarrBlock, #[case] prompt_block: ActiveSonarrBlock,
@@ -193,6 +195,7 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data.prompt_confirm = true; 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(ActiveSonarrBlock::SeriesDetails.into());
app.push_navigation_stack(prompt_block.into()); app.push_navigation_stack(prompt_block.into());
@@ -398,7 +401,7 @@ mod tests {
assert!(app.is_routing); assert!(app.is_routing);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::ToggleSeasonMonitoring(None)) Some(SonarrEvent::ToggleSeasonMonitoring((0, 0)))
); );
} }
@@ -565,11 +568,11 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
SonarrEvent::TriggerAutomaticSeriesSearch(None) SonarrEvent::TriggerAutomaticSeriesSearch(1)
)] )]
#[case( #[case(
ActiveSonarrBlock::UpdateAndScanSeriesPrompt, ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
SonarrEvent::UpdateAndScanSeries(None) SonarrEvent::UpdateAndScanSeries(1)
)] )]
fn test_series_details_prompt_confirm_confirm_key( fn test_series_details_prompt_confirm_confirm_key(
#[case] prompt_block: ActiveSonarrBlock, #[case] prompt_block: ActiveSonarrBlock,
@@ -579,6 +582,7 @@ mod tests {
) { ) {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data.prompt_confirm = true; 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(active_sonarr_block.into());
app.push_navigation_stack(prompt_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] #[test]
fn test_series_details_handler_is_not_ready_when_loading() { fn test_series_details_handler_is_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -5,7 +5,7 @@ use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; 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_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::models::HorizontallyScrollableText;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; 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, self.app.data.sonarr_data.root_folders,
RootFolder 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> { 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 => { ActiveSonarrBlock::DeleteRootFolderPrompt => {
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
self.app.data.sonarr_data.prompt_confirm_action = 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(); self.app.pop_navigation_stack();
@@ -140,7 +164,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
.text .text
.is_empty() => .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.data.sonarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false; self.app.should_ignore_quit_key = false;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -192,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
if key == DEFAULT_KEYBINDINGS.confirm.key { if key == DEFAULT_KEYBINDINGS.confirm.key {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = 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(); self.app.pop_navigation_stack();
} }
@@ -1,14 +1,16 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::root_folders::RootFoldersHandler; 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::handlers::KeyEventHandler;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_FOLDERS_BLOCKS}; 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::models::HorizontallyScrollableText;
mod test_handle_home_end { mod test_handle_home_end {
@@ -257,6 +259,9 @@ mod tests {
#[test] #[test]
fn test_add_root_folder_prompt_confirm_submit() { fn test_add_root_folder_prompt_confirm_submit() {
let mut app = App::default(); let mut app = App::default();
let expected_add_root_folder_body = AddRootFolderBody {
path: "Test".to_owned(),
};
app app
.data .data
.sonarr_data .sonarr_data
@@ -280,12 +285,13 @@ mod tests {
assert!(!app.should_ignore_quit_key); assert!(!app.should_ignore_quit_key);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::AddRootFolder(None)) Some(SonarrEvent::AddRootFolder(expected_add_root_folder_body))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::RootFolders.into() ActiveSonarrBlock::RootFolders.into()
); );
assert!(app.data.sonarr_data.edit_root_folder.is_none());
} }
#[test] #[test]
@@ -321,7 +327,7 @@ mod tests {
.data .data
.sonarr_data .sonarr_data
.root_folders .root_folders
.set_items(vec![RootFolder::default()]); .set_items(vec![root_folder()]);
app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into());
@@ -337,7 +343,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteRootFolder(None)) Some(SonarrEvent::DeleteRootFolder(1))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -614,7 +620,7 @@ mod tests {
.data .data
.sonarr_data .sonarr_data
.root_folders .root_folders
.set_items(vec![RootFolder::default()]); .set_items(vec![root_folder()]);
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::DeleteRootFolderPrompt.into());
@@ -629,7 +635,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::DeleteRootFolder(None)) Some(SonarrEvent::DeleteRootFolder(1))
); );
assert_eq!( assert_eq!(
app.get_current_route(), 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] #[test]
fn test_root_folders_handler_not_ready_when_loading() { fn test_root_folders_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
@@ -1,6 +1,17 @@
#[cfg(test)] #[cfg(test)]
#[macro_use] #[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_export]
macro_rules! test_edit_series_key { 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<String> {
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,
}
}
} }
@@ -3,6 +3,7 @@ use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS}; 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::stateful_list::StatefulList;
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
@@ -18,6 +19,18 @@ pub(super) struct SystemDetailsHandler<'a, 'b> {
_context: Option<ActiveSonarrBlock>, _context: Option<ActiveSonarrBlock>,
} }
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> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler<'a, 'b> {
fn accepts(active_block: ActiveSonarrBlock) -> bool { fn accepts(active_block: ActiveSonarrBlock) -> bool {
SYSTEM_DETAILS_BLOCKS.contains(&active_block) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
@@ -137,7 +150,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
} }
ActiveSonarrBlock::SystemTaskStartConfirmPrompt => { ActiveSonarrBlock::SystemTaskStartConfirmPrompt => {
if self.app.data.sonarr_data.prompt_confirm { 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(); 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.key == DEFAULT_KEYBINDINGS.confirm.key
{ {
self.app.data.sonarr_data.prompt_confirm = true; 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(); self.app.pop_navigation_stack();
} }
} }
@@ -1,6 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use pretty_assertions::{assert_eq, assert_str_eq};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -12,10 +12,11 @@ mod tests {
ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS, ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS,
}; };
use crate::models::servarr_models::QueueEvent; use crate::models::servarr_models::QueueEvent;
use crate::models::sonarr_models::SonarrTask; use crate::models::sonarr_models::{SonarrTask, SonarrTaskName};
use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::models::{HorizontallyScrollableText, ScrollableText};
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use crate::models::{HorizontallyScrollableText, ScrollableText}; use crate::models::{HorizontallyScrollableText, ScrollableText};
@@ -245,6 +246,7 @@ mod tests {
use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end};
use super::*; use super::*;
use pretty_assertions::assert_eq;
test_iterable_home_and_end!( test_iterable_home_and_end!(
test_log_details_home_end, test_log_details_home_end,
@@ -693,6 +695,11 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned());
app.data.sonarr_data.prompt_confirm = true; 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::SystemTasks.into());
app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into());
@@ -707,7 +714,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::StartTask(None)) Some(SonarrEvent::StartTask(SonarrTaskName::default()))
); );
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -848,6 +855,7 @@ mod tests {
} }
mod test_handle_key_char { mod test_handle_key_char {
use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
@@ -914,6 +922,11 @@ mod tests {
let mut app = App::default(); let mut app = App::default();
app.push_navigation_stack(ActiveSonarrBlock::System.into()); app.push_navigation_stack(ActiveSonarrBlock::System.into());
app.data.sonarr_data.updates = ScrollableText::with_string("Test".to_owned()); 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::SystemTasks.into());
app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::SystemTaskStartConfirmPrompt.into());
@@ -928,7 +941,7 @@ mod tests {
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::StartTask(None)) Some(SonarrEvent::StartTask(SonarrTaskName::default()))
); );
assert_eq!( assert_eq!(
app.get_current_route(), 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] #[test]
fn test_system_details_handler_not_ready_when_loading() { fn test_system_details_handler_not_ready_when_loading() {
let mut app = App::default(); let mut app = App::default();
+5 -1
View File
@@ -29,6 +29,8 @@ pub struct AddMovieBody {
pub minimum_availability: String, pub minimum_availability: String,
pub monitored: bool, pub monitored: bool,
pub tags: Vec<i64>, pub tags: Vec<i64>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
pub add_options: AddMovieOptions, pub add_options: AddMovieOptions,
} }
@@ -135,7 +137,7 @@ pub enum CreditType {
Crew, Crew,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct DeleteMovieParams { pub struct DeleteMovieParams {
pub id: i64, pub id: i64,
@@ -188,6 +190,8 @@ pub struct EditMovieParams {
pub quality_profile_id: Option<i64>, pub quality_profile_id: Option<i64>,
pub root_folder_path: Option<String>, pub root_folder_path: Option<String>,
pub tags: Option<Vec<i64>>, pub tags: Option<Vec<i64>>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
pub clear_tags: bool, pub clear_tags: bool,
} }
+3 -1
View File
@@ -11,7 +11,7 @@ use super::HorizontallyScrollableText;
#[path = "servarr_models_tests.rs"] #[path = "servarr_models_tests.rs"]
mod servarr_models_tests; mod servarr_models_tests;
#[derive(Default, Serialize, Debug)] #[derive(Default, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct AddRootFolderBody { pub struct AddRootFolderBody {
pub path: String, pub path: String,
} }
@@ -101,6 +101,8 @@ pub struct EditIndexerParams {
pub api_key: Option<String>, pub api_key: Option<String>,
pub seed_ratio: Option<String>, pub seed_ratio: Option<String>,
pub tags: Option<Vec<i64>>, pub tags: Option<Vec<i64>>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
pub priority: Option<i64>, pub priority: Option<i64>,
pub clear_tags: bool, pub clear_tags: bool,
} }
+5 -1
View File
@@ -34,6 +34,8 @@ pub struct AddSeriesBody {
pub series_type: String, pub series_type: String,
pub season_folder: bool, pub season_folder: bool,
pub tags: Vec<i64>, pub tags: Vec<i64>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
pub add_options: AddSeriesOptions, pub add_options: AddSeriesOptions,
} }
@@ -95,7 +97,7 @@ pub struct BlocklistResponse {
pub records: Vec<BlocklistItem>, pub records: Vec<BlocklistItem>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct DeleteSeriesParams { pub struct DeleteSeriesParams {
pub id: i64, pub id: i64,
@@ -192,6 +194,8 @@ pub struct EditSeriesParams {
pub series_type: Option<SeriesType>, pub series_type: Option<SeriesType>,
pub root_folder_path: Option<String>, pub root_folder_path: Option<String>,
pub tags: Option<Vec<i64>>, pub tags: Option<Vec<i64>>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
pub clear_tags: bool, pub clear_tags: bool,
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff