feat(cli): Support for editing a sonarr series

This commit is contained in:
2024-11-25 16:19:48 -07:00
parent 3af22cceac
commit ee312a21eb
2 changed files with 493 additions and 3 deletions
+111 -1
View File
@@ -9,7 +9,7 @@ use crate::{
cli::{mutex_flags_or_option, CliCommandHandler, Command},
models::{
servarr_models::EditIndexerParams,
sonarr_models::{IndexerSettings, SonarrSerdeable},
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
Serdeable,
},
network::{sonarr_network::SonarrEvent, NetworkTrait},
@@ -148,6 +148,82 @@ pub enum SonarrEditCommand {
#[arg(long, help = "Clear all tags on this indexer", conflicts_with = "tag")]
clear_tags: bool,
},
#[command(
about = "Edit preferences for the specified series",
group(
ArgGroup::new("edit_series")
.args([
"enable_monitoring",
"disable_monitoring",
"enable_season_folders",
"disable_season_folders",
"series_type",
"quality_profile_id",
"language_profile_id",
"root_folder_path",
"tag",
"clear_tags"
]).required(true)
.multiple(true))
)]
Series {
#[arg(
long,
help = "The ID of the series whose settings you want to edit",
required = true
)]
series_id: i64,
#[arg(
long,
help = "Enable monitoring of this series in Sonarr so Sonarr will automatically download this series if it is available",
conflicts_with = "disable_monitoring"
)]
enable_monitoring: bool,
#[arg(
long,
help = "Disable monitoring of this series so Sonarr does not automatically download the series if it is found to be available",
conflicts_with = "enable_monitoring"
)]
disable_monitoring: bool,
#[arg(
long,
help = "The minimum availability to monitor for this film",
value_enum
)]
#[arg(
long,
help = "Enable sorting episodes of this series into season folders",
conflicts_with = "disable_season_folders"
)]
enable_season_folders: bool,
#[arg(
long,
help = "Disable sorting episodes of this series into season folders",
conflicts_with = "enable_season_folders"
)]
disable_season_folders: bool,
#[arg(long, help = "The type of series", value_enum)]
series_type: Option<SeriesType>,
#[arg(long, help = "The ID of the quality profile to use for this series")]
quality_profile_id: Option<i64>,
#[arg(long, help = "The ID of the language profile to use for this series")]
language_profile_id: Option<i64>,
#[arg(
long,
help = "The root folder path where all film data and metadata should live"
)]
root_folder_path: Option<String>,
#[arg(
long,
help = "Tag IDs to tag this series with",
value_parser,
action = ArgAction::Append,
conflicts_with = "clear_tags"
)]
tag: Option<Vec<i64>>,
#[arg(long, help = "Clear all tags on this series", conflicts_with = "tag")]
clear_tags: bool,
},
}
impl From<SonarrEditCommand> for Command {
@@ -246,6 +322,40 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrEditCommand> for SonarrEditCommandH
.await?;
"Indexer updated".to_owned()
}
SonarrEditCommand::Series {
series_id,
enable_monitoring,
disable_monitoring,
enable_season_folders,
disable_season_folders,
series_type,
quality_profile_id,
language_profile_id,
root_folder_path,
tag,
clear_tags,
} => {
let monitored_value = mutex_flags_or_option(enable_monitoring, disable_monitoring);
let season_folders_value =
mutex_flags_or_option(enable_season_folders, disable_season_folders);
let edit_series_params = EditSeriesParams {
series_id,
monitored: monitored_value,
use_season_folders: season_folders_value,
series_type,
quality_profile_id,
language_profile_id,
root_folder_path,
tags: tag,
clear_tags,
};
self
.network
.handle_network_event(SonarrEvent::EditSeries(Some(edit_series_params)).into())
.await?;
"Series Updated".to_owned()
}
};
Ok(result)
+382 -2
View File
@@ -20,7 +20,7 @@ mod tests {
}
mod cli {
use crate::Cli;
use crate::{models::sonarr_models::SeriesType, Cli};
use super::*;
use clap::{error::ErrorKind, CommandFactory, Parser};
@@ -358,6 +358,245 @@ mod tests {
assert_eq!(edit_command, expected_args);
}
}
#[test]
fn test_edit_series_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "series"]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_series_with_series_id_still_requires_arguments() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
]);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_series_monitoring_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--enable-monitoring",
"--disable-monitoring",
]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_series_season_folders_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--enable-season-folders",
"--disable-season-folders",
]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_series_tag_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--tag",
"1",
"--clear-tags",
]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[rstest]
fn test_edit_series_assert_argument_flags_require_args(
#[values(
"--series-type",
"--quality-profile-id",
"--language-profile-id",
"--root-folder-path",
"--tag"
)]
flag: &str,
) {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
flag,
]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_series_series_type_validation() {
let result = Cli::command().try_get_matches_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--series-type",
"test",
]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_series_only_requires_at_least_one_argument_plus_series_id() {
let expected_args = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: false,
disable_monitoring: false,
enable_season_folders: false,
disable_season_folders: false,
series_type: None,
quality_profile_id: None,
language_profile_id: None,
root_folder_path: Some("/nfs/test".to_owned()),
tag: None,
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--root-folder-path",
"/nfs/test",
]);
assert!(result.is_ok());
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
assert_eq!(edit_command, expected_args);
}
}
#[test]
fn test_edit_series_tag_argument_is_repeatable() {
let expected_args = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: false,
disable_monitoring: false,
enable_season_folders: false,
disable_season_folders: false,
series_type: None,
quality_profile_id: None,
language_profile_id: None,
root_folder_path: None,
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--tag",
"1",
"--tag",
"2",
]);
assert!(result.is_ok());
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
assert_eq!(edit_command, expected_args);
}
}
#[test]
fn test_edit_series_all_arguments_defined() {
let expected_args = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: true,
disable_monitoring: false,
enable_season_folders: true,
disable_season_folders: false,
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"sonarr",
"edit",
"series",
"--series-id",
"1",
"--enable-monitoring",
"--enable-season-folders",
"--series-type",
"anime",
"--quality-profile-id",
"1",
"--language-profile-id",
"1",
"--root-folder-path",
"/nfs/test",
"--tag",
"1",
"--tag",
"2",
]);
assert!(result.is_ok());
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command {
assert_eq!(edit_command, expected_args);
}
}
}
mod handler {
@@ -375,7 +614,7 @@ mod tests {
},
models::{
servarr_models::EditIndexerParams,
sonarr_models::{IndexerSettings, SonarrSerdeable},
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
Serdeable,
},
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
@@ -490,5 +729,146 @@ mod tests {
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_edit_series_command() {
let expected_edit_series_params = EditSeriesParams {
series_id: 1,
monitored: Some(true),
use_season_folders: Some(true),
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let edit_series_command = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: true,
disable_monitoring: false,
enable_season_folders: true,
disable_season_folders: false,
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = SonarrEditCommandHandler::with(&app_arc, edit_series_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_edit_series_command_handles_disable_monitoring_flag_properly() {
let expected_edit_series_params = EditSeriesParams {
series_id: 1,
monitored: Some(false),
use_season_folders: Some(false),
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let edit_series_command = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: false,
disable_monitoring: true,
enable_season_folders: false,
disable_season_folders: true,
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = SonarrEditCommandHandler::with(&app_arc, edit_series_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_handle_edit_series_command_no_monitoring_boolean_flags_returns_none_value() {
let expected_edit_series_params = EditSeriesParams {
series_id: 1,
monitored: None,
use_season_folders: None,
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
SonarrEvent::EditSeries(Some(expected_edit_series_params)).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::default()));
let edit_series_command = SonarrEditCommand::Series {
series_id: 1,
enable_monitoring: false,
disable_monitoring: false,
enable_season_folders: false,
disable_season_folders: false,
series_type: Some(SeriesType::Anime),
quality_profile_id: Some(1),
language_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = SonarrEditCommandHandler::with(&app_arc, edit_series_command, &mut mock_network)
.handle()
.await;
assert!(result.is_ok());
}
}
}