feat: Implemented the Lidarr History tab and CLI support
This commit is contained in:
@@ -102,6 +102,18 @@ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||||
|
];
|
||||||
|
|
||||||
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
|
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
|
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
|
||||||
(DEFAULT_KEYBINDINGS.events, "open events"),
|
(DEFAULT_KEYBINDINGS.events, "open events"),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
mod test {
|
mod test {
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
ServarrContextClueProvider,
|
ServarrContextClueProvider,
|
||||||
};
|
};
|
||||||
@@ -204,6 +204,40 @@ mod test {
|
|||||||
assert_none!(indexers_context_clues_iter.next());
|
assert_none!(indexers_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_context_clues() {
|
||||||
|
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
|
);
|
||||||
|
assert_none!(history_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_context_clues() {
|
fn test_system_context_clues() {
|
||||||
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
|
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
|
||||||
|
|||||||
@@ -70,6 +70,25 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_history_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::History)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetHistory(500).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_extract_add_new_artist_search_query() {
|
async fn test_extract_add_new_artist_search_query() {
|
||||||
let app = App::test_default_fully_populated();
|
let app = App::test_default_fully_populated();
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ impl App<'_> {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::History => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetHistory(500).into())
|
||||||
|
.await
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,18 +57,6 @@ pub static SERIES_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
|
||||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
|
||||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
|
||||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
|
||||||
(
|
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
|
||||||
),
|
|
||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::app::sonarr::sonarr_context_clues::{
|
use crate::app::sonarr::sonarr_context_clues::{
|
||||||
SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
|
SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
|
||||||
@@ -13,10 +13,9 @@ mod tests {
|
|||||||
key_binding::DEFAULT_KEYBINDINGS,
|
key_binding::DEFAULT_KEYBINDINGS,
|
||||||
sonarr::sonarr_context_clues::{
|
sonarr::sonarr_context_clues::{
|
||||||
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
|
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
|
||||||
HISTORY_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES,
|
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
||||||
MANUAL_SEASON_SEARCH_CONTEXT_CLUES, SEASON_DETAILS_CONTEXT_CLUES,
|
SEASON_DETAILS_CONTEXT_CLUES, SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
|
||||||
SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
@@ -146,40 +145,6 @@ mod tests {
|
|||||||
assert_none!(series_history_context_clues_iter.next());
|
assert_none!(series_history_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_history_context_clues() {
|
|
||||||
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
|
|
||||||
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(DEFAULT_KEYBINDINGS.submit, "details")
|
|
||||||
);
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
|
||||||
);
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
|
||||||
);
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
|
||||||
);
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(
|
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc
|
|
||||||
)
|
|
||||||
);
|
|
||||||
assert_some_eq_x!(
|
|
||||||
history_context_clues_iter.next(),
|
|
||||||
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
|
||||||
);
|
|
||||||
assert_none!(history_context_clues_iter.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_series_details_context_clues() {
|
fn test_series_details_context_clues() {
|
||||||
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
|
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|||||||
@@ -123,6 +123,31 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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 {
|
mod handler {
|
||||||
@@ -364,5 +389,36 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ pub enum LidarrListCommand {
|
|||||||
},
|
},
|
||||||
#[command(about = "List all artists in your Lidarr library")]
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
Artists,
|
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")]
|
#[command(about = "List all Lidarr metadata profiles")]
|
||||||
MetadataProfiles,
|
MetadataProfiles,
|
||||||
#[command(about = "List all Lidarr quality profiles")]
|
#[command(about = "List all Lidarr quality profiles")]
|
||||||
@@ -78,6 +83,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 => {
|
LidarrListCommand::MetadataProfiles => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -57,6 +57,29 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(album_command, expected_args);
|
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 {
|
mod handler {
|
||||||
@@ -127,5 +150,31 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
|
|||||||
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
||||||
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
||||||
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use trigger_automatic_search_command_handler::{
|
use trigger_automatic_search_command_handler::{
|
||||||
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
@@ -67,6 +68,15 @@ pub enum LidarrCommand {
|
|||||||
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
||||||
)]
|
)]
|
||||||
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
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")]
|
#[command(about = "Search for a new artist to add to Lidarr")]
|
||||||
SearchNewArtist {
|
SearchNewArtist {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -166,6 +176,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.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 } => {
|
LidarrCommand::SearchNewArtist { query } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler};
|
|||||||
use list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
|
use list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
|
||||||
use manual_search_command_handler::{SonarrManualSearchCommand, SonarrManualSearchCommandHandler};
|
use manual_search_command_handler::{SonarrManualSearchCommand, SonarrManualSearchCommandHandler};
|
||||||
use refresh_command_handler::{SonarrRefreshCommand, SonarrRefreshCommandHandler};
|
use refresh_command_handler::{SonarrRefreshCommand, SonarrRefreshCommandHandler};
|
||||||
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use trigger_automatic_search_command_handler::{
|
use trigger_automatic_search_command_handler::{
|
||||||
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
|
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
|
||||||
@@ -251,7 +252,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
|||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
|
.handle_network_event(SonarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
|
||||||
.await?;
|
.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 } => {
|
SonarrCommand::SearchNewSeries { query } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
|
|||||||
@@ -0,0 +1,394 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use chrono::DateTime;
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::history::{HistoryHandler, history_sorting_options};
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryEventType, LidarrHistoryItem};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
|
||||||
|
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_history_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
|
HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Artists.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_history_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(1);
|
||||||
|
|
||||||
|
HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Artists.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.history.set_items(history_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
|
||||||
|
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::HistoryItemDetails.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.history.set_items(history_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
|
||||||
|
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_esc_history_item_details() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::HistoryItemDetails.into());
|
||||||
|
|
||||||
|
HistoryHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::HistoryItemDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::History.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_default_esc(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.error = "test error".to_owned().into();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
|
||||||
|
HistoryHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
||||||
|
assert_is_empty!(app.error.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_history_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.history.set_items(history_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
|
||||||
|
HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_history_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.history.set_items(history_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
|
||||||
|
HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_sorting_options_source_title() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
|
||||||
|
a.source_title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_history_vec = history_vec();
|
||||||
|
expected_history_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = history_sorting_options()[0].clone();
|
||||||
|
let mut sorted_history_vec = history_vec();
|
||||||
|
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_history_vec, expected_history_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_sorting_options_event_type() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
|
||||||
|
a.event_type
|
||||||
|
.to_string()
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.event_type.to_string().to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_history_vec = history_vec();
|
||||||
|
expected_history_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = history_sorting_options()[1].clone();
|
||||||
|
let mut sorted_history_vec = history_vec();
|
||||||
|
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_history_vec, expected_history_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Event Type");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_history_vec = history_vec();
|
||||||
|
expected_history_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = history_sorting_options()[2].clone();
|
||||||
|
let mut sorted_history_vec = history_vec();
|
||||||
|
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_history_vec, expected_history_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_sorting_options_date() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering =
|
||||||
|
|a, b| a.date.cmp(&b.date);
|
||||||
|
let mut expected_history_vec = history_vec();
|
||||||
|
expected_history_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = history_sorting_options()[3].clone();
|
||||||
|
let mut sorted_history_vec = history_vec();
|
||||||
|
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_history_vec, expected_history_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Date");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if HISTORY_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(HistoryHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!HistoryHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_history_handler_ignore_special_keys(
|
||||||
|
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||||
|
let handler = HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handler.ignore_special_keys(),
|
||||||
|
ignore_special_keys_for_textbox_input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_handler_not_ready_when_history_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_handler_ready_when_not_loading_and_history_is_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
|
||||||
|
let handler = HistoryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn history_vec() -> Vec<LidarrHistoryItem> {
|
||||||
|
vec![
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 3,
|
||||||
|
source_title: "test 1".into(),
|
||||||
|
event_type: LidarrHistoryEventType::Grabbed,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "FLAC".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
|
||||||
|
..LidarrHistoryItem::default()
|
||||||
|
},
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 2,
|
||||||
|
source_title: "test 2".into(),
|
||||||
|
event_type: LidarrHistoryEventType::DownloadImported,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "MP3-320".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||||
|
..LidarrHistoryItem::default()
|
||||||
|
},
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "test 3".into(),
|
||||||
|
event_type: LidarrHistoryEventType::TrackFileDeleted,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "FLAC".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
|
||||||
|
..LidarrHistoryItem::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_clear_errors};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "history_handler_tests.rs"]
|
||||||
|
mod history_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct HistoryHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryHandler<'_, '_> {}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for HistoryHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let history_table_handling_config = TableHandlingConfig::new(ActiveLidarrBlock::History.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::HistorySortPrompt.into())
|
||||||
|
.sort_options(history_sorting_options())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchHistory.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchHistoryError.into())
|
||||||
|
.search_field_fn(|history| &history.source_title.text)
|
||||||
|
.filtering_block(ActiveLidarrBlock::FilterHistory.into())
|
||||||
|
.filter_error_block(ActiveLidarrBlock::FilterHistoryError.into())
|
||||||
|
.filter_field_fn(|history| &history.source_title.text);
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.history,
|
||||||
|
history_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
HISTORY_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
HistoryHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
_context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ignore_special_keys(&self) -> bool {
|
||||||
|
self.app.ignore_special_keys_for_textbox_input
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading && !self.app.data.lidarr_data.history.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::History {
|
||||||
|
handle_change_tab_left_right_keys(self.app, self.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::History {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::HistoryItemDetails.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::HistoryItemDetails {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
} else {
|
||||||
|
handle_clear_errors(self.app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::History {
|
||||||
|
match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::handlers::lidarr_handlers) fn history_sorting_options()
|
||||||
|
-> Vec<SortOption<LidarrHistoryItem>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.source_title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Event Type",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.event_type
|
||||||
|
.to_string()
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.event_type.to_string().to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Date",
|
||||||
|
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::LidarrHandler;
|
use crate::handlers::lidarr_handlers::{LidarrHandler, handle_change_tab_left_right_keys};
|
||||||
use crate::models::lidarr_models::Artist;
|
use crate::models::lidarr_models::Artist;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryItem;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -52,6 +54,97 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
||||||
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
||||||
|
fn test_lidarr_handler_change_tab_left_right_keys(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
|
#[case] right_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.key);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
left_block.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, left_block.into());
|
||||||
|
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.key);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
right_block.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, right_block.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
||||||
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
||||||
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
|
#[case] right_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
left_block.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, left_block.into());
|
||||||
|
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
right_block.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, right_block.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::Artists)]
|
||||||
|
#[case(1, ActiveLidarrBlock::History)]
|
||||||
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(block.into());
|
||||||
|
app.ignore_special_keys_for_textbox_input = true;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
block.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), block.into());
|
||||||
|
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(index);
|
||||||
|
|
||||||
|
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
block.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.get_current_route(), block.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_delegates_library_blocks_to_library_handler(
|
fn test_delegates_library_blocks_to_library_handler(
|
||||||
#[values(
|
#[values(
|
||||||
@@ -92,4 +185,37 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_history_blocks_to_history_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
ActiveLidarrBlock::HistoryItemDetails,
|
||||||
|
ActiveLidarrBlock::HistorySortPrompt,
|
||||||
|
ActiveLidarrBlock::FilterHistory,
|
||||||
|
ActiveLidarrBlock::FilterHistoryError,
|
||||||
|
ActiveLidarrBlock::SearchHistory,
|
||||||
|
ActiveLidarrBlock::SearchHistoryError
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
LidarrHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use history::HistoryHandler;
|
||||||
use library::LibraryHandler;
|
use library::LibraryHandler;
|
||||||
|
|
||||||
use super::KeyEventHandler;
|
use super::KeyEventHandler;
|
||||||
@@ -6,6 +7,7 @@ use crate::{
|
|||||||
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod history;
|
||||||
mod library;
|
mod library;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -25,6 +27,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
||||||
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use strum::{Display, EnumIter};
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
HorizontallyScrollableText, Serdeable,
|
HorizontallyScrollableText, Serdeable,
|
||||||
servarr_models::{DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag},
|
servarr_models::{
|
||||||
|
DiskSpace, HostConfig, QualityProfile, QualityWrapper, RootFolder, SecurityConfig, Tag,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use crate::serde_enum_from;
|
use crate::serde_enum_from;
|
||||||
|
|
||||||
@@ -337,6 +339,78 @@ pub struct AlbumStatistics {
|
|||||||
|
|
||||||
impl Eq for AlbumStatistics {}
|
impl Eq for AlbumStatistics {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LidarrHistoryWrapper {
|
||||||
|
pub records: Vec<LidarrHistoryItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LidarrHistoryData {
|
||||||
|
pub indexer: Option<String>,
|
||||||
|
pub release_group: Option<String>,
|
||||||
|
pub nzb_info_url: Option<String>,
|
||||||
|
pub download_client_name: Option<String>,
|
||||||
|
pub download_client: Option<String>,
|
||||||
|
pub age: Option<String>,
|
||||||
|
pub published_date: Option<DateTime<Utc>>,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub dropped_path: Option<String>,
|
||||||
|
pub imported_path: Option<String>,
|
||||||
|
pub source_path: Option<String>,
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub status_messages: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Display, EnumDisplayStyle,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[strum(serialize_all = "camelCase")]
|
||||||
|
pub enum LidarrHistoryEventType {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Grabbed,
|
||||||
|
#[display_style(name = "Artist Folder Imported")]
|
||||||
|
ArtistFolderImported,
|
||||||
|
#[display_style(name = "Album Import Incomplete")]
|
||||||
|
AlbumImportIncomplete,
|
||||||
|
#[display_style(name = "Download Ignored")]
|
||||||
|
DownloadIgnored,
|
||||||
|
#[display_style(name = "Download Imported")]
|
||||||
|
DownloadImported,
|
||||||
|
#[display_style(name = "Download Failed")]
|
||||||
|
DownloadFailed,
|
||||||
|
#[display_style(name = "Track File Deleted")]
|
||||||
|
TrackFileDeleted,
|
||||||
|
#[display_style(name = "Track File Imported")]
|
||||||
|
TrackFileImported,
|
||||||
|
#[display_style(name = "Track File Renamed")]
|
||||||
|
TrackFileRenamed,
|
||||||
|
#[display_style(name = "Track File Retagged")]
|
||||||
|
TrackFileRetagged,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LidarrHistoryItem {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub id: i64,
|
||||||
|
pub source_title: HorizontallyScrollableText,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub album_id: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub artist_id: i64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub quality: QualityWrapper,
|
||||||
|
pub date: DateTime<Utc>,
|
||||||
|
pub event_type: LidarrHistoryEventType,
|
||||||
|
#[serde(default)]
|
||||||
|
pub data: LidarrHistoryData,
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LidarrSerdeable> for Serdeable {
|
impl From<LidarrSerdeable> for Serdeable {
|
||||||
fn from(value: LidarrSerdeable) -> Serdeable {
|
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||||
Serdeable::Lidarr(value)
|
Serdeable::Lidarr(value)
|
||||||
@@ -352,6 +426,7 @@ serde_enum_from!(
|
|||||||
Artists(Vec<Artist>),
|
Artists(Vec<Artist>),
|
||||||
DiskSpaces(Vec<DiskSpace>),
|
DiskSpaces(Vec<DiskSpace>),
|
||||||
DownloadsResponse(DownloadsResponse),
|
DownloadsResponse(DownloadsResponse),
|
||||||
|
HistoryWrapper(LidarrHistoryWrapper),
|
||||||
HostConfig(HostConfig),
|
HostConfig(HostConfig),
|
||||||
MetadataProfiles(Vec<MetadataProfile>),
|
MetadataProfiles(Vec<MetadataProfile>),
|
||||||
QualityProfiles(Vec<QualityProfile>),
|
QualityProfiles(Vec<QualityProfile>),
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse, Member,
|
AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse,
|
||||||
MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus,
|
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, Member, MetadataProfile,
|
||||||
|
MonitorType, NewItemMonitorType, SystemStatus,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
||||||
@@ -302,6 +303,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_serdeable_from_history_wrapper() {
|
||||||
|
let history_wrapper = LidarrHistoryWrapper {
|
||||||
|
records: vec![LidarrHistoryItem {
|
||||||
|
id: 1,
|
||||||
|
..LidarrHistoryItem::default()
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let lidarr_serdeable: LidarrSerdeable = history_wrapper.clone().into();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_serdeable,
|
||||||
|
LidarrSerdeable::HistoryWrapper(history_wrapper)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lidarr_serdeable_from_metadata_profiles() {
|
fn test_lidarr_serdeable_from_metadata_profiles() {
|
||||||
let metadata_profiles = vec![MetadataProfile {
|
let metadata_profiles = vec![MetadataProfile {
|
||||||
@@ -488,6 +506,90 @@ mod tests {
|
|||||||
assert_str_eq!(DownloadStatus::Fallback.to_display_str(), "Fallback");
|
assert_str_eq!(DownloadStatus::Fallback.to_display_str(), "Fallback");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_history_event_type_display() {
|
||||||
|
assert_str_eq!(LidarrHistoryEventType::Unknown.to_string(), "unknown");
|
||||||
|
assert_str_eq!(LidarrHistoryEventType::Grabbed.to_string(), "grabbed");
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::ArtistFolderImported.to_string(),
|
||||||
|
"artistFolderImported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::AlbumImportIncomplete.to_string(),
|
||||||
|
"albumImportIncomplete"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadIgnored.to_string(),
|
||||||
|
"downloadIgnored"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadImported.to_string(),
|
||||||
|
"downloadImported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadFailed.to_string(),
|
||||||
|
"downloadFailed"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileDeleted.to_string(),
|
||||||
|
"trackFileDeleted"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileImported.to_string(),
|
||||||
|
"trackFileImported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileRenamed.to_string(),
|
||||||
|
"trackFileRenamed"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileRetagged.to_string(),
|
||||||
|
"trackFileRetagged"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_history_event_type_to_display_str() {
|
||||||
|
assert_str_eq!(LidarrHistoryEventType::Unknown.to_display_str(), "Unknown");
|
||||||
|
assert_str_eq!(LidarrHistoryEventType::Grabbed.to_display_str(), "Grabbed");
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::ArtistFolderImported.to_display_str(),
|
||||||
|
"Artist Folder Imported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::AlbumImportIncomplete.to_display_str(),
|
||||||
|
"Album Import Incomplete"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadIgnored.to_display_str(),
|
||||||
|
"Download Ignored"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadImported.to_display_str(),
|
||||||
|
"Download Imported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::DownloadFailed.to_display_str(),
|
||||||
|
"Download Failed"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileDeleted.to_display_str(),
|
||||||
|
"Track File Deleted"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileImported.to_display_str(),
|
||||||
|
"Track File Imported"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileRenamed.to_display_str(),
|
||||||
|
"Track File Renamed"
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
LidarrHistoryEventType::TrackFileRetagged.to_display_str(),
|
||||||
|
"Track File Retagged"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_artist_search_result_deserialization() {
|
fn test_add_artist_search_result_deserialization() {
|
||||||
let search_result_json = json!({
|
let search_result_json = json!({
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
|
|
||||||
use super::modals::{AddArtistModal, EditArtistModal};
|
use super::modals::{AddArtistModal, EditArtistModal};
|
||||||
|
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
||||||
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord},
|
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord, LidarrHistoryItem},
|
||||||
servarr_models::{DiskSpace, RootFolder},
|
servarr_models::{DiskSpace, RootFolder},
|
||||||
stateful_table::StatefulTable,
|
stateful_table::StatefulTable,
|
||||||
};
|
};
|
||||||
@@ -21,8 +22,8 @@ use {
|
|||||||
crate::models::stateful_table::SortOption,
|
crate::models::stateful_table::SortOption,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
add_artist_search_result, album, artist, download_record, metadata_profile,
|
add_artist_search_result, album, artist, download_record, lidarr_history_item,
|
||||||
metadata_profile_map, quality_profile, root_folder, tags_map,
|
metadata_profile, metadata_profile_map, quality_profile, root_folder, tags_map,
|
||||||
},
|
},
|
||||||
crate::network::servarr_test_utils::diskspace,
|
crate::network::servarr_test_utils::diskspace,
|
||||||
strum::{Display, EnumString, IntoEnumIterator},
|
strum::{Display, EnumString, IntoEnumIterator},
|
||||||
@@ -44,6 +45,7 @@ pub struct LidarrData<'a> {
|
|||||||
pub disk_space_vec: Vec<DiskSpace>,
|
pub disk_space_vec: Vec<DiskSpace>,
|
||||||
pub downloads: StatefulTable<DownloadRecord>,
|
pub downloads: StatefulTable<DownloadRecord>,
|
||||||
pub edit_artist_modal: Option<EditArtistModal>,
|
pub edit_artist_modal: Option<EditArtistModal>,
|
||||||
|
pub history: StatefulTable<LidarrHistoryItem>,
|
||||||
pub main_tabs: TabState,
|
pub main_tabs: TabState,
|
||||||
pub metadata_profile_map: BiMap<i64, String>,
|
pub metadata_profile_map: BiMap<i64, String>,
|
||||||
pub prompt_confirm: bool,
|
pub prompt_confirm: bool,
|
||||||
@@ -112,6 +114,7 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
disk_space_vec: Vec::new(),
|
disk_space_vec: Vec::new(),
|
||||||
downloads: StatefulTable::default(),
|
downloads: StatefulTable::default(),
|
||||||
edit_artist_modal: None,
|
edit_artist_modal: None,
|
||||||
|
history: StatefulTable::default(),
|
||||||
metadata_profile_map: BiMap::new(),
|
metadata_profile_map: BiMap::new(),
|
||||||
prompt_confirm: false,
|
prompt_confirm: false,
|
||||||
prompt_confirm_action: None,
|
prompt_confirm_action: None,
|
||||||
@@ -121,12 +124,20 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
start_time: DateTime::default(),
|
start_time: DateTime::default(),
|
||||||
tags_map: BiMap::new(),
|
tags_map: BiMap::new(),
|
||||||
version: String::new(),
|
version: String::new(),
|
||||||
main_tabs: TabState::new(vec![TabRoute {
|
main_tabs: TabState::new(vec![
|
||||||
title: "Library".to_string(),
|
TabRoute {
|
||||||
route: ActiveLidarrBlock::Artists.into(),
|
title: "Library".to_string(),
|
||||||
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
route: ActiveLidarrBlock::Artists.into(),
|
||||||
config: None,
|
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
||||||
}]),
|
config: None,
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "History".to_string(),
|
||||||
|
route: ActiveLidarrBlock::History.into(),
|
||||||
|
contextual_help: Some(&HISTORY_CONTEXT_CLUES),
|
||||||
|
config: None,
|
||||||
|
},
|
||||||
|
]),
|
||||||
artist_info_tabs: TabState::new(vec![TabRoute {
|
artist_info_tabs: TabState::new(vec![TabRoute {
|
||||||
title: "Albums".to_string(),
|
title: "Albums".to_string(),
|
||||||
route: ActiveLidarrBlock::ArtistDetails.into(),
|
route: ActiveLidarrBlock::ArtistDetails.into(),
|
||||||
@@ -195,6 +206,13 @@ impl LidarrData<'_> {
|
|||||||
lidarr_data.artists.search = Some("artist search".into());
|
lidarr_data.artists.search = Some("artist search".into());
|
||||||
lidarr_data.artists.filter = Some("artist filter".into());
|
lidarr_data.artists.filter = Some("artist filter".into());
|
||||||
lidarr_data.downloads.set_items(vec![download_record()]);
|
lidarr_data.downloads.set_items(vec![download_record()]);
|
||||||
|
lidarr_data.history.set_items(vec![lidarr_history_item()]);
|
||||||
|
lidarr_data.history.sorting(vec![SortOption {
|
||||||
|
name: "Date",
|
||||||
|
cmp_fn: Some(|a: &LidarrHistoryItem, b: &LidarrHistoryItem| a.date.cmp(&b.date)),
|
||||||
|
}]);
|
||||||
|
lidarr_data.history.search = Some("test search".into());
|
||||||
|
lidarr_data.history.filter = Some("test filter".into());
|
||||||
lidarr_data.root_folders.set_items(vec![root_folder()]);
|
lidarr_data.root_folders.set_items(vec![root_folder()]);
|
||||||
lidarr_data.version = "1.0.0".to_owned();
|
lidarr_data.version = "1.0.0".to_owned();
|
||||||
lidarr_data.add_artist_search = Some("Test Artist".into());
|
lidarr_data.add_artist_search = Some("Test Artist".into());
|
||||||
@@ -244,10 +262,17 @@ pub enum ActiveLidarrBlock {
|
|||||||
EditArtistToggleMonitored,
|
EditArtistToggleMonitored,
|
||||||
FilterArtists,
|
FilterArtists,
|
||||||
FilterArtistsError,
|
FilterArtistsError,
|
||||||
|
FilterHistory,
|
||||||
|
FilterHistoryError,
|
||||||
|
History,
|
||||||
|
HistoryItemDetails,
|
||||||
|
HistorySortPrompt,
|
||||||
SearchAlbums,
|
SearchAlbums,
|
||||||
SearchAlbumsError,
|
SearchAlbumsError,
|
||||||
SearchArtists,
|
SearchArtists,
|
||||||
SearchArtistsError,
|
SearchArtistsError,
|
||||||
|
SearchHistory,
|
||||||
|
SearchHistoryError,
|
||||||
UpdateAllArtistsPrompt,
|
UpdateAllArtistsPrompt,
|
||||||
UpdateAndScanArtistPrompt,
|
UpdateAndScanArtistPrompt,
|
||||||
}
|
}
|
||||||
@@ -270,6 +295,16 @@ pub static ARTIST_DETAILS_BLOCKS: [ActiveLidarrBlock; 5] = [
|
|||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static HISTORY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
ActiveLidarrBlock::HistoryItemDetails,
|
||||||
|
ActiveLidarrBlock::HistorySortPrompt,
|
||||||
|
ActiveLidarrBlock::SearchHistory,
|
||||||
|
ActiveLidarrBlock::SearchHistoryError,
|
||||||
|
ActiveLidarrBlock::FilterHistory,
|
||||||
|
ActiveLidarrBlock::FilterHistoryError,
|
||||||
|
];
|
||||||
|
|
||||||
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [
|
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [
|
||||||
ActiveLidarrBlock::AddArtistAlreadyInLibrary,
|
ActiveLidarrBlock::AddArtistAlreadyInLibrary,
|
||||||
ActiveLidarrBlock::AddArtistConfirmPrompt,
|
ActiveLidarrBlock::AddArtistConfirmPrompt,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
@@ -7,7 +8,7 @@ mod tests {
|
|||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
||||||
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
||||||
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, HISTORY_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BlockSelectionState, Route,
|
BlockSelectionState, Route,
|
||||||
@@ -134,6 +135,7 @@ mod tests {
|
|||||||
assert_is_empty!(lidarr_data.disk_space_vec);
|
assert_is_empty!(lidarr_data.disk_space_vec);
|
||||||
assert_is_empty!(lidarr_data.downloads);
|
assert_is_empty!(lidarr_data.downloads);
|
||||||
assert_none!(lidarr_data.edit_artist_modal);
|
assert_none!(lidarr_data.edit_artist_modal);
|
||||||
|
assert_is_empty!(lidarr_data.history);
|
||||||
assert_is_empty!(lidarr_data.metadata_profile_map);
|
assert_is_empty!(lidarr_data.metadata_profile_map);
|
||||||
assert!(!lidarr_data.prompt_confirm);
|
assert!(!lidarr_data.prompt_confirm);
|
||||||
assert_none!(lidarr_data.prompt_confirm_action);
|
assert_none!(lidarr_data.prompt_confirm_action);
|
||||||
@@ -144,7 +146,7 @@ mod tests {
|
|||||||
assert_is_empty!(lidarr_data.tags_map);
|
assert_is_empty!(lidarr_data.tags_map);
|
||||||
assert_is_empty!(lidarr_data.version);
|
assert_is_empty!(lidarr_data.version);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.main_tabs.tabs.len(), 1);
|
assert_eq!(lidarr_data.main_tabs.tabs.len(), 2);
|
||||||
|
|
||||||
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -157,6 +159,17 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
||||||
|
|
||||||
|
assert_str_eq!(lidarr_data.main_tabs.tabs[1].title, "History");
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_data.main_tabs.tabs[1].route,
|
||||||
|
ActiveLidarrBlock::History.into()
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&lidarr_data.main_tabs.tabs[1].contextual_help,
|
||||||
|
&HISTORY_CONTEXT_CLUES
|
||||||
|
);
|
||||||
|
assert_none!(lidarr_data.main_tabs.tabs[1].config);
|
||||||
|
|
||||||
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
|
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
|
||||||
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -192,6 +205,18 @@ mod tests {
|
|||||||
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_blocks_contains_expected_blocks() {
|
||||||
|
assert_eq!(HISTORY_BLOCKS.len(), 7);
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::History));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::HistoryItemDetails));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::HistorySortPrompt));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::SearchHistory));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::SearchHistoryError));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::FilterHistory));
|
||||||
|
assert!(HISTORY_BLOCKS.contains(&ActiveLidarrBlock::FilterHistoryError));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_artist_blocks_contents() {
|
fn test_add_artist_blocks_contents() {
|
||||||
assert_eq!(ADD_ARTIST_BLOCKS.len(), 12);
|
assert_eq!(ADD_ARTIST_BLOCKS.len(), 12);
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ use super::modals::{AddSeriesModal, EditSeriesModal, SeasonDetailsModal};
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
context_clues::{
|
context_clues::{
|
||||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
},
|
},
|
||||||
sonarr::sonarr_context_clues::{
|
sonarr::sonarr_context_clues::{
|
||||||
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES,
|
||||||
SERIES_HISTORY_CONTEXT_CLUES,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
mod sonarr_data_tests {
|
mod sonarr_data_tests {
|
||||||
|
use crate::app::context_clues::HISTORY_CONTEXT_CLUES;
|
||||||
use crate::app::sonarr::sonarr_context_clues::SERIES_HISTORY_CONTEXT_CLUES;
|
use crate::app::sonarr::sonarr_context_clues::SERIES_HISTORY_CONTEXT_CLUES;
|
||||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||||
use crate::models::stateful_table::StatefulTable;
|
use crate::models::stateful_table::StatefulTable;
|
||||||
@@ -10,9 +11,7 @@ mod tests {
|
|||||||
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
},
|
},
|
||||||
sonarr::sonarr_context_clues::{
|
sonarr::sonarr_context_clues::{SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES},
|
||||||
HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
BlockSelectionState, Route,
|
BlockSelectionState, Route,
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrHistoryWrapper, LidarrSerdeable};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::lidarr_history_item;
|
||||||
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
|
use rstest::rstest;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_lidarr_history_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||||
|
let history_json = json!({"records": [{
|
||||||
|
"id": 123,
|
||||||
|
"sourceTitle": "z album",
|
||||||
|
"albumId": 1007,
|
||||||
|
"artistId": 1007,
|
||||||
|
"quality": { "quality": { "name": "Lossless" } },
|
||||||
|
"date": "2023-01-01T00:00:00Z",
|
||||||
|
"eventType": "grabbed",
|
||||||
|
"data": {
|
||||||
|
"droppedPath": "/nfs/nzbget/completed/music/Something/cool.mp3",
|
||||||
|
"importedPath": "/nfs/music/Something/Album 1/Cool.mp3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"sourceTitle": "An Album",
|
||||||
|
"albumId": 2001,
|
||||||
|
"artistId": 2001,
|
||||||
|
"quality": { "quality": { "name": "Lossless" } },
|
||||||
|
"date": "2023-01-01T00:00:00Z",
|
||||||
|
"eventType": "grabbed",
|
||||||
|
"data": {
|
||||||
|
"droppedPath": "/nfs/nzbget/completed/music/Something/cool.mp3",
|
||||||
|
"importedPath": "/nfs/music/Something/Album 1/Cool.mp3"
|
||||||
|
}
|
||||||
|
}]});
|
||||||
|
let response: LidarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap();
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(history_json)
|
||||||
|
.query("pageSize=500&sortDirection=descending&sortKey=date")
|
||||||
|
.build_for(LidarrEvent::GetHistory(500))
|
||||||
|
.await;
|
||||||
|
let mut expected_history_items = vec![
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 123,
|
||||||
|
album_id: 1007,
|
||||||
|
artist_id: 1007,
|
||||||
|
source_title: "z album".into(),
|
||||||
|
..lidarr_history_item()
|
||||||
|
},
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 456,
|
||||||
|
album_id: 2001,
|
||||||
|
artist_id: 2001,
|
||||||
|
source_title: "An Album".into(),
|
||||||
|
..lidarr_history_item()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
{
|
||||||
|
let mut app_mut = app.lock().await;
|
||||||
|
app_mut.server_tabs.set_index(2);
|
||||||
|
app_mut.data.lidarr_data.history.sort_asc = true;
|
||||||
|
}
|
||||||
|
if use_custom_sorting {
|
||||||
|
let cmp_fn = |a: &LidarrHistoryItem, b: &LidarrHistoryItem| {
|
||||||
|
a.source_title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
expected_history_items.sort_by(cmp_fn);
|
||||||
|
|
||||||
|
let history_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.sorting(vec![history_sort_option]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::GetHistory(500))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let LidarrSerdeable::HistoryWrapper(history) = result.unwrap() else {
|
||||||
|
panic!("Expected LidarrHistoryWrapper")
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
app.lock().await.data.lidarr_data.history.items,
|
||||||
|
expected_history_items
|
||||||
|
);
|
||||||
|
assert!(app.lock().await.data.lidarr_data.history.sort_asc);
|
||||||
|
assert_eq!(history, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_lidarr_history_event_no_op_when_user_is_selecting_sort_options() {
|
||||||
|
let history_json = json!({"records": [{
|
||||||
|
"id": 123,
|
||||||
|
"sourceTitle": "z album",
|
||||||
|
"albumId": 1007,
|
||||||
|
"artistId": 1007,
|
||||||
|
"quality": { "quality": { "name": "Lossless" } },
|
||||||
|
"date": "2023-01-01T00:00:00Z",
|
||||||
|
"eventType": "grabbed",
|
||||||
|
"data": {
|
||||||
|
"droppedPath": "/nfs/nzbget/completed/music/Something/cool.mp3",
|
||||||
|
"importedPath": "/nfs/music/Something/Album 1/Cool.mp3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 456,
|
||||||
|
"sourceTitle": "An Album",
|
||||||
|
"albumId": 2001,
|
||||||
|
"artistId": 2001,
|
||||||
|
"quality": { "quality": { "name": "Lossless" } },
|
||||||
|
"date": "2023-01-01T00:00:00Z",
|
||||||
|
"eventType": "grabbed",
|
||||||
|
"data": {
|
||||||
|
"droppedPath": "/nfs/nzbget/completed/music/Something/cool.mp3",
|
||||||
|
"importedPath": "/nfs/music/Something/Album 1/Cool.mp3"
|
||||||
|
}
|
||||||
|
}]});
|
||||||
|
let response: LidarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap();
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(history_json)
|
||||||
|
.query("pageSize=500&sortDirection=descending&sortKey=date")
|
||||||
|
.build_for(LidarrEvent::GetHistory(500))
|
||||||
|
.await;
|
||||||
|
app.lock().await.data.lidarr_data.history.sort_asc = true;
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::HistorySortPrompt.into());
|
||||||
|
let cmp_fn = |a: &LidarrHistoryItem, b: &LidarrHistoryItem| {
|
||||||
|
a.source_title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let history_sort_option = SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(cmp_fn),
|
||||||
|
};
|
||||||
|
app
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.history
|
||||||
|
.sorting(vec![history_sort_option]);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let LidarrSerdeable::HistoryWrapper(history) = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::GetHistory(500))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("Expected LidarrHistoryWrapper")
|
||||||
|
};
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert!(app.lock().await.data.lidarr_data.history.is_empty());
|
||||||
|
assert!(app.lock().await.data.lidarr_data.history.sort_asc);
|
||||||
|
assert_eq!(history, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_mark_lidarr_history_item_as_failed_event() {
|
||||||
|
let history_item_id = 1234i64;
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.returns(json!({}))
|
||||||
|
.path("/1234")
|
||||||
|
.build_for(LidarrEvent::MarkHistoryItemAsFailed(history_item_id))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::MarkHistoryItemAsFailed(history_item_id))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryWrapper;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_history_network_tests.rs"]
|
||||||
|
mod lidarr_history_network_tests;
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn get_lidarr_history(
|
||||||
|
&mut self,
|
||||||
|
events: u64,
|
||||||
|
) -> Result<LidarrHistoryWrapper> {
|
||||||
|
info!("Fetching all Lidarr history events");
|
||||||
|
let event = LidarrEvent::GetHistory(events);
|
||||||
|
|
||||||
|
let params = format!("pageSize={events}&sortDirection=descending&sortKey=date");
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), LidarrHistoryWrapper>(request_props, |history_response, mut app| {
|
||||||
|
if !matches!(
|
||||||
|
app.get_current_route(),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::HistorySortPrompt, _)
|
||||||
|
) {
|
||||||
|
let mut history_vec = history_response.records;
|
||||||
|
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
app.data.lidarr_data.history.set_items(history_vec);
|
||||||
|
app.data.lidarr_data.history.apply_sorting_toggle(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn mark_lidarr_history_item_as_failed(
|
||||||
|
&mut self,
|
||||||
|
history_item_id: i64,
|
||||||
|
) -> Result<Value> {
|
||||||
|
info!("Marking the Lidarr history item with ID: {history_item_id} as 'failed'");
|
||||||
|
let event = LidarrEvent::MarkHistoryItemAsFailed(history_item_id);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Post,
|
||||||
|
None,
|
||||||
|
Some(format!("/{history_item_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,11 @@ pub mod test_utils {
|
|||||||
use crate::models::HorizontallyScrollableText;
|
use crate::models::HorizontallyScrollableText;
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus,
|
AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus,
|
||||||
DownloadRecord, DownloadStatus, DownloadsResponse, EditArtistParams, Member, MetadataProfile,
|
DownloadRecord, DownloadStatus, DownloadsResponse, EditArtistParams, LidarrHistoryData,
|
||||||
|
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, Member, MetadataProfile,
|
||||||
NewItemMonitorType, Ratings, SystemStatus,
|
NewItemMonitorType, Ratings, SystemStatus,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{QualityProfile, RootFolder, Tag};
|
use crate::models::servarr_models::{Quality, QualityProfile, QualityWrapper, RootFolder, Tag};
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
@@ -120,6 +121,16 @@ pub mod test_utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn quality_wrapper() -> QualityWrapper {
|
||||||
|
QualityWrapper { quality: quality() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quality() -> Quality {
|
||||||
|
Quality {
|
||||||
|
name: "Lossless".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn quality_profile() -> QualityProfile {
|
pub fn quality_profile() -> QualityProfile {
|
||||||
QualityProfile {
|
QualityProfile {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -249,4 +260,31 @@ pub mod test_utils {
|
|||||||
statistics: Some(album_statistics()),
|
statistics: Some(album_statistics()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lidarr_history_wrapper() -> LidarrHistoryWrapper {
|
||||||
|
LidarrHistoryWrapper {
|
||||||
|
records: vec![lidarr_history_item()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lidarr_history_item() -> LidarrHistoryItem {
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "Test source title".into(),
|
||||||
|
album_id: 1,
|
||||||
|
artist_id: 1,
|
||||||
|
quality: quality_wrapper(),
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z").unwrap()),
|
||||||
|
event_type: LidarrHistoryEventType::Grabbed,
|
||||||
|
data: lidarr_history_data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lidarr_history_data() -> LidarrHistoryData {
|
||||||
|
LidarrHistoryData {
|
||||||
|
dropped_path: Some("/nfs/nzbget/completed/music/Something/cool.mp3".to_owned()),
|
||||||
|
imported_path: Some("/nfs/music/Something/Album 1/Cool.mp3".to_owned()),
|
||||||
|
..LidarrHistoryData::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ mod tests {
|
|||||||
assert_str_eq!(event.resource(), "/artist");
|
assert_str_eq!(event.resource(), "/artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_resource_history(#[values(LidarrEvent::GetHistory(0))] event: LidarrEvent) {
|
||||||
|
assert_str_eq!(event.resource(), "/history");
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_tag(
|
fn test_resource_tag(
|
||||||
#[values(
|
#[values(
|
||||||
@@ -83,6 +88,7 @@ mod tests {
|
|||||||
#[case(LidarrEvent::GetStatus, "/system/status")]
|
#[case(LidarrEvent::GetStatus, "/system/status")]
|
||||||
#[case(LidarrEvent::GetTags, "/tag")]
|
#[case(LidarrEvent::GetTags, "/tag")]
|
||||||
#[case(LidarrEvent::HealthCheck, "/health")]
|
#[case(LidarrEvent::HealthCheck, "/health")]
|
||||||
|
#[case(LidarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")]
|
||||||
fn test_resource(#[case] event: LidarrEvent, #[case] expected_uri: &str) {
|
fn test_resource(#[case] event: LidarrEvent, #[case] expected_uri: &str) {
|
||||||
assert_str_eq!(event.resource(), expected_uri);
|
assert_str_eq!(event.resource(), expected_uri);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::models::servarr_models::{QualityProfile, Tag};
|
|||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
|
|
||||||
mod downloads;
|
mod downloads;
|
||||||
|
mod history;
|
||||||
mod library;
|
mod library;
|
||||||
mod root_folders;
|
mod root_folders;
|
||||||
mod system;
|
mod system;
|
||||||
@@ -34,7 +35,9 @@ pub enum LidarrEvent {
|
|||||||
GetArtistDetails(i64),
|
GetArtistDetails(i64),
|
||||||
GetDiskSpace,
|
GetDiskSpace,
|
||||||
GetDownloads(u64),
|
GetDownloads(u64),
|
||||||
|
GetHistory(u64),
|
||||||
GetHostConfig,
|
GetHostConfig,
|
||||||
|
MarkHistoryItemAsFailed(i64),
|
||||||
GetMetadataProfiles,
|
GetMetadataProfiles,
|
||||||
GetQualityProfiles,
|
GetQualityProfiles,
|
||||||
GetRootFolders,
|
GetRootFolders,
|
||||||
@@ -67,6 +70,8 @@ impl NetworkResource for LidarrEvent {
|
|||||||
| LidarrEvent::DeleteAlbum(_) => "/album",
|
| LidarrEvent::DeleteAlbum(_) => "/album",
|
||||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||||
LidarrEvent::GetDownloads(_) => "/queue",
|
LidarrEvent::GetDownloads(_) => "/queue",
|
||||||
|
LidarrEvent::GetHistory(_) => "/history",
|
||||||
|
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||||
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
||||||
| LidarrEvent::UpdateAllArtists
|
| LidarrEvent::UpdateAllArtists
|
||||||
@@ -120,6 +125,14 @@ impl Network<'_, '_> {
|
|||||||
.get_lidarr_downloads(count)
|
.get_lidarr_downloads(count)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::GetHistory(events) => self
|
||||||
|
.get_lidarr_history(events)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::MarkHistoryItemAsFailed(history_item_id) => self
|
||||||
|
.mark_lidarr_history_item_as_failed(history_item_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::GetHostConfig => self
|
LidarrEvent::GetHostConfig => self
|
||||||
.get_lidarr_host_config()
|
.get_lidarr_host_config()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::history::HistoryUi;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_ui_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if HISTORY_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(HistoryUi::accepts(active_lidarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!HistoryUi::accepts(active_lidarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_history_ui_renders_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
HistoryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_history_ui_renders_empty(
|
||||||
|
#[values(ActiveLidarrBlock::History, ActiveLidarrBlock::HistoryItemDetails)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
HistoryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("loading_history_tab_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_history_ui_renders(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::History,
|
||||||
|
ActiveLidarrBlock::HistoryItemDetails,
|
||||||
|
ActiveLidarrBlock::HistorySortPrompt,
|
||||||
|
ActiveLidarrBlock::FilterHistory,
|
||||||
|
ActiveLidarrBlock::FilterHistoryError,
|
||||||
|
ActiveLidarrBlock::SearchHistory,
|
||||||
|
ActiveLidarrBlock::SearchHistoryError
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
HistoryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("history_tab_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use super::lidarr_ui_utils::create_history_event_details;
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
||||||
|
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
|
||||||
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::message::Message;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use ratatui::Frame;
|
||||||
|
use ratatui::layout::{Alignment, Rect};
|
||||||
|
use ratatui::prelude::Constraint;
|
||||||
|
use ratatui::text::Text;
|
||||||
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "history_ui_tests.rs"]
|
||||||
|
mod history_ui_tests;
|
||||||
|
|
||||||
|
pub(super) struct HistoryUi;
|
||||||
|
|
||||||
|
impl DrawUi for HistoryUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = route {
|
||||||
|
return HISTORY_BLOCKS.contains(&active_lidarr_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
draw_history_table(f, app, area);
|
||||||
|
|
||||||
|
if active_lidarr_block == ActiveLidarrBlock::HistoryItemDetails {
|
||||||
|
draw_history_item_details_popup(f, app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let current_selection = if app.data.lidarr_data.history.items.is_empty() {
|
||||||
|
LidarrHistoryItem::default()
|
||||||
|
} else {
|
||||||
|
app.data.lidarr_data.history.current_selection().clone()
|
||||||
|
};
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
let history_row_mapping = |history_item: &LidarrHistoryItem| {
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
quality,
|
||||||
|
event_type,
|
||||||
|
date,
|
||||||
|
..
|
||||||
|
} = history_item;
|
||||||
|
|
||||||
|
source_title.scroll_left_or_reset(
|
||||||
|
get_width_from_percentage(area, 50),
|
||||||
|
current_selection == *history_item,
|
||||||
|
app.ui_scroll_tick_count == 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(source_title.to_string()),
|
||||||
|
Cell::from(event_type.to_string()),
|
||||||
|
Cell::from(quality.quality.name.to_owned()),
|
||||||
|
Cell::from(date.to_string()),
|
||||||
|
])
|
||||||
|
.primary()
|
||||||
|
};
|
||||||
|
let history_table =
|
||||||
|
ManagarrTable::new(Some(&mut app.data.lidarr_data.history), history_row_mapping)
|
||||||
|
.block(layout_block_top_border())
|
||||||
|
.loading(app.is_loading)
|
||||||
|
.sorting(active_lidarr_block == ActiveLidarrBlock::HistorySortPrompt)
|
||||||
|
.searching(active_lidarr_block == ActiveLidarrBlock::SearchHistory)
|
||||||
|
.search_produced_empty_results(active_lidarr_block == ActiveLidarrBlock::SearchHistoryError)
|
||||||
|
.filtering(active_lidarr_block == ActiveLidarrBlock::FilterHistory)
|
||||||
|
.filter_produced_empty_results(active_lidarr_block == ActiveLidarrBlock::FilterHistoryError)
|
||||||
|
.headers(["Source Title", "Event Type", "Quality", "Date"])
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(18),
|
||||||
|
Constraint::Percentage(12),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if [
|
||||||
|
ActiveLidarrBlock::SearchHistory,
|
||||||
|
ActiveLidarrBlock::FilterHistory,
|
||||||
|
]
|
||||||
|
.contains(&active_lidarr_block)
|
||||||
|
{
|
||||||
|
history_table.show_cursor(f, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(history_table, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let current_selection = if app.data.lidarr_data.history.items.is_empty() {
|
||||||
|
LidarrHistoryItem::default()
|
||||||
|
} else {
|
||||||
|
app.data.lidarr_data.history.current_selection().clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_vec = create_history_event_details(current_selection);
|
||||||
|
let text = Text::from(line_vec);
|
||||||
|
|
||||||
|
let message = Message::new(text)
|
||||||
|
.title("Details")
|
||||||
|
.style(secondary_style())
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
|
||||||
|
f.render_widget(Popup::new(message).size(Size::NarrowMessage), f.area());
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────── Filter ──────────────────╮
|
||||||
|
│test filter │
|
||||||
|
╰─────────────────────────────────────────────╯
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────── Error ───────────────╮
|
||||||
|
│The given filter produced empty results│
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────╯
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────────────────────────── Details ───────────────────────────────────╮
|
||||||
|
│Source Title: Test source title │
|
||||||
|
│Event Type: grabbed │
|
||||||
|
│Quality: Lossless │
|
||||||
|
│Date: 2023-01-01 00:00:00 UTC │
|
||||||
|
│Indexer: │
|
||||||
|
│NZB Info URL: │
|
||||||
|
│Release Group: │
|
||||||
|
│Age: 0 days │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────────────────────╮
|
||||||
|
│Date │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────╯
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────── Search ──────────────────╮
|
||||||
|
│test search │
|
||||||
|
╰─────────────────────────────────────────────╯
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
Source Title ▼ Event Type Quality Date
|
||||||
|
=> Test source title grabbed Lossless 2023-01-01 00:00:00 UTC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────── Error ───────────────╮
|
||||||
|
│ No items found matching search │
|
||||||
|
│ │
|
||||||
|
╰───────────────────────────────────────╯
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
Loading ...
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/history/history_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭─────────────────────────────────── Details ───────────────────────────────────╮
|
||||||
|
│Source Title: │
|
||||||
|
│Event Type: unknown │
|
||||||
|
│Quality: │
|
||||||
|
│Date: 1970-01-01 00:00:00 UTC │
|
||||||
|
│No additional details available. │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────╯
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
use crate::models::lidarr_models::{LidarrHistoryData, LidarrHistoryEventType, LidarrHistoryItem};
|
||||||
|
use ratatui::text::Line;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_ui_utils_tests.rs"]
|
||||||
|
mod lidarr_ui_utils_tests;
|
||||||
|
|
||||||
|
pub(super) fn create_history_event_details(history_item: LidarrHistoryItem) -> Vec<Line<'static>> {
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item;
|
||||||
|
let LidarrHistoryData {
|
||||||
|
indexer,
|
||||||
|
nzb_info_url,
|
||||||
|
release_group,
|
||||||
|
age,
|
||||||
|
published_date,
|
||||||
|
download_client_name,
|
||||||
|
download_client,
|
||||||
|
message,
|
||||||
|
reason,
|
||||||
|
dropped_path,
|
||||||
|
imported_path,
|
||||||
|
source_path,
|
||||||
|
path,
|
||||||
|
status_messages,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
let mut lines = vec![
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text)),
|
||||||
|
Line::from(format!("Event Type: {event_type}")),
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name)),
|
||||||
|
Line::from(format!("Date: {date}")),
|
||||||
|
];
|
||||||
|
|
||||||
|
match event_type {
|
||||||
|
LidarrHistoryEventType::Grabbed => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Indexer: {}",
|
||||||
|
indexer.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"NZB Info URL: {}",
|
||||||
|
nzb_info_url.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Age: {} days",
|
||||||
|
age.unwrap_or("0".to_owned())
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Published Date: {}",
|
||||||
|
published_date.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
download_client_name.unwrap_or(download_client.unwrap_or_default())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::DownloadImported => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::DownloadFailed => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
download_client_name.unwrap_or(download_client.unwrap_or_default())
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Message: {}",
|
||||||
|
message.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Indexer: {}",
|
||||||
|
indexer.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::TrackFileDeleted => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Reason: {}",
|
||||||
|
reason.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::TrackFileImported => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Dropped Path: {}",
|
||||||
|
dropped_path.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Imported Path: {}",
|
||||||
|
imported_path.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
download_client_name.unwrap_or(download_client.unwrap_or_default())
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::TrackFileRenamed => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Source Path: {}",
|
||||||
|
source_path.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!("Path: {}", path.unwrap_or_default())));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::TrackFileRetagged => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
LidarrHistoryEventType::AlbumImportIncomplete => {
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Status Messages: {}",
|
||||||
|
status_messages.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
lines.push(Line::from(format!(
|
||||||
|
"Release Group: {}",
|
||||||
|
release_group.unwrap_or_default()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
lines.push(Line::from("No additional details available.".to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use chrono::Utc;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use ratatui::text::Line;
|
||||||
|
|
||||||
|
use crate::models::lidarr_models::{
|
||||||
|
LidarrHistoryData, LidarrHistoryEventType, LidarrHistoryItem,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||||
|
use crate::ui::lidarr_ui::lidarr_ui_utils::create_history_event_details;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_grabbed() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::Grabbed);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Indexer: {}", data.indexer.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("NZB Info URL: {}", data.nzb_info_url.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[6],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[7],
|
||||||
|
Line::from(format!("Age: {} days", data.age.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[8],
|
||||||
|
Line::from(format!("Published Date: {}", data.published_date.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[9],
|
||||||
|
Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
data.download_client_name.unwrap()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_grabbed_uses_download_client_as_fallback() {
|
||||||
|
let mut history_item = lidarr_history_item(LidarrHistoryEventType::Grabbed);
|
||||||
|
history_item.data.download_client_name = None;
|
||||||
|
history_item.data.download_client = Some("Fallback Client".to_owned());
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(result[9], Line::from("Download Client: Fallback Client"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_download_imported() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::DownloadImported);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_download_failed() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::DownloadFailed);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
data.download_client_name.unwrap()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("Message: {}", data.message.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[6],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[7],
|
||||||
|
Line::from(format!("Indexer: {}", data.indexer.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_track_file_deleted() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::TrackFileDeleted);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Reason: {}", data.reason.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_track_file_imported() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::TrackFileImported);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Dropped Path: {}", data.dropped_path.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("Imported Path: {}", data.imported_path.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[6],
|
||||||
|
Line::from(format!(
|
||||||
|
"Download Client: {}",
|
||||||
|
data.download_client_name.unwrap()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[7],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_track_file_renamed() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::TrackFileRenamed);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Source Path: {}", data.source_path.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("Path: {}", data.path.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[6],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_track_file_retagged() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::TrackFileRetagged);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_album_import_incomplete() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::AlbumImportIncomplete);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[4],
|
||||||
|
Line::from(format!(
|
||||||
|
"Status Messages: {}",
|
||||||
|
data.status_messages.unwrap()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
result[5],
|
||||||
|
Line::from(format!("Release Group: {}", data.release_group.unwrap()))
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_unknown() {
|
||||||
|
let history_item = lidarr_history_item(LidarrHistoryEventType::Unknown);
|
||||||
|
let LidarrHistoryItem {
|
||||||
|
source_title,
|
||||||
|
event_type,
|
||||||
|
quality,
|
||||||
|
date,
|
||||||
|
..
|
||||||
|
} = history_item.clone();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result[0],
|
||||||
|
Line::from(format!("Source Title: {}", source_title.text))
|
||||||
|
);
|
||||||
|
assert_eq!(result[1], Line::from(format!("Event Type: {event_type}")));
|
||||||
|
assert_eq!(
|
||||||
|
result[2],
|
||||||
|
Line::from(format!("Quality: {}", quality.quality.name))
|
||||||
|
);
|
||||||
|
assert_eq!(result[3], Line::from(format!("Date: {date}")));
|
||||||
|
assert_eq!(result[4], Line::from("No additional details available."));
|
||||||
|
assert_eq!(result.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_history_event_details_with_empty_optional_fields() {
|
||||||
|
let mut history_item = lidarr_history_item(LidarrHistoryEventType::Grabbed);
|
||||||
|
history_item.data = LidarrHistoryData::default();
|
||||||
|
|
||||||
|
let result = create_history_event_details(history_item);
|
||||||
|
|
||||||
|
assert_eq!(result[4], Line::from("Indexer: "));
|
||||||
|
assert_eq!(result[5], Line::from("NZB Info URL: "));
|
||||||
|
assert_eq!(result[6], Line::from("Release Group: "));
|
||||||
|
assert_eq!(result[7], Line::from("Age: 0 days"));
|
||||||
|
assert!(result[8].to_string().starts_with("Published Date:"));
|
||||||
|
assert_eq!(result[9], Line::from("Download Client: "));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lidarr_history_item(event_type: LidarrHistoryEventType) -> LidarrHistoryItem {
|
||||||
|
LidarrHistoryItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "Test Album - Artist Name".into(),
|
||||||
|
album_id: 100,
|
||||||
|
artist_id: 10,
|
||||||
|
event_type,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "FLAC".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: Utc::now(),
|
||||||
|
data: lidarr_history_data(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lidarr_history_data() -> LidarrHistoryData {
|
||||||
|
LidarrHistoryData {
|
||||||
|
indexer: Some("Test Indexer".to_owned()),
|
||||||
|
release_group: Some("Test Release Group".to_owned()),
|
||||||
|
nzb_info_url: Some("https://test.url".to_owned()),
|
||||||
|
download_client_name: Some("Test Download Client".to_owned()),
|
||||||
|
download_client: Some("Fallback Download Client".to_owned()),
|
||||||
|
age: Some("7".to_owned()),
|
||||||
|
published_date: Some(Utc::now()),
|
||||||
|
message: Some("Test failure message".to_owned()),
|
||||||
|
reason: Some("Test deletion reason".to_owned()),
|
||||||
|
dropped_path: Some("/downloads/completed/album".to_owned()),
|
||||||
|
imported_path: Some("/music/artist/album".to_owned()),
|
||||||
|
source_path: Some("/music/artist/old_album_name".to_owned()),
|
||||||
|
path: Some("/music/artist/new_album_name".to_owned()),
|
||||||
|
status_messages: Some("Missing tracks: 1, 2, 3".to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ use crate::ui::ui_test_utils::test_utils::Utc;
|
|||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use history::HistoryUi;
|
||||||
use library::LibraryUi;
|
use library::LibraryUi;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
@@ -35,7 +36,9 @@ use super::{
|
|||||||
widgets::loading_block::LoadingBlock,
|
widgets::loading_block::LoadingBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod history;
|
||||||
mod library;
|
mod library;
|
||||||
|
mod lidarr_ui_utils;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_ui_tests.rs"]
|
#[path = "lidarr_ui_tests.rs"]
|
||||||
@@ -54,6 +57,7 @@ impl DrawUi for LidarrUi {
|
|||||||
|
|
||||||
match route {
|
match route {
|
||||||
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
|
||||||
|
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user