feat: Support for toggling the monitoring of a given artist via the CLI and TUI
This commit is contained in:
@@ -7,7 +7,11 @@ use crate::models::Route;
|
||||
#[path = "lidarr_context_clues_tests.rs"]
|
||||
mod lidarr_context_clues_tests;
|
||||
|
||||
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||
),
|
||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||
|
||||
@@ -13,6 +13,13 @@ mod tests {
|
||||
fn test_artists_context_clues() {
|
||||
let mut artists_context_clues_iter = ARTISTS_CONTEXT_CLUES.iter();
|
||||
|
||||
assert_some_eq_x!(
|
||||
artists_context_clues_iter.next(),
|
||||
&(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||
)
|
||||
);
|
||||
assert_some_eq_x!(
|
||||
artists_context_clues_iter.next(),
|
||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||
|
||||
@@ -19,6 +19,8 @@ mod tests {
|
||||
|
||||
mod cli {
|
||||
use super::*;
|
||||
use clap::error::ErrorKind;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_list_artists_has_no_arg_requirements() {
|
||||
@@ -40,6 +42,31 @@ mod tests {
|
||||
|
||||
assert_err!(&result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_artist_monitoring_requires_artist_id() {
|
||||
let result =
|
||||
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-artist-monitoring"]);
|
||||
|
||||
assert_err!(&result);
|
||||
assert_eq!(
|
||||
result.unwrap_err().kind(),
|
||||
ErrorKind::MissingRequiredArgument
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_artist_monitoring_requirements_satisfied() {
|
||||
let result = Cli::command().try_get_matches_from([
|
||||
"managarr",
|
||||
"lidarr",
|
||||
"toggle-artist-monitoring",
|
||||
"--artist-id",
|
||||
"1",
|
||||
]);
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
|
||||
mod handler {
|
||||
@@ -119,5 +146,33 @@ mod tests {
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_toggle_artist_monitoring_command() {
|
||||
let mut mock_network = MockNetworkTrait::new();
|
||||
mock_network
|
||||
.expect_handle_network_event()
|
||||
.with(eq::<NetworkEvent>(
|
||||
LidarrEvent::ToggleArtistMonitoring(1).into(),
|
||||
))
|
||||
.times(1)
|
||||
.returning(|_| {
|
||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||
json!({"testResponse": "response"}),
|
||||
)))
|
||||
});
|
||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||
let toggle_artist_monitoring_command = LidarrCommand::ToggleArtistMonitoring { artist_id: 1 };
|
||||
|
||||
let result = LidarrCliHandler::with(
|
||||
&app_arc,
|
||||
toggle_artist_monitoring_command,
|
||||
&mut mock_network,
|
||||
)
|
||||
.handle()
|
||||
.await;
|
||||
|
||||
assert_ok!(&result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -1,11 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use clap::{Subcommand, arg};
|
||||
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
|
||||
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::{app::App, network::NetworkTrait};
|
||||
|
||||
use super::{CliCommandHandler, Command};
|
||||
@@ -29,6 +30,17 @@ pub enum LidarrCommand {
|
||||
about = "Commands to list attributes from your Lidarr instance"
|
||||
)]
|
||||
List(LidarrListCommand),
|
||||
#[command(
|
||||
about = "Toggle monitoring for the specified artist corresponding to the given artist ID"
|
||||
)]
|
||||
ToggleArtistMonitoring {
|
||||
#[arg(
|
||||
long,
|
||||
help = "The Lidarr ID of the artist to toggle monitoring on",
|
||||
required = true
|
||||
)]
|
||||
artist_id: i64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<LidarrCommand> for Command {
|
||||
@@ -68,6 +80,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
||||
.handle()
|
||||
.await?
|
||||
}
|
||||
LidarrCommand::ToggleArtistMonitoring { artist_id } => {
|
||||
let resp = self
|
||||
.network
|
||||
.handle_network_event(LidarrEvent::ToggleArtistMonitoring(artist_id).into())
|
||||
.await?;
|
||||
serde_json::to_string_pretty(&resp)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
|
||||
@@ -6,10 +6,14 @@ mod tests {
|
||||
use serde_json::Number;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::assert_modal_absent;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LIBRARY_BLOCKS};
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
|
||||
#[test]
|
||||
fn test_library_handler_accepts() {
|
||||
@@ -214,6 +218,55 @@ mod tests {
|
||||
assert_str_eq!(sort_option.name, "Tags");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_monitoring_key() {
|
||||
let mut app = App::test_default();
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.artists
|
||||
.set_items(vec![Artist::default()]);
|
||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||
app.is_routing = false;
|
||||
|
||||
LibraryHandler::new(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::Artists,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||
assert!(app.data.lidarr_data.prompt_confirm);
|
||||
assert!(app.is_routing);
|
||||
assert_some_eq_x!(
|
||||
&app.data.lidarr_data.prompt_confirm_action,
|
||||
&LidarrEvent::ToggleArtistMonitoring(0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_toggle_monitoring_key_no_op_when_not_ready() {
|
||||
let mut app = App::test_default();
|
||||
app.is_loading = true;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||
app.is_routing = false;
|
||||
|
||||
LibraryHandler::new(
|
||||
DEFAULT_KEYBINDINGS.toggle_monitoring.key,
|
||||
&mut app,
|
||||
ActiveLidarrBlock::Artists,
|
||||
None,
|
||||
)
|
||||
.handle();
|
||||
|
||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||
assert_modal_absent!(app.data.lidarr_data.prompt_confirm_action);
|
||||
assert!(!app.is_routing);
|
||||
}
|
||||
|
||||
fn artists_vec() -> Vec<Artist> {
|
||||
vec![
|
||||
Artist {
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
},
|
||||
stateful_table::SortOption,
|
||||
},
|
||||
network::lidarr_network::LidarrEvent,
|
||||
};
|
||||
|
||||
use super::handle_change_tab_left_right_keys;
|
||||
@@ -31,6 +32,12 @@ pub(super) struct LibraryHandler<'a, 'b> {
|
||||
_context: Option<ActiveLidarrBlock>,
|
||||
}
|
||||
|
||||
impl LibraryHandler<'_, '_> {
|
||||
fn extract_artist_id(&self) -> i64 {
|
||||
self.app.data.lidarr_data.artists.current_selection().id
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
let artists_table_handling_config = TableHandlingConfig::new(ActiveLidarrBlock::Artists.into())
|
||||
@@ -114,9 +121,24 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
let key = self.key;
|
||||
if self.active_lidarr_block == ActiveLidarrBlock::Artists && matches_key!(refresh, key) {
|
||||
if self.active_lidarr_block == ActiveLidarrBlock::Artists {
|
||||
match key {
|
||||
_ if matches_key!(toggle_monitoring, key) => {
|
||||
self.app.data.lidarr_data.prompt_confirm = true;
|
||||
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||
LidarrEvent::ToggleArtistMonitoring(self.extract_artist_id()),
|
||||
);
|
||||
|
||||
self
|
||||
.app
|
||||
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||
}
|
||||
_ if matches_key!(refresh, key) => {
|
||||
self.app.should_refresh = true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn app_mut(&mut self) -> &mut App<'b> {
|
||||
|
||||
@@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Number, Value};
|
||||
use strum::{Display, EnumIter};
|
||||
|
||||
use super::{HorizontallyScrollableText, Serdeable};
|
||||
use super::{
|
||||
HorizontallyScrollableText, Serdeable,
|
||||
servarr_models::{DiskSpace, QualityProfile, RootFolder, Tag},
|
||||
};
|
||||
use crate::serde_enum_from;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -29,6 +32,7 @@ pub struct Artist {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub metadata_profile_id: i64,
|
||||
pub monitored: bool,
|
||||
pub monitor_new_items: NewItemMonitorType,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<Number>,
|
||||
pub added: DateTime<Utc>,
|
||||
@@ -94,6 +98,31 @@ impl From<(&i64, &String)> for MetadataProfile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum NewItemMonitorType {
|
||||
#[default]
|
||||
#[display_style(name = "All Albums")]
|
||||
All,
|
||||
#[display_style(name = "No New Albums")]
|
||||
None,
|
||||
#[display_style(name = "New Albums")]
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
@@ -174,14 +203,15 @@ impl From<LidarrSerdeable> for Serdeable {
|
||||
|
||||
serde_enum_from!(
|
||||
LidarrSerdeable {
|
||||
Artist(Artist),
|
||||
Artists(Vec<Artist>),
|
||||
DiskSpaces(Vec<super::servarr_models::DiskSpace>),
|
||||
DiskSpaces(Vec<DiskSpace>),
|
||||
DownloadsResponse(DownloadsResponse),
|
||||
MetadataProfiles(Vec<MetadataProfile>),
|
||||
QualityProfiles(Vec<super::servarr_models::QualityProfile>),
|
||||
RootFolders(Vec<super::servarr_models::RootFolder>),
|
||||
QualityProfiles(Vec<QualityProfile>),
|
||||
RootFolders(Vec<RootFolder>),
|
||||
SystemStatus(SystemStatus),
|
||||
Tags(Vec<super::servarr_models::Tag>),
|
||||
Tags(Vec<Tag>),
|
||||
Value(Value),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::Utc;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::models::lidarr_models::{
|
||||
DownloadRecord, DownloadStatus, DownloadsResponse, MetadataProfile, NewItemMonitorType,
|
||||
SystemStatus,
|
||||
};
|
||||
use crate::models::servarr_models::{DiskSpace, QualityProfile, RootFolder, Tag};
|
||||
use crate::models::{
|
||||
Serdeable,
|
||||
lidarr_models::{Artist, ArtistStatistics, ArtistStatus, LidarrSerdeable, Ratings},
|
||||
@@ -13,6 +19,20 @@ mod tests {
|
||||
assert_eq!(ArtistStatus::default(), ArtistStatus::Continuing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_item_monitor_type_display() {
|
||||
assert_str_eq!(NewItemMonitorType::All.to_string(), "all");
|
||||
assert_str_eq!(NewItemMonitorType::None.to_string(), "none");
|
||||
assert_str_eq!(NewItemMonitorType::New.to_string(), "new");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_item_monitor_type_to_display_str() {
|
||||
assert_str_eq!(NewItemMonitorType::All.to_display_str(), "All Albums");
|
||||
assert_str_eq!(NewItemMonitorType::None.to_display_str(), "No New Albums");
|
||||
assert_str_eq!(NewItemMonitorType::New.to_display_str(), "New Albums");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from() {
|
||||
let lidarr_serdeable = LidarrSerdeable::Value(json!({}));
|
||||
@@ -65,6 +85,7 @@ mod tests {
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"monitored": true,
|
||||
"monitorNewItems": "all",
|
||||
"genres": ["Rock", "Alternative"],
|
||||
"tags": [1, 2],
|
||||
"added": "2023-01-01T00:00:00Z",
|
||||
@@ -95,6 +116,7 @@ mod tests {
|
||||
assert_eq!(artist.quality_profile_id, 1);
|
||||
assert_eq!(artist.metadata_profile_id, 1);
|
||||
assert!(artist.monitored);
|
||||
assert_eq!(artist.monitor_new_items, NewItemMonitorType::All);
|
||||
assert_eq!(artist.genres, vec!["Rock", "Alternative"]);
|
||||
assert_eq!(artist.tags.len(), 2);
|
||||
assert_some!(&artist.ratings);
|
||||
@@ -184,6 +206,7 @@ mod tests {
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"monitored": false,
|
||||
"monitorNewItems": "all",
|
||||
"genres": [],
|
||||
"tags": [],
|
||||
"added": "2023-01-01T00:00:00Z"
|
||||
@@ -194,7 +217,169 @@ mod tests {
|
||||
assert_none!(&artist.overview);
|
||||
assert_none!(&artist.artist_type);
|
||||
assert_none!(&artist.disambiguation);
|
||||
assert_eq!(artist.monitor_new_items, NewItemMonitorType::All);
|
||||
assert_none!(&artist.ratings);
|
||||
assert_none!(&artist.statistics);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_artist() {
|
||||
let artist = Artist {
|
||||
id: 1,
|
||||
..Artist::default()
|
||||
};
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = artist.clone().into();
|
||||
|
||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::Artist(artist));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_disk_spaces() {
|
||||
let disk_spaces = vec![DiskSpace {
|
||||
free_space: 1,
|
||||
total_space: 1,
|
||||
}];
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = disk_spaces.clone().into();
|
||||
|
||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::DiskSpaces(disk_spaces));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_downloads_response() {
|
||||
let downloads_response = DownloadsResponse {
|
||||
records: vec![DownloadRecord {
|
||||
id: 1,
|
||||
..DownloadRecord::default()
|
||||
}],
|
||||
};
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = downloads_response.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
lidarr_serdeable,
|
||||
LidarrSerdeable::DownloadsResponse(downloads_response)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_metadata_profiles() {
|
||||
let metadata_profiles = vec![MetadataProfile {
|
||||
id: 1,
|
||||
name: "Standard".to_owned(),
|
||||
}];
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = metadata_profiles.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
lidarr_serdeable,
|
||||
LidarrSerdeable::MetadataProfiles(metadata_profiles)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_quality_profiles() {
|
||||
let quality_profiles = vec![QualityProfile {
|
||||
id: 1,
|
||||
name: "Any".to_owned(),
|
||||
}];
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = quality_profiles.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
lidarr_serdeable,
|
||||
LidarrSerdeable::QualityProfiles(quality_profiles)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_root_folders() {
|
||||
let root_folders = vec![RootFolder {
|
||||
id: 1,
|
||||
path: "/music".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 1000000,
|
||||
unmapped_folders: None,
|
||||
}];
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = root_folders.clone().into();
|
||||
|
||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::RootFolders(root_folders));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_system_status() {
|
||||
let system_status = SystemStatus {
|
||||
version: "1.0.0".to_owned(),
|
||||
start_time: Utc::now(),
|
||||
};
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = system_status.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
lidarr_serdeable,
|
||||
LidarrSerdeable::SystemStatus(system_status)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from_tags() {
|
||||
let tags = vec![Tag {
|
||||
id: 1,
|
||||
label: "rock".to_owned(),
|
||||
}];
|
||||
|
||||
let lidarr_serdeable: LidarrSerdeable = tags.clone().into();
|
||||
|
||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::Tags(tags));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_status_display() {
|
||||
assert_str_eq!(ArtistStatus::Continuing.to_string(), "continuing");
|
||||
assert_str_eq!(ArtistStatus::Ended.to_string(), "ended");
|
||||
assert_str_eq!(ArtistStatus::Deleted.to_string(), "deleted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_status_to_display_str() {
|
||||
assert_str_eq!(ArtistStatus::Continuing.to_display_str(), "Continuing");
|
||||
assert_str_eq!(ArtistStatus::Ended.to_display_str(), "Ended");
|
||||
assert_str_eq!(ArtistStatus::Deleted.to_display_str(), "Deleted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_status_display() {
|
||||
assert_str_eq!(DownloadStatus::Unknown.to_string(), "unknown");
|
||||
assert_str_eq!(DownloadStatus::Queued.to_string(), "queued");
|
||||
assert_str_eq!(DownloadStatus::Paused.to_string(), "paused");
|
||||
assert_str_eq!(DownloadStatus::Downloading.to_string(), "downloading");
|
||||
assert_str_eq!(DownloadStatus::Completed.to_string(), "completed");
|
||||
assert_str_eq!(DownloadStatus::Failed.to_string(), "failed");
|
||||
assert_str_eq!(DownloadStatus::Warning.to_string(), "warning");
|
||||
assert_str_eq!(DownloadStatus::Delay.to_string(), "delay");
|
||||
assert_str_eq!(
|
||||
DownloadStatus::DownloadClientUnavailable.to_string(),
|
||||
"downloadClientUnavailable"
|
||||
);
|
||||
assert_str_eq!(DownloadStatus::Fallback.to_string(), "fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_status_to_display_str() {
|
||||
assert_str_eq!(DownloadStatus::Unknown.to_display_str(), "Unknown");
|
||||
assert_str_eq!(DownloadStatus::Queued.to_display_str(), "Queued");
|
||||
assert_str_eq!(DownloadStatus::Paused.to_display_str(), "Paused");
|
||||
assert_str_eq!(DownloadStatus::Downloading.to_display_str(), "Downloading");
|
||||
assert_str_eq!(DownloadStatus::Completed.to_display_str(), "Completed");
|
||||
assert_str_eq!(DownloadStatus::Failed.to_display_str(), "Failed");
|
||||
assert_str_eq!(DownloadStatus::Warning.to_display_str(), "Warning");
|
||||
assert_str_eq!(DownloadStatus::Delay.to_display_str(), "Delay");
|
||||
assert_str_eq!(
|
||||
DownloadStatus::DownloadClientUnavailable.to_display_str(),
|
||||
"Download Client Unavailable"
|
||||
);
|
||||
assert_str_eq!(DownloadStatus::Fallback.to_display_str(), "Fallback");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ mod tests {
|
||||
use crate::models::lidarr_models::{Artist, DeleteArtistParams, LidarrSerdeable};
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||
use mockito::Matcher;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -18,6 +19,7 @@ mod tests {
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"monitored": true,
|
||||
"monitorNewItems": "all",
|
||||
"genres": [],
|
||||
"tags": [],
|
||||
"added": "2023-01-01T00:00:00Z"
|
||||
@@ -66,4 +68,88 @@ mod tests {
|
||||
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_artist_details_event() {
|
||||
let artist_json = json!({
|
||||
"id": 1,
|
||||
"mbId": "test-mb-id",
|
||||
"artistName": "Test Artist",
|
||||
"foreignArtistId": "test-foreign-id",
|
||||
"status": "continuing",
|
||||
"path": "/music/test-artist",
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"monitored": true,
|
||||
"monitorNewItems": "all",
|
||||
"genres": [],
|
||||
"tags": [],
|
||||
"added": "2023-01-01T00:00:00Z"
|
||||
});
|
||||
let response: Artist = serde_json::from_value(artist_json.clone()).unwrap();
|
||||
let (mock, app, _server) = MockServarrApi::get()
|
||||
.returns(artist_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetArtistDetails(1))
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let result = network
|
||||
.handle_lidarr_event(LidarrEvent::GetArtistDetails(1))
|
||||
.await;
|
||||
|
||||
mock.assert_async().await;
|
||||
|
||||
let LidarrSerdeable::Artist(artist) = result.unwrap() else {
|
||||
panic!("Expected Artist");
|
||||
};
|
||||
|
||||
assert_eq!(artist, response);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_toggle_artist_monitoring_event() {
|
||||
let artist_json = json!({
|
||||
"id": 1,
|
||||
"mbId": "test-mb-id",
|
||||
"artistName": "Test Artist",
|
||||
"foreignArtistId": "test-foreign-id",
|
||||
"status": "continuing",
|
||||
"path": "/music/test-artist",
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"monitored": true,
|
||||
"monitorNewItems": "all",
|
||||
"genres": [],
|
||||
"tags": [],
|
||||
"added": "2023-01-01T00:00:00Z"
|
||||
});
|
||||
let mut expected_body = artist_json.clone();
|
||||
*expected_body.get_mut("monitored").unwrap() = json!(false);
|
||||
let (get_mock, app, mut server) = MockServarrApi::get()
|
||||
.returns(artist_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetArtistDetails(1))
|
||||
.await;
|
||||
let put_mock = server
|
||||
.mock("PUT", "/api/v1/artist/1")
|
||||
.match_body(Matcher::Json(expected_body))
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.with_status(202)
|
||||
.create_async()
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::ToggleArtistMonitoring(1))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
get_mock.assert_async().await;
|
||||
put_mock.assert_async().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
use log::{debug, info, warn};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::Route;
|
||||
use crate::models::lidarr_models::{Artist, DeleteArtistParams};
|
||||
@@ -65,4 +66,89 @@ impl Network<'_, '_> {
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn get_artist_details(
|
||||
&mut self,
|
||||
artist_id: i64,
|
||||
) -> Result<Artist> {
|
||||
info!("Fetching details for Lidarr artist with ID: {artist_id}");
|
||||
let event = LidarrEvent::GetArtistDetails(artist_id);
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some(format!("/{artist_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), Artist>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn toggle_artist_monitoring(
|
||||
&mut self,
|
||||
artist_id: i64,
|
||||
) -> Result<()> {
|
||||
let event = LidarrEvent::ToggleArtistMonitoring(artist_id);
|
||||
|
||||
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
||||
info!("Toggling artist monitoring for artist with ID: {artist_id}");
|
||||
info!("Fetching artist details for artist with ID: {artist_id}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
detail_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some(format!("/{artist_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut response = String::new();
|
||||
|
||||
self
|
||||
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
||||
response = detailed_artist_body.to_string()
|
||||
})
|
||||
.await?;
|
||||
|
||||
info!("Constructing toggle artist monitoring body");
|
||||
|
||||
match serde_json::from_str::<Value>(&response) {
|
||||
Ok(mut detailed_artist_body) => {
|
||||
let monitored = detailed_artist_body
|
||||
.get("monitored")
|
||||
.unwrap()
|
||||
.as_bool()
|
||||
.unwrap();
|
||||
|
||||
*detailed_artist_body.get_mut("monitored").unwrap() = json!(!monitored);
|
||||
|
||||
debug!("Toggle artist monitoring body: {detailed_artist_body:?}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Put,
|
||||
Some(detailed_artist_body),
|
||||
Some(format!("/{artist_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Request for detailed artist body was interrupted");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,18 @@ mod tests {
|
||||
use rstest::rstest;
|
||||
use serde_json::json;
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_artist(
|
||||
#[values(
|
||||
LidarrEvent::GetArtistDetails(0),
|
||||
LidarrEvent::ListArtists,
|
||||
LidarrEvent::ToggleArtistMonitoring(0)
|
||||
)]
|
||||
event: LidarrEvent,
|
||||
) {
|
||||
assert_str_eq!(event.resource(), "/artist");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
||||
#[case(LidarrEvent::GetDownloads(500), "/queue")]
|
||||
@@ -17,7 +29,6 @@ mod tests {
|
||||
#[case(LidarrEvent::GetStatus, "/system/status")]
|
||||
#[case(LidarrEvent::GetTags, "/tag")]
|
||||
#[case(LidarrEvent::HealthCheck, "/health")]
|
||||
#[case(LidarrEvent::ListArtists, "/artist")]
|
||||
fn test_resource(#[case] event: LidarrEvent, #[case] expected_uri: &str) {
|
||||
assert_str_eq!(event.resource(), expected_uri);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ mod lidarr_network_tests;
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum LidarrEvent {
|
||||
DeleteArtist(DeleteArtistParams),
|
||||
GetArtistDetails(i64),
|
||||
GetDiskSpace,
|
||||
GetDownloads(u64),
|
||||
GetMetadataProfiles,
|
||||
@@ -27,12 +28,16 @@ pub enum LidarrEvent {
|
||||
GetTags,
|
||||
HealthCheck,
|
||||
ListArtists,
|
||||
ToggleArtistMonitoring(i64),
|
||||
}
|
||||
|
||||
impl NetworkResource for LidarrEvent {
|
||||
fn resource(&self) -> &'static str {
|
||||
match &self {
|
||||
LidarrEvent::DeleteArtist(_) | LidarrEvent::ListArtists => "/artist",
|
||||
LidarrEvent::DeleteArtist(_)
|
||||
| LidarrEvent::GetArtistDetails(_)
|
||||
| LidarrEvent::ListArtists
|
||||
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
|
||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||
LidarrEvent::GetDownloads(_) => "/queue",
|
||||
LidarrEvent::GetMetadataProfiles => "/metadataprofile",
|
||||
@@ -60,6 +65,10 @@ impl Network<'_, '_> {
|
||||
LidarrEvent::DeleteArtist(params) => {
|
||||
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
||||
}
|
||||
LidarrEvent::GetArtistDetails(artist_id) => self
|
||||
.get_artist_details(artist_id)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::GetDiskSpace => self.get_lidarr_diskspace().await.map(LidarrSerdeable::from),
|
||||
LidarrEvent::GetDownloads(count) => self
|
||||
.get_lidarr_downloads(count)
|
||||
@@ -84,6 +93,10 @@ impl Network<'_, '_> {
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::ListArtists => self.list_artists().await.map(LidarrSerdeable::from),
|
||||
LidarrEvent::ToggleArtistMonitoring(artist_id) => self
|
||||
.toggle_artist_monitoring(artist_id)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user