feat(cli): Support for adding a series to Sonarr
This commit is contained in:
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::radarr_models::{AddMovieBody, AddOptions, MinimumAvailability, Monitor},
|
||||
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
|
||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ pub enum RadarrAddCommand {
|
||||
default_value_t = MinimumAvailability::default()
|
||||
)]
|
||||
minimum_availability: MinimumAvailability,
|
||||
#[arg(long, help = "Should Radarr monitor this film")]
|
||||
#[arg(long, help = "Disable monitoring for this film")]
|
||||
disable_monitoring: bool,
|
||||
#[arg(
|
||||
long,
|
||||
@@ -59,9 +59,9 @@ pub enum RadarrAddCommand {
|
||||
long,
|
||||
help = "What Radarr should monitor",
|
||||
value_enum,
|
||||
default_value_t = Monitor::default()
|
||||
default_value_t = MovieMonitor::default()
|
||||
)]
|
||||
monitor: Monitor,
|
||||
monitor: MovieMonitor,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Tell Radarr to not start a search for this film once it's added to your library"
|
||||
@@ -125,7 +125,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
|
||||
minimum_availability: minimum_availability.to_string(),
|
||||
monitored: !disable_monitoring,
|
||||
tags,
|
||||
add_options: AddOptions {
|
||||
add_options: AddMovieOptions {
|
||||
monitor: monitor.to_string(),
|
||||
search_for_movie: !no_search_for_movie,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ mod tests {
|
||||
radarr::{add_command_handler::RadarrAddCommand, RadarrCommand},
|
||||
Command,
|
||||
},
|
||||
models::radarr_models::{MinimumAvailability, Monitor},
|
||||
models::radarr_models::{MinimumAvailability, MovieMonitor},
|
||||
Cli,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -112,6 +112,8 @@ mod tests {
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--tmdb-id",
|
||||
"1",
|
||||
flag,
|
||||
]);
|
||||
|
||||
@@ -188,7 +190,7 @@ mod tests {
|
||||
minimum_availability: MinimumAvailability::default(),
|
||||
disable_monitoring: false,
|
||||
tag: vec![],
|
||||
monitor: Monitor::default(),
|
||||
monitor: MovieMonitor::default(),
|
||||
no_search_for_movie: false,
|
||||
};
|
||||
|
||||
@@ -220,7 +222,7 @@ mod tests {
|
||||
minimum_availability: MinimumAvailability::default(),
|
||||
disable_monitoring: false,
|
||||
tag: vec![1, 2],
|
||||
monitor: Monitor::default(),
|
||||
monitor: MovieMonitor::default(),
|
||||
no_search_for_movie: false,
|
||||
};
|
||||
|
||||
@@ -256,7 +258,7 @@ mod tests {
|
||||
minimum_availability: MinimumAvailability::Released,
|
||||
disable_monitoring: true,
|
||||
tag: vec![1, 2],
|
||||
monitor: Monitor::MovieAndCollection,
|
||||
monitor: MovieMonitor::MovieAndCollection,
|
||||
no_search_for_movie: true,
|
||||
};
|
||||
|
||||
@@ -357,7 +359,7 @@ mod tests {
|
||||
app::App,
|
||||
cli::{radarr::add_command_handler::RadarrAddCommandHandler, CliCommandHandler},
|
||||
models::{
|
||||
radarr_models::{AddMovieBody, AddOptions, RadarrSerdeable},
|
||||
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
|
||||
Serdeable,
|
||||
},
|
||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
@@ -379,7 +381,7 @@ mod tests {
|
||||
minimum_availability: "released".to_owned(),
|
||||
monitored: false,
|
||||
tags: vec![1, 2],
|
||||
add_options: AddOptions {
|
||||
add_options: AddMovieOptions {
|
||||
monitor: "movieAndCollection".to_owned(),
|
||||
search_for_movie: false,
|
||||
},
|
||||
@@ -404,7 +406,7 @@ mod tests {
|
||||
minimum_availability: MinimumAvailability::Released,
|
||||
disable_monitoring: true,
|
||||
tag: vec![1, 2],
|
||||
monitor: Monitor::MovieAndCollection,
|
||||
monitor: MovieMonitor::MovieAndCollection,
|
||||
no_search_for_movie: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use clap::{ArgAction, Subcommand};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{CliCommandHandler, Command},
|
||||
models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType},
|
||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
||||
};
|
||||
|
||||
@@ -18,6 +19,63 @@ mod add_command_handler_tests;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||
pub enum SonarrAddCommand {
|
||||
#[command(about = "Add a new series to your Sonarr library")]
|
||||
Series {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The TVDB ID of the series you wish to add to your library",
|
||||
required = true
|
||||
)]
|
||||
tvdb_id: i64,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The root folder path where all series data and metadata should live",
|
||||
required = true
|
||||
)]
|
||||
root_folder_path: String,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The ID of the quality profile to use for this series",
|
||||
required = true
|
||||
)]
|
||||
quality_profile_id: i64,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The ID of the language profile to use for this series",
|
||||
required = true
|
||||
)]
|
||||
language_profile_id: i64,
|
||||
#[arg(
|
||||
long,
|
||||
help = "The type of series",
|
||||
value_enum,
|
||||
default_value_t = SeriesType::default()
|
||||
)]
|
||||
series_type: SeriesType,
|
||||
#[arg(long, help = "Disable monitoring for this series")]
|
||||
disable_monitoring: bool,
|
||||
#[arg(long, help = "Don't use season folders for this series")]
|
||||
disable_season_folders: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Tag IDs to tag the series with",
|
||||
value_parser,
|
||||
action = ArgAction::Append
|
||||
)]
|
||||
tag: Vec<i64>,
|
||||
#[arg(
|
||||
long,
|
||||
help = "What Sonarr should monitor",
|
||||
value_enum,
|
||||
default_value_t = SeriesMonitor::default()
|
||||
)]
|
||||
monitor: SeriesMonitor,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Tell Sonarr to not start a search for this series once it's added to your library"
|
||||
)]
|
||||
no_search_for_series: bool,
|
||||
},
|
||||
#[command(about = "Add a new root folder")]
|
||||
RootFolder {
|
||||
#[arg(long, help = "The path of the new root folder", required = true)]
|
||||
@@ -57,6 +115,40 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
|
||||
|
||||
async fn handle(self) -> Result<String> {
|
||||
let result = match self.command {
|
||||
SonarrAddCommand::Series {
|
||||
tvdb_id,
|
||||
root_folder_path,
|
||||
quality_profile_id,
|
||||
language_profile_id,
|
||||
series_type,
|
||||
disable_monitoring,
|
||||
disable_season_folders,
|
||||
tag: tags,
|
||||
monitor,
|
||||
no_search_for_series,
|
||||
} => {
|
||||
let body = AddSeriesBody {
|
||||
tvdb_id,
|
||||
title: String::new(),
|
||||
monitored: !disable_monitoring,
|
||||
root_folder_path,
|
||||
quality_profile_id,
|
||||
language_profile_id,
|
||||
series_type: series_type.to_string(),
|
||||
season_folder: !disable_season_folders,
|
||||
tags,
|
||||
add_options: AddSeriesOptions {
|
||||
monitor: monitor.to_string(),
|
||||
search_for_cutoff_unmet_episodes: !no_search_for_series,
|
||||
search_for_missing_episodes: !no_search_for_series,
|
||||
},
|
||||
};
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(SonarrEvent::AddSeries(Some(body)).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
SonarrAddCommand::RootFolder { root_folder_path } => {
|
||||
let resp = self
|
||||
.network
|
||||
|
||||
@@ -23,8 +23,11 @@ mod tests {
|
||||
}
|
||||
|
||||
mod cli {
|
||||
use crate::models::sonarr_models::{SeriesMonitor, SeriesType};
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_add_root_folder_requires_arguments() {
|
||||
@@ -60,6 +63,318 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_tvdb_id() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_root_folder_path() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_quality_profile_id() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--root-folder-path",
|
||||
"test",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_requires_language_profile_id() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--root-folder-path",
|
||||
"test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_add_series_assert_argument_flags_require_args(
|
||||
#[values("--series-type", "--tag", "--monitor")] flag: &str,
|
||||
) {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
flag,
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_all_arguments_satisfied() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_series_type_validation() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--series-type",
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_monitor_validation() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--monitor",
|
||||
"test",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_defaults() {
|
||||
let expected_args = SonarrAddCommand::Series {
|
||||
tvdb_id: 1,
|
||||
root_folder_path: "/test".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
language_profile_id: 1,
|
||||
series_type: SeriesType::default(),
|
||||
disable_monitoring: false,
|
||||
disable_season_folders: false,
|
||||
tag: vec![],
|
||||
monitor: SeriesMonitor::default(),
|
||||
no_search_for_series: false,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_tags_is_repeatable() {
|
||||
let expected_args = SonarrAddCommand::Series {
|
||||
tvdb_id: 1,
|
||||
root_folder_path: "/test".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
language_profile_id: 1,
|
||||
series_type: SeriesType::default(),
|
||||
disable_monitoring: false,
|
||||
disable_season_folders: false,
|
||||
tag: vec![1, 2],
|
||||
monitor: SeriesMonitor::default(),
|
||||
no_search_for_series: false,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--tag",
|
||||
"1",
|
||||
"--tag",
|
||||
"2",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_series_all_args_defined() {
|
||||
let expected_args = SonarrAddCommand::Series {
|
||||
tvdb_id: 1,
|
||||
root_folder_path: "/test".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
language_profile_id: 1,
|
||||
series_type: SeriesType::Anime,
|
||||
disable_monitoring: true,
|
||||
disable_season_folders: true,
|
||||
tag: vec![1, 2],
|
||||
monitor: SeriesMonitor::Future,
|
||||
no_search_for_series: true,
|
||||
};
|
||||
|
||||
let result = Cli::try_parse_from([
|
||||
"managarr",
|
||||
"sonarr",
|
||||
"add",
|
||||
"series",
|
||||
"--root-folder-path",
|
||||
"/test",
|
||||
"--quality-profile-id",
|
||||
"1",
|
||||
"--language-profile-id",
|
||||
"1",
|
||||
"--series-type",
|
||||
"anime",
|
||||
"--disable-monitoring",
|
||||
"--disable-season-folders",
|
||||
"--tvdb-id",
|
||||
"1",
|
||||
"--tag",
|
||||
"1",
|
||||
"--tag",
|
||||
"2",
|
||||
"--monitor",
|
||||
"future",
|
||||
"--no-search-for-series",
|
||||
]);
|
||||
|
||||
assert!(result.is_ok());
|
||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
||||
assert_eq!(add_command, expected_args);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_tag_requires_arguments() {
|
||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]);
|
||||
@@ -93,7 +408,12 @@ mod tests {
|
||||
use crate::{
|
||||
app::App,
|
||||
cli::{sonarr::add_command_handler::SonarrAddCommandHandler, CliCommandHandler},
|
||||
models::{sonarr_models::SonarrSerdeable, Serdeable},
|
||||
models::{
|
||||
sonarr_models::{
|
||||
AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable,
|
||||
},
|
||||
Serdeable,
|
||||
},
|
||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
||||
};
|
||||
|
||||
@@ -131,6 +451,57 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_series_command() {
|
||||
let expected_add_series_body = AddSeriesBody {
|
||||
tvdb_id: 1,
|
||||
title: String::new(),
|
||||
root_folder_path: "/test".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
language_profile_id: 1,
|
||||
series_type: "anime".to_owned(),
|
||||
monitored: false,
|
||||
tags: vec![1, 2],
|
||||
season_folder: false,
|
||||
add_options: AddSeriesOptions {
|
||||
monitor: "future".to_owned(),
|
||||
search_for_cutoff_unmet_episodes: false,
|
||||
search_for_missing_episodes: false,
|
||||
},
|
||||
};
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
SonarrEvent::AddSeries(Some(expected_add_series_body)).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let add_series_command = SonarrAddCommand::Series {
|
||||
tvdb_id: 1,
|
||||
root_folder_path: "/test".to_owned(),
|
||||
quality_profile_id: 1,
|
||||
language_profile_id: 1,
|
||||
series_type: SeriesType::Anime,
|
||||
disable_monitoring: true,
|
||||
disable_season_folders: true,
|
||||
tag: vec![1, 2],
|
||||
monitor: SeriesMonitor::Future,
|
||||
no_search_for_series: true,
|
||||
};
|
||||
|
||||
let result = SonarrAddCommandHandler::with(&app_arc, add_series_command, &mut mock_network)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_add_tag_command() {
|
||||
let expected_tag_name = "test".to_owned();
|
||||
|
||||
Reference in New Issue
Block a user