feat: Full CLI and TUI support for adding an artist to Lidarr

This commit is contained in:
2026-01-08 15:16:01 -07:00
parent e94f78dc7b
commit c624d1b9e4
28 changed files with 3448 additions and 86 deletions
@@ -1,8 +1,8 @@
#[cfg(test)]
mod tests {
use crate::models::lidarr_models::{
AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams, LidarrSerdeable,
NewItemMonitorType,
AddArtistBody, AddArtistOptions, AddArtistSearchResult, Artist, DeleteArtistParams,
EditArtistParams, LidarrSerdeable, MonitorType, NewItemMonitorType,
};
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::network::NetworkResource;
@@ -439,4 +439,105 @@ mod tests {
assert_some!(&app.data.lidarr_data.add_searched_artists);
assert_is_empty!(app.data.lidarr_data.add_searched_artists.as_ref().unwrap());
}
#[tokio::test]
async fn test_handle_add_artist_event() {
let add_artist_body = AddArtistBody {
foreign_artist_id: "test-foreign-id".to_owned(),
artist_name: "Test Artist".to_owned(),
monitored: true,
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
tags: Vec::default(),
tag_input_string: Some("usenet, testing".to_owned()),
add_options: AddArtistOptions {
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
search_for_missing_albums: true,
},
};
let expected_body = json!({
"foreignArtistId": "test-foreign-id",
"artistName": "Test Artist",
"monitored": true,
"rootFolderPath": "/music",
"qualityProfileId": 1,
"metadataProfileId": 1,
"tags": [1, 2],
"addOptions": {
"monitor": "all",
"monitorNewItems": "all",
"searchForMissingAlbums": true
}
});
let (mock, app, _server) = MockServarrApi::post()
.with_request_body(expected_body)
.returns(json!({"id": 1}))
.build_for(LidarrEvent::AddArtist(AddArtistBody::default()))
.await;
app.lock().await.data.lidarr_data.tags_map =
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
assert!(
network
.handle_lidarr_event(LidarrEvent::AddArtist(add_artist_body))
.await
.is_ok()
);
mock.assert_async().await;
}
#[tokio::test]
async fn test_handle_add_artist_event_does_not_overwrite_tags_vec_when_tag_input_string_is_none()
{
let add_artist_body = AddArtistBody {
foreign_artist_id: "test-foreign-id".to_owned(),
artist_name: "Test Artist".to_owned(),
monitored: true,
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
tags: vec![1, 2],
tag_input_string: None,
add_options: AddArtistOptions {
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
search_for_missing_albums: true,
},
};
let expected_body = json!({
"foreignArtistId": "test-foreign-id",
"artistName": "Test Artist",
"monitored": true,
"rootFolderPath": "/music",
"qualityProfileId": 1,
"metadataProfileId": 1,
"tags": [1, 2],
"addOptions": {
"monitor": "all",
"monitorNewItems": "all",
"searchForMissingAlbums": true
}
});
let (mock, app, _server) = MockServarrApi::post()
.with_request_body(expected_body)
.returns(json!({"id": 1}))
.build_for(LidarrEvent::AddArtist(add_artist_body.clone()))
.await;
app.lock().await.server_tabs.set_index(2);
let mut network = test_network(&app);
assert!(
network
.handle_lidarr_event(LidarrEvent::AddArtist(add_artist_body))
.await
.is_ok()
);
mock.assert_async().await;
}
}
+27 -1
View File
@@ -4,7 +4,7 @@ use serde_json::{Value, json};
use crate::models::Route;
use crate::models::lidarr_models::{
AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams,
AddArtistBody, AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams,
};
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::servarr_models::CommandBody;
@@ -213,6 +213,32 @@ impl Network<'_, '_> {
result
}
pub(in crate::network::lidarr_network) async fn add_artist(
&mut self,
mut add_artist_body: AddArtistBody,
) -> Result<Value> {
info!("Adding Lidarr artist: {}", add_artist_body.artist_name);
if let Some(tag_input_str) = add_artist_body.tag_input_string.as_ref() {
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
add_artist_body.tags = tag_ids_vec;
}
let event = LidarrEvent::AddArtist(AddArtistBody::default());
let request_props = self
.request_props_from(
event,
RequestMethod::Post,
Some(add_artist_body),
None,
None,
)
.await;
self
.handle_request::<AddArtistBody, Value>(request_props, |_, _| ())
.await
}
pub(in crate::network::lidarr_network) async fn edit_artist(
&mut self,
mut edit_artist_params: EditArtistParams,
@@ -1,7 +1,7 @@
#[cfg(test)]
mod tests {
use crate::app::App;
use crate::models::lidarr_models::{LidarrSerdeable, MetadataProfile};
use crate::models::lidarr_models::{AddArtistBody, LidarrSerdeable, MetadataProfile};
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
use crate::models::servarr_models::{QualityProfile, Tag};
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
@@ -18,6 +18,7 @@ mod tests {
#[values(
LidarrEvent::GetArtistDetails(0),
LidarrEvent::ListArtists,
LidarrEvent::AddArtist(AddArtistBody::default()),
LidarrEvent::ToggleArtistMonitoring(0)
)]
event: LidarrEvent,
+4 -1
View File
@@ -3,7 +3,7 @@ use log::info;
use super::{NetworkEvent, NetworkResource};
use crate::models::lidarr_models::{
DeleteArtistParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
AddArtistBody, DeleteArtistParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
};
use crate::models::servarr_models::{QualityProfile, Tag};
use crate::network::{Network, RequestMethod};
@@ -23,6 +23,7 @@ pub mod lidarr_network_test_utils;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum LidarrEvent {
AddArtist(AddArtistBody),
AddTag(String),
DeleteArtist(DeleteArtistParams),
DeleteTag(i64),
@@ -52,6 +53,7 @@ impl NetworkResource for LidarrEvent {
| LidarrEvent::EditArtist(_)
| LidarrEvent::GetArtistDetails(_)
| LidarrEvent::ListArtists
| LidarrEvent::AddArtist(_)
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
LidarrEvent::GetDiskSpace => "/diskspace",
LidarrEvent::GetDownloads(_) => "/queue",
@@ -132,6 +134,7 @@ impl Network<'_, '_> {
.map(LidarrSerdeable::from),
LidarrEvent::UpdateAllArtists => self.update_all_artists().await.map(LidarrSerdeable::from),
LidarrEvent::EditArtist(params) => self.edit_artist(params).await.map(LidarrSerdeable::from),
LidarrEvent::AddArtist(body) => self.add_artist(body).await.map(LidarrSerdeable::from),
}
}