feat: Implemented the Lidarr History tab and CLI support
This commit is contained in:
@@ -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::lidarr_models::{
|
||||
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,
|
||||
};
|
||||
use crate::models::servarr_models::{QualityProfile, RootFolder, Tag};
|
||||
use crate::models::servarr_models::{Quality, QualityProfile, QualityWrapper, RootFolder, Tag};
|
||||
use bimap::BiMap;
|
||||
use chrono::DateTime;
|
||||
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 {
|
||||
QualityProfile {
|
||||
id: 1,
|
||||
@@ -249,4 +260,31 @@ pub mod test_utils {
|
||||
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");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_history(#[values(LidarrEvent::GetHistory(0))] event: LidarrEvent) {
|
||||
assert_str_eq!(event.resource(), "/history");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_tag(
|
||||
#[values(
|
||||
@@ -83,6 +88,7 @@ mod tests {
|
||||
#[case(LidarrEvent::GetStatus, "/system/status")]
|
||||
#[case(LidarrEvent::GetTags, "/tag")]
|
||||
#[case(LidarrEvent::HealthCheck, "/health")]
|
||||
#[case(LidarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")]
|
||||
fn test_resource(#[case] event: LidarrEvent, #[case] expected_uri: &str) {
|
||||
assert_str_eq!(event.resource(), expected_uri);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::models::servarr_models::{QualityProfile, Tag};
|
||||
use crate::network::{Network, RequestMethod};
|
||||
|
||||
mod downloads;
|
||||
mod history;
|
||||
mod library;
|
||||
mod root_folders;
|
||||
mod system;
|
||||
@@ -34,7 +35,9 @@ pub enum LidarrEvent {
|
||||
GetArtistDetails(i64),
|
||||
GetDiskSpace,
|
||||
GetDownloads(u64),
|
||||
GetHistory(u64),
|
||||
GetHostConfig,
|
||||
MarkHistoryItemAsFailed(i64),
|
||||
GetMetadataProfiles,
|
||||
GetQualityProfiles,
|
||||
GetRootFolders,
|
||||
@@ -67,6 +70,8 @@ impl NetworkResource for LidarrEvent {
|
||||
| LidarrEvent::DeleteAlbum(_) => "/album",
|
||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||
LidarrEvent::GetDownloads(_) => "/queue",
|
||||
LidarrEvent::GetHistory(_) => "/history",
|
||||
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
||||
| LidarrEvent::UpdateAllArtists
|
||||
@@ -120,6 +125,14 @@ impl Network<'_, '_> {
|
||||
.get_lidarr_downloads(count)
|
||||
.await
|
||||
.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
|
||||
.get_lidarr_host_config()
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user