feat: Implemented the Lidarr History tab and CLI support

This commit is contained in:
2026-01-12 14:21:58 -07:00
parent f31810e48a
commit 68b08d1cd7
41 changed files with 2505 additions and 78 deletions
+56
View File
@@ -123,6 +123,31 @@ mod tests {
assert_ok!(&result);
}
#[test]
fn test_mark_history_item_as_failed_requires_history_item_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "mark-history-item-as-failed"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_mark_history_item_as_failed_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"mark-history-item-as-failed",
"--history-item-id",
"1",
]);
assert_ok!(&result);
}
}
mod handler {
@@ -364,5 +389,36 @@ mod tests {
assert_ok!(&result);
}
#[tokio::test]
async fn test_mark_history_item_as_failed_command() {
let expected_history_item_id = 1i64;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let mark_history_item_as_failed_command = LidarrCommand::MarkHistoryItemAsFailed {
history_item_id: expected_history_item_id,
};
let result = LidarrCliHandler::with(
&app_arc,
mark_history_item_as_failed_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
}
}
+12
View File
@@ -29,6 +29,11 @@ pub enum LidarrListCommand {
},
#[command(about = "List all artists in your Lidarr library")]
Artists,
#[command(about = "Fetch all Lidarr history events")]
History {
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
events: u64,
},
#[command(about = "List all Lidarr metadata profiles")]
MetadataProfiles,
#[command(about = "List all Lidarr quality profiles")]
@@ -78,6 +83,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::History { events: items } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetHistory(items).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::MetadataProfiles => {
let resp = self
.network
@@ -57,6 +57,29 @@ mod tests {
};
assert_eq!(album_command, expected_args);
}
#[test]
fn test_list_history_events_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "history", "--events"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_history_default_values() {
let expected_args = LidarrListCommand::History { events: 500 };
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "history"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(history_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(history_command, expected_args);
}
}
mod handler {
@@ -127,5 +150,31 @@ mod tests {
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_history_command() {
let expected_events = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetHistory(expected_events).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_history_command = LidarrListCommand::History { events: 1000 };
let result =
LidarrListCommandHandler::with(&app_arc, list_history_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+17
View File
@@ -8,6 +8,7 @@ use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
use serde_json::json;
use tokio::sync::Mutex;
use trigger_automatic_search_command_handler::{
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
@@ -67,6 +68,15 @@ pub enum LidarrCommand {
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
)]
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
MarkHistoryItemAsFailed {
#[arg(
long,
help = "The Lidarr ID of the history item you wish to mark as 'failed'",
required = true
)]
history_item_id: i64,
},
#[command(about = "Search for a new artist to add to Lidarr")]
SearchNewArtist {
#[arg(
@@ -166,6 +176,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
.handle()
.await?
}
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
let _ = self
.network
.handle_network_event(LidarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?;
serde_json::to_string_pretty(&json!({"message": "Lidarr history item marked as 'failed'"}))?
}
LidarrCommand::SearchNewArtist { query } => {
let resp = self
.network
+2 -1
View File
@@ -10,6 +10,7 @@ use get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler};
use list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
use manual_search_command_handler::{SonarrManualSearchCommand, SonarrManualSearchCommandHandler};
use refresh_command_handler::{SonarrRefreshCommand, SonarrRefreshCommandHandler};
use serde_json::json;
use tokio::sync::Mutex;
use trigger_automatic_search_command_handler::{
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
@@ -251,7 +252,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
.network
.handle_network_event(SonarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?;
"Sonarr history item marked as 'failed'".to_owned()
serde_json::to_string_pretty(&json!({"message": "Sonarr history item marked as 'failed'"}))?
}
SonarrCommand::SearchNewSeries { query } => {
let resp = self