Lidarr support #1

Merged
Dark-Alex-17 merged 61 commits from lidarr into main 2026-01-21 21:30:47 +00:00
14 changed files with 402 additions and 212 deletions
Showing only changes of commit 09bee7473f - Show all commits
+27 -2
View File
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
models::lidarr_models::DeleteArtistParams,
models::lidarr_models::DeleteParams,
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
@@ -19,6 +19,15 @@ mod delete_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrDeleteCommand {
#[command(about = "Delete an album from your Lidarr library")]
Album {
#[arg(long, help = "The ID of the album to delete", required = true)]
album_id: i64,
#[arg(long, help = "Delete the album files from disk as well")]
delete_files_from_disk: bool,
#[arg(long, help = "Add a list exclusion for this album")]
add_list_exclusion: bool,
},
#[command(about = "Delete an artist from your Lidarr library")]
Artist {
#[arg(long, help = "The ID of the artist to delete", required = true)]
@@ -62,12 +71,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrDeleteCommand::Album {
album_id,
delete_files_from_disk,
add_list_exclusion,
} => {
let delete_album_params = DeleteParams {
id: album_id,
delete_files: delete_files_from_disk,
add_import_list_exclusion: add_list_exclusion,
};
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteAlbum(delete_album_params).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::Artist {
artist_id,
delete_files_from_disk,
add_list_exclusion,
} => {
let delete_artist_params = DeleteArtistParams {
let delete_artist_params = DeleteParams {
id: artist_id,
delete_files: delete_files_from_disk,
add_import_list_exclusion: add_list_exclusion,
+95 -2
View File
@@ -27,6 +27,65 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_delete_album_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "album"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_album_defaults() {
let expected_args = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: false,
add_list_exclusion: false,
};
let result =
Cli::try_parse_from(["managarr", "lidarr", "delete", "album", "--album-id", "1"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_album_all_args_defined() {
let expected_args = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"album",
"--album-id",
"1",
"--delete-files-from-disk",
"--add-list-exclusion",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_artist_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
@@ -128,14 +187,48 @@ mod tests {
},
models::{
Serdeable,
lidarr_models::{DeleteArtistParams, LidarrSerdeable},
lidarr_models::{DeleteParams, LidarrSerdeable},
},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_handle_delete_album_command() {
let expected_delete_album_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteAlbum(expected_delete_album_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_album_command = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_album_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_artist_command() {
let expected_delete_artist_params = DeleteArtistParams {
let expected_delete_artist_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
+2 -2
View File
@@ -147,7 +147,7 @@ mod tests {
},
models::{
Serdeable,
lidarr_models::{Artist, DeleteArtistParams, LidarrSerdeable},
lidarr_models::{Artist, DeleteParams, LidarrSerdeable},
},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
@@ -203,7 +203,7 @@ mod tests {
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
let expected_delete_artist_params = DeleteArtistParams {
let expected_delete_artist_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
@@ -4,12 +4,12 @@ mod tests {
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, ARTIST_DETAILS_BLOCKS,
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
};
mod test_handle_left_right_action {
@@ -17,64 +17,59 @@ mod tests {
use crate::app::App;
use crate::event::Key;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
#[rstest]
fn test_left_right_prompt_toggle(
#[values(
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
)] active_lidarr_block: ActiveLidarrBlock,
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
ActiveLidarrBlock::AutomaticallySearchArtistPrompt
)]
active_lidarr_block: ActiveLidarrBlock,
#[values(Key::Left, Key::Right)] key: Key,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
app.push_navigation_stack(active_lidarr_block.into());
ArtistDetailsHandler::new(
key,
&mut app,
active_lidarr_block,
None,
)
.handle();
ArtistDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle();
assert!(app.data.lidarr_data.prompt_confirm);
ArtistDetailsHandler::new(
key,
&mut app,
active_lidarr_block,
None,
)
.handle();
ArtistDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle();
assert!(!app.data.lidarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use rstest::rstest;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_popped;
use crate::event::Key;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
use crate::network::lidarr_network::LidarrEvent;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
use rstest::rstest;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[rstest]
#[case(ActiveLidarrBlock::AutomaticallySearchArtistPrompt, LidarrEvent::TriggerAutomaticArtistSearch(1))]
#[case(ActiveLidarrBlock::UpdateAndScanArtistPrompt, LidarrEvent::UpdateAndScanArtist(1))]
#[case(
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
LidarrEvent::TriggerAutomaticArtistSearch(1)
)]
#[case(
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
LidarrEvent::UpdateAndScanArtist(1)
)]
fn test_artist_details_prompt_confirm_submit(
#[case] prompt_block: ActiveLidarrBlock,
#[case] expected_action: LidarrEvent
#[case] expected_action: LidarrEvent,
) {
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
@@ -82,13 +77,7 @@ mod tests {
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
app.push_navigation_stack(prompt_block.into());
ArtistDetailsHandler::new(
SUBMIT_KEY,
&mut app,
prompt_block,
None,
)
.handle();
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(app.data.lidarr_data.prompt_confirm);
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
@@ -101,21 +90,16 @@ mod tests {
#[rstest]
fn test_artist_details_prompt_decline_submit(
#[values(
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
)] prompt_block: ActiveLidarrBlock
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
ActiveLidarrBlock::UpdateAndScanArtistPrompt
)]
prompt_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
app.push_navigation_stack(prompt_block.into());
ArtistDetailsHandler::new(
SUBMIT_KEY,
&mut app,
prompt_block,
None,
)
.handle();
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(!app.data.lidarr_data.prompt_confirm);
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
@@ -124,14 +108,14 @@ mod tests {
}
mod test_handle_esc {
use rstest::rstest;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_popped;
use crate::event::Key;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use rstest::rstest;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
@@ -140,8 +124,9 @@ mod tests {
#[values(
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
ActiveLidarrBlock::UpdateAndScanArtistPrompt
)] prompt_block: ActiveLidarrBlock,
#[values(true, false)] is_ready: bool
)]
prompt_block: ActiveLidarrBlock,
#[values(true, false)] is_ready: bool,
) {
let mut app = App::test_default();
app.is_loading = is_ready;
@@ -157,21 +142,23 @@ mod tests {
}
mod test_handle_char_key_event {
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_pushed;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::models::lidarr_models::Artist;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS,
};
use crate::network::lidarr_network::LidarrEvent;
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App;
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
use crate::assert_navigation_pushed;
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
use crate::handlers::KeyEventHandler;
use crate::models::lidarr_models::{Artist};
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS};
use crate::network::lidarr_network::LidarrEvent;
#[rstest]
fn test_artist_details_edit_key(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
@@ -183,7 +170,7 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_navigation_pushed!(
app,
@@ -203,7 +190,7 @@ mod tests {
#[rstest]
fn test_artist_details_edit_key_no_op_when_not_ready(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.is_loading = true;
@@ -216,7 +203,7 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_eq!(app.get_current_route(), active_lidarr_block.into());
assert_modal_absent!(app.data.lidarr_data.edit_artist_modal);
@@ -235,9 +222,12 @@ mod tests {
ActiveLidarrBlock::ArtistDetails,
None,
)
.handle();
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::ArtistDetails.into());
assert_eq!(
app.get_current_route(),
ActiveLidarrBlock::ArtistDetails.into()
);
assert!(app.data.lidarr_data.prompt_confirm);
assert!(app.is_routing);
assert_eq!(
@@ -259,9 +249,12 @@ mod tests {
ActiveLidarrBlock::ArtistDetails,
None,
)
.handle();
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::ArtistDetails.into());
assert_eq!(
app.get_current_route(),
ActiveLidarrBlock::ArtistDetails.into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_none!(app.data.lidarr_data.prompt_confirm_action);
}
@@ -281,7 +274,7 @@ mod tests {
ActiveLidarrBlock::ArtistDetails,
None,
)
.handle();
.handle();
assert!(!app.data.lidarr_data.prompt_confirm);
assert!(app.data.lidarr_data.prompt_confirm_action.is_none());
@@ -289,7 +282,7 @@ mod tests {
#[rstest]
fn test_artist_details_auto_search_key(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
@@ -300,7 +293,7 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_navigation_pushed!(
app,
@@ -310,7 +303,7 @@ mod tests {
#[rstest]
fn test_artist_details_auto_search_key_no_op_when_not_ready(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
@@ -322,14 +315,14 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_eq!(app.get_current_route(), active_lidarr_block.into());
}
#[rstest]
fn test_artist_details_update_key(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
@@ -340,14 +333,14 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::UpdateAndScanArtistPrompt.into());
}
#[rstest]
fn test_artist_details_update_key_no_op_when_not_ready(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.is_loading = true;
@@ -359,14 +352,14 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_eq!(app.get_current_route(), active_lidarr_block.into());
}
#[rstest]
fn test_artist_details_refresh_key(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.is_routing = false;
@@ -381,16 +374,13 @@ mod tests {
)
.handle();
assert_navigation_pushed!(
app,
active_lidarr_block.into()
);
assert_navigation_pushed!(app, active_lidarr_block.into());
assert!(app.is_routing);
}
#[rstest]
fn test_artist_details_refresh_key_no_op_when_not_ready(
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.is_loading = true;
@@ -403,12 +393,9 @@ mod tests {
active_lidarr_block,
None,
)
.handle();
.handle();
assert_eq!(
app.get_current_route(),
active_lidarr_block.into()
);
assert_eq!(app.get_current_route(), active_lidarr_block.into());
assert!(!app.is_routing);
}
@@ -424,8 +411,7 @@ mod tests {
fn test_artist_details_prompt_confirm_key(
#[case] prompt_block: ActiveLidarrBlock,
#[case] expected_action: LidarrEvent,
#[values(ActiveLidarrBlock::ArtistDetails)]
active_lidarr_block: ActiveLidarrBlock,
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_lidarr_block.into());
@@ -463,8 +449,13 @@ mod tests {
fn test_extract_artist_id() {
let mut app = App::test_default_fully_populated();
let artist_id = ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.esc.key,
&mut app, ActiveLidarrBlock::ArtistDetails, None).extract_artist_id();
let artist_id = ArtistDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::ArtistDetails,
None,
)
.extract_artist_id();
assert_eq!(artist_id, 1);
}
@@ -473,8 +464,13 @@ mod tests {
fn test_extract_album_id() {
let mut app = App::test_default_fully_populated();
let album_id = ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.esc.key,
&mut app, ActiveLidarrBlock::ArtistDetails, None).extract_album_id();
let album_id = ArtistDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::ArtistDetails,
None,
)
.extract_album_id();
assert_eq!(album_id, 1);
}
@@ -1,5 +1,5 @@
use crate::models::Route;
use crate::models::lidarr_models::DeleteArtistParams;
use crate::models::lidarr_models::DeleteParams;
use crate::network::lidarr_network::LidarrEvent;
use crate::{
app::App,
@@ -21,13 +21,13 @@ pub(in crate::handlers::lidarr_handlers) struct DeleteArtistHandler<'a, 'b> {
}
impl DeleteArtistHandler<'_, '_> {
fn build_delete_artist_params(&mut self) -> DeleteArtistParams {
fn build_delete_artist_params(&mut self) -> DeleteParams {
let id = self.app.data.lidarr_data.artists.current_selection().id;
let delete_files = self.app.data.lidarr_data.delete_artist_files;
let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion;
self.app.data.lidarr_data.reset_delete_artist_preferences();
DeleteArtistParams {
DeleteParams {
id,
delete_files,
add_import_list_exclusion,
@@ -9,7 +9,7 @@ mod tests {
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::library::delete_artist_handler::DeleteArtistHandler;
use crate::models::lidarr_models::{Artist, DeleteArtistParams};
use crate::models::lidarr_models::{Artist, DeleteParams};
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ARTIST_BLOCKS};
mod test_handle_scroll_up_and_down {
@@ -137,7 +137,7 @@ mod tests {
.lidarr_data
.artists
.set_items(vec![Artist::default()]);
let expected_delete_artist_params = DeleteArtistParams {
let expected_delete_artist_params = DeleteParams {
id: 0,
delete_files: true,
add_import_list_exclusion: true,
@@ -286,7 +286,7 @@ mod tests {
.lidarr_data
.artists
.set_items(vec![Artist::default()]);
let expected_delete_artist_params = DeleteArtistParams {
let expected_delete_artist_params = DeleteParams {
id: 0,
delete_files: true,
add_import_list_exclusion: true,
@@ -359,7 +359,7 @@ mod tests {
.set_items(vec![Artist::default()]);
app.data.lidarr_data.delete_artist_files = true;
app.data.lidarr_data.add_import_list_exclusion = true;
let expected_delete_artist_params = DeleteArtistParams {
let expected_delete_artist_params = DeleteParams {
id: 0,
delete_files: true,
add_import_list_exclusion: true,
+1 -1
View File
@@ -255,7 +255,7 @@ pub struct LidarrCommandBody {
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub struct DeleteArtistParams {
pub struct DeleteParams {
pub id: i64,
pub delete_files: bool,
pub add_import_list_exclusion: bool,
@@ -1,106 +1,131 @@
#[cfg(test)]
mod tests {
use mockito::Matcher;
use pretty_assertions::assert_eq;
use serde_json::{json, Value};
use crate::models::lidarr_models::{Album, LidarrSerdeable};
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{ALBUM_JSON};
use crate::network::lidarr_network::LidarrEvent;
use crate::network::network_tests::test_utils::{test_network, MockServarrApi};
use crate::models::lidarr_models::{Album, DeleteParams, LidarrSerdeable};
use crate::network::lidarr_network::LidarrEvent;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::ALBUM_JSON;
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
use mockito::Matcher;
use pretty_assertions::assert_eq;
use serde_json::{Value, json};
#[tokio::test]
async fn test_handle_get_albums_event() {
let albums_json = json!([{
#[tokio::test]
async fn test_handle_get_albums_event() {
let albums_json = json!([{
"id": 1,
"title": "Test Album",
"foreignAlbumId": "test-foreign-album-id",
"monitored": true,
"anyReleaseOk": true,
"profileId": 1,
"duration": 180,
"albumType": "Album",
"genres": ["Classical"],
"ratings": {"votes": 15, "value": 8.4},
"releaseDate": "2023-01-01T00:00:00Z",
"statistics": {
"trackFileCount": 10,
"trackCount": 10,
"totalTrackCount": 10,
"sizeOnDisk": 1024,
"percentOfTracks": 99.9
}
"foreignAlbumId": "test-foreign-album-id",
"monitored": true,
"anyReleaseOk": true,
"profileId": 1,
"duration": 180,
"albumType": "Album",
"genres": ["Classical"],
"ratings": {"votes": 15, "value": 8.4},
"releaseDate": "2023-01-01T00:00:00Z",
"statistics": {
"trackFileCount": 10,
"trackCount": 10,
"totalTrackCount": 10,
"sizeOnDisk": 1024,
"percentOfTracks": 99.9
}
}]);
let response: Vec<Album> = serde_json::from_value(albums_json.clone()).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(albums_json)
.query("artistId=1")
.build_for(LidarrEvent::GetAlbums(1))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
let response: Vec<Album> = serde_json::from_value(albums_json.clone()).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(albums_json)
.query("artistId=1")
.build_for(LidarrEvent::GetAlbums(1))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
let result = network.handle_lidarr_event(LidarrEvent::GetAlbums(1)).await;
let result = network.handle_lidarr_event(LidarrEvent::GetAlbums(1)).await;
mock.assert_async().await;
mock.assert_async().await;
let LidarrSerdeable::Albums(albums) = result.unwrap() else {
panic!("Expected Albums");
};
let LidarrSerdeable::Albums(albums) = result.unwrap() else {
panic!("Expected Albums");
};
assert_eq!(albums, response);
assert!(!app.lock().await.data.lidarr_data.albums.is_empty());
}
assert_eq!(albums, response);
assert!(!app.lock().await.data.lidarr_data.albums.is_empty());
}
#[tokio::test]
async fn test_handle_toggle_album_monitoring_event() {
let mut expected_body: Value = serde_json::from_str(ALBUM_JSON).unwrap();
*expected_body.get_mut("monitored").unwrap() = json!(false);
let (get_mock, app, mut server) = MockServarrApi::get()
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
.path("/1")
.build_for(LidarrEvent::GetAlbums(1))
.await;
let put_mock = server
.mock("PUT", "/api/v1/album/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);
#[tokio::test]
async fn test_handle_delete_album_event() {
let delete_album_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
};
let (async_server, app, _server) = MockServarrApi::delete()
.path("/1")
.query("deleteFiles=true&addImportListExclusion=true")
.build_for(LidarrEvent::DeleteAlbum(delete_album_params.clone()))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
assert_ok!(
network
.handle_lidarr_event(LidarrEvent::ToggleAlbumMonitoring(1))
.await
);
assert!(
network
.handle_lidarr_event(LidarrEvent::DeleteAlbum(delete_album_params))
.await
.is_ok()
);
get_mock.assert_async().await;
put_mock.assert_async().await;
}
async_server.assert_async().await;
}
#[tokio::test]
async fn test_handle_get_album_details_event() {
let expected_album: Album = serde_json::from_str(ALBUM_JSON).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
.path("/1")
.build_for(LidarrEvent::GetAlbumDetails(1))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
#[tokio::test]
async fn test_handle_toggle_album_monitoring_event() {
let mut expected_body: Value = serde_json::from_str(ALBUM_JSON).unwrap();
*expected_body.get_mut("monitored").unwrap() = json!(false);
let (get_mock, app, mut server) = MockServarrApi::get()
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
.path("/1")
.build_for(LidarrEvent::GetAlbums(1))
.await;
let put_mock = server
.mock("PUT", "/api/v1/album/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);
let result = network
.handle_lidarr_event(LidarrEvent::GetAlbumDetails(1))
.await;
assert_ok!(
network
.handle_lidarr_event(LidarrEvent::ToggleAlbumMonitoring(1))
.await
);
mock.assert_async().await;
get_mock.assert_async().await;
put_mock.assert_async().await;
}
let LidarrSerdeable::Album(album) = result.unwrap() else {
panic!("Expected Album");
};
#[tokio::test]
async fn test_handle_get_album_details_event() {
let expected_album: Album = serde_json::from_str(ALBUM_JSON).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
.path("/1")
.build_for(LidarrEvent::GetAlbumDetails(1))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
assert_eq!(album, expected_album);
}
let result = network
.handle_lidarr_event(LidarrEvent::GetAlbumDetails(1))
.await;
mock.assert_async().await;
let LidarrSerdeable::Album(album) = result.unwrap() else {
panic!("Expected Album");
};
assert_eq!(album, expected_album);
}
}
@@ -1,4 +1,4 @@
use crate::models::lidarr_models::{Album};
use crate::models::lidarr_models::{Album, DeleteParams};
use crate::network::lidarr_network::LidarrEvent;
use crate::network::{Network, RequestMethod};
use anyhow::Result;
@@ -57,6 +57,38 @@ impl Network<'_, '_> {
.await
}
pub(in crate::network::lidarr_network) async fn delete_album(
&mut self,
delete_album_params: DeleteParams,
) -> Result<()> {
let event = LidarrEvent::DeleteAlbum(DeleteParams::default());
let DeleteParams {
id,
delete_files,
add_import_list_exclusion,
} = delete_album_params;
info!(
"Deleting Lidarr album with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}"
);
let request_props = self
.request_props_from(
event,
RequestMethod::Delete,
None::<()>,
Some(format!("/{id}")),
Some(format!(
"deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}"
)),
)
.await;
self
.handle_request::<(), ()>(request_props, |_, _| ())
.await
}
pub(in crate::network::lidarr_network) async fn toggle_album_monitoring(
&mut self,
album_id: i64,
@@ -1,8 +1,8 @@
#[cfg(test)]
mod tests {
use crate::models::lidarr_models::{
AddArtistBody, AddArtistOptions, AddArtistSearchResult, Artist, DeleteArtistParams,
EditArtistParams, LidarrSerdeable, MonitorType, NewItemMonitorType,
AddArtistBody, AddArtistOptions, AddArtistSearchResult, Artist, DeleteParams, EditArtistParams,
LidarrSerdeable, MonitorType, NewItemMonitorType,
};
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::network::NetworkResource;
@@ -54,7 +54,7 @@ mod tests {
#[tokio::test]
async fn test_handle_delete_artist_event() {
let delete_artist_params = DeleteArtistParams {
let delete_artist_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
@@ -4,8 +4,7 @@ use serde_json::{Value, json};
use crate::models::Route;
use crate::models::lidarr_models::{
AddArtistBody, AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams,
LidarrCommandBody,
AddArtistBody, AddArtistSearchResult, Artist, DeleteParams, EditArtistParams, LidarrCommandBody,
};
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::stateful_table::StatefulTable;
@@ -20,10 +19,10 @@ mod lidarr_artists_network_tests;
impl Network<'_, '_> {
pub(in crate::network::lidarr_network) async fn delete_artist(
&mut self,
delete_artist_params: DeleteArtistParams,
delete_artist_params: DeleteParams,
) -> Result<()> {
let event = LidarrEvent::DeleteArtist(DeleteArtistParams::default());
let DeleteArtistParams {
let event = LidarrEvent::DeleteArtist(DeleteParams::default());
let DeleteParams {
id,
delete_files,
add_import_list_exclusion,
@@ -50,7 +50,7 @@ pub mod test_utils {
"percentOfTracks": 99.9
}
}"#;
pub const ALBUM_JSON: &str = r#"{
"id": 1,
"title": "Test Album",
@@ -1,7 +1,9 @@
#[cfg(test)]
mod tests {
use crate::app::App;
use crate::models::lidarr_models::{AddArtistBody, LidarrSerdeable, MetadataProfile};
use crate::models::lidarr_models::{
AddArtistBody, DeleteParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
};
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
use crate::models::servarr_models::{QualityProfile, Tag};
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
@@ -19,7 +21,9 @@ mod tests {
LidarrEvent::GetArtistDetails(0),
LidarrEvent::ListArtists,
LidarrEvent::AddArtist(AddArtistBody::default()),
LidarrEvent::ToggleArtistMonitoring(0)
LidarrEvent::ToggleArtistMonitoring(0),
LidarrEvent::DeleteArtist(DeleteParams::default()),
LidarrEvent::EditArtist(EditArtistParams::default())
)]
event: LidarrEvent,
) {
@@ -58,8 +62,14 @@ mod tests {
}
#[rstest]
fn test_resource_albums(
#[values(LidarrEvent::GetAlbums(0), LidarrEvent::ToggleAlbumMonitoring(0), LidarrEvent::GetAlbumDetails(0))] event: LidarrEvent,
fn test_resource_album(
#[values(
LidarrEvent::GetAlbums(0),
LidarrEvent::ToggleAlbumMonitoring(0),
LidarrEvent::GetAlbumDetails(0),
LidarrEvent::DeleteAlbum(DeleteParams::default())
)]
event: LidarrEvent,
) {
assert_str_eq!(event.resource(), "/album");
}
+14 -4
View File
@@ -3,7 +3,7 @@ use log::info;
use super::{NetworkEvent, NetworkResource};
use crate::models::lidarr_models::{
AddArtistBody, DeleteArtistParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
AddArtistBody, DeleteParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
};
use crate::models::servarr_models::{QualityProfile, Tag};
use crate::network::{Network, RequestMethod};
@@ -25,7 +25,8 @@ pub mod lidarr_network_test_utils;
pub enum LidarrEvent {
AddArtist(AddArtistBody),
AddTag(String),
DeleteArtist(DeleteArtistParams),
DeleteAlbum(DeleteParams),
DeleteArtist(DeleteParams),
DeleteTag(i64),
EditArtist(EditArtistParams),
GetAlbums(i64),
@@ -60,7 +61,10 @@ impl NetworkResource for LidarrEvent {
| LidarrEvent::ListArtists
| LidarrEvent::AddArtist(_)
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
LidarrEvent::GetAlbums(_) | LidarrEvent::ToggleAlbumMonitoring(_) | LidarrEvent::GetAlbumDetails(_) => "/album",
LidarrEvent::GetAlbums(_)
| LidarrEvent::ToggleAlbumMonitoring(_)
| LidarrEvent::GetAlbumDetails(_)
| LidarrEvent::DeleteAlbum(_) => "/album",
LidarrEvent::GetDiskSpace => "/diskspace",
LidarrEvent::GetDownloads(_) => "/queue",
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
@@ -90,6 +94,9 @@ impl Network<'_, '_> {
) -> Result<LidarrSerdeable> {
match lidarr_event {
LidarrEvent::AddTag(tag) => self.add_lidarr_tag(tag).await.map(LidarrSerdeable::from),
LidarrEvent::DeleteAlbum(params) => {
self.delete_album(params).await.map(LidarrSerdeable::from)
}
LidarrEvent::DeleteArtist(params) => {
self.delete_artist(params).await.map(LidarrSerdeable::from)
}
@@ -104,7 +111,10 @@ impl Network<'_, '_> {
.get_artist_details(artist_id)
.await
.map(LidarrSerdeable::from),
LidarrEvent::GetAlbumDetails(album_id) => self.get_album_details(album_id).await.map(LidarrSerdeable::from),
LidarrEvent::GetAlbumDetails(album_id) => self
.get_album_details(album_id)
.await
.map(LidarrSerdeable::from),
LidarrEvent::GetDiskSpace => self.get_lidarr_diskspace().await.map(LidarrSerdeable::from),
LidarrEvent::GetDownloads(count) => self
.get_lidarr_downloads(count)