diff --git a/src/cli/readarr/list_command_handler.rs b/src/cli/readarr/list_command_handler.rs index 1e198efc..265ef2ac 100644 --- a/src/cli/readarr/list_command_handler.rs +++ b/src/cli/readarr/list_command_handler.rs @@ -93,6 +93,8 @@ pub enum ReadarrListCommand { #[arg(long, help = "How many history events to fetch", default_value_t = 500)] events: u64, }, + #[command(about = "List all Readarr indexers")] + Indexers, #[command(about = "Fetch Readarr logs")] Logs { #[arg(long, help = "How many log events to fetch", default_value_t = 500)] @@ -233,6 +235,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrListCommand> for ReadarrListComman .await?; serde_json::to_string_pretty(&resp)? } + ReadarrListCommand::Indexers => { + let resp = self + .network + .handle_network_event(ReadarrEvent::GetIndexers.into()) + .await?; + serde_json::to_string_pretty(&resp)? + } ReadarrListCommand::Logs { events, output_in_log_format, diff --git a/src/cli/readarr/list_command_handler_tests.rs b/src/cli/readarr/list_command_handler_tests.rs index b74c1c38..1b11fb5b 100644 --- a/src/cli/readarr/list_command_handler_tests.rs +++ b/src/cli/readarr/list_command_handler_tests.rs @@ -29,6 +29,7 @@ mod tests { "authors", "blocklist", "disk-space", + "indexers", "metadata-profiles", "quality-profiles", "queued-events", @@ -656,6 +657,29 @@ mod tests { assert_ok!(&result); } + #[tokio::test] + async fn test_handle_list_indexers_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::(ReadarrEvent::GetIndexers.into())) + .times(1) + .returning(|_| { + Ok(Serdeable::Readarr(ReadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let list_indexers_command = ReadarrListCommand::Indexers; + + let result = + ReadarrListCommandHandler::with(&app_arc, list_indexers_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + #[tokio::test] async fn test_handle_list_logs_command() { let expected_events = 1000; diff --git a/src/network/readarr_network/indexers/mod.rs b/src/network/readarr_network/indexers/mod.rs new file mode 100644 index 00000000..3b9b8db5 --- /dev/null +++ b/src/network/readarr_network/indexers/mod.rs @@ -0,0 +1,28 @@ +use crate::models::servarr_models::Indexer; +use crate::network::readarr_network::ReadarrEvent; +use crate::network::{Network, RequestMethod}; +use anyhow::Result; +use log::info; + +#[cfg(test)] +#[path = "readarr_indexers_network_tests.rs"] +mod readarr_indexers_network_tests; + +impl Network<'_, '_> { + pub(in crate::network::readarr_network) async fn get_readarr_indexers( + &mut self, + ) -> Result> { + info!("Fetching Readarr indexers"); + let event = ReadarrEvent::GetIndexers; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), Vec>(request_props, |indexers, mut app| { + app.data.readarr_data.indexers.set_items(indexers); + }) + .await + } +} diff --git a/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs b/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs new file mode 100644 index 00000000..18f7016a --- /dev/null +++ b/src/network/readarr_network/indexers/readarr_indexers_network_tests.rs @@ -0,0 +1,81 @@ +#[cfg(test)] +mod tests { + use crate::models::readarr_models::ReadarrSerdeable; + use crate::models::servarr_models::Indexer; + use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; + use crate::network::readarr_network::ReadarrEvent; + use crate::network::readarr_network::readarr_network_test_utils::test_utils::{ + INDEXER_JSON, indexer, stale_indexer, + }; + use pretty_assertions::assert_eq; + use serde_json::{Value, json}; + + #[tokio::test] + async fn test_handle_get_readarr_indexers_event() { + let indexers_response_json = json!([serde_json::from_str::(INDEXER_JSON).unwrap()]); + let response: Vec = serde_json::from_value(indexers_response_json.clone()).unwrap(); + let (mock, app, _server) = MockServarrApi::get() + .returns(indexers_response_json) + .build_for(ReadarrEvent::GetIndexers) + .await; + { + let mut app = app.lock().await; + app.server_tabs.set_index(3); + app + .data + .readarr_data + .indexers + .set_items(vec![stale_indexer()]); + } + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetIndexers) + .await; + + mock.assert_async().await; + + let ReadarrSerdeable::Indexers(indexers) = result.unwrap() else { + panic!("Expected Indexers") + }; + + assert_eq!(indexers, response); + assert_eq!( + app.lock().await.data.readarr_data.indexers.items, + vec![indexer()] + ); + } + + #[tokio::test] + async fn test_handle_get_readarr_indexers_event_failure() { + let stale_indexers = vec![stale_indexer()]; + let indexers_response_json = json!([serde_json::from_str::(INDEXER_JSON).unwrap()]); + let (mock, app, _server) = MockServarrApi::get() + .returns(indexers_response_json) + .status(500) + .build_for(ReadarrEvent::GetIndexers) + .await; + { + let mut app = app.lock().await; + app.server_tabs.set_index(3); + app + .data + .readarr_data + .indexers + .set_items(stale_indexers.clone()); + } + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetIndexers) + .await; + + mock.assert_async().await; + + assert_err!(result); + assert_eq!( + app.lock().await.data.readarr_data.indexers.items, + stale_indexers + ); + } +} diff --git a/src/network/readarr_network/mod.rs b/src/network/readarr_network/mod.rs index 4a35ee10..91ce827a 100644 --- a/src/network/readarr_network/mod.rs +++ b/src/network/readarr_network/mod.rs @@ -13,6 +13,7 @@ use crate::network::{Network, RequestMethod}; mod blocklist; mod downloads; mod history; +mod indexers; mod library; mod root_folders; mod system; @@ -54,6 +55,7 @@ pub enum ReadarrEvent { GetDownloads(u64), GetHistory(u64), GetHostConfig, + GetIndexers, GetLogs(u64), GetMetadataProfiles, GetQualityProfiles, @@ -110,6 +112,7 @@ impl NetworkResource for ReadarrEvent { ReadarrEvent::GetHistory(_) => "/history", ReadarrEvent::GetAuthorHistory(_) | ReadarrEvent::GetBookHistory(_, _) => "/history/author", ReadarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", + ReadarrEvent::GetIndexers => "/indexer", ReadarrEvent::GetLogs(_) => "/log", ReadarrEvent::GetMetadataProfiles => "/metadataprofile", ReadarrEvent::GetQualityProfiles => "/qualityprofile", @@ -241,6 +244,10 @@ impl Network<'_, '_> { .get_readarr_host_config() .await .map(ReadarrSerdeable::from), + ReadarrEvent::GetIndexers => self + .get_readarr_indexers() + .await + .map(ReadarrSerdeable::from), ReadarrEvent::GetLogs(events) => self .get_readarr_logs(events) .await diff --git a/src/network/readarr_network/readarr_network_test_utils.rs b/src/network/readarr_network/readarr_network_test_utils.rs index 7bdee11d..b95342b5 100644 --- a/src/network/readarr_network/readarr_network_test_utils.rs +++ b/src/network/readarr_network/readarr_network_test_utils.rs @@ -7,9 +7,9 @@ pub mod test_utils { NewItemMonitorType, ReadarrHistoryData, ReadarrHistoryEventType, ReadarrHistoryItem, ReadarrRelease, }; - use crate::models::servarr_models::{Quality, QualityWrapper, RootFolder}; + use crate::models::servarr_models::{Indexer, IndexerField, Quality, QualityWrapper, RootFolder}; use chrono::DateTime; - use serde_json::Number; + use serde_json::{Number, json}; pub const AUTHOR_JSON: &str = r#"{ "id": 1, @@ -144,6 +144,29 @@ pub mod test_utils { "publishDate": "2014-05-11T14:38:20Z" }"#; + pub const INDEXER_JSON: &str = r#"{ + "enableRss": true, + "enableAutomaticSearch": true, + "enableInteractiveSearch": true, + "supportsRss": true, + "supportsSearch": true, + "protocol": "torrent", + "priority": 25, + "downloadClientId": 0, + "name": "Test Indexer", + "implementationName": "Torznab", + "implementation": "Torznab", + "configContract": "TorznabSettings", + "infoLink": "https://wiki.servarr.com/readarr/supported#torznab", + "fields": [ + { "name": "baseUrl", "value": "https://test.com" }, + { "name": "apiKey", "value": "" }, + { "name": "seedCriteria.seedRatio", "value": "1.2" } + ], + "tags": [1], + "id": 8 + }"#; + pub fn stale_author() -> Author { Author { id: 99, @@ -373,4 +396,47 @@ pub mod test_utils { records: vec![download_record()], } } + + pub fn stale_indexer() -> Indexer { + Indexer { + id: 99, + name: Some("Stale Indexer".to_owned()), + protocol: "usenet".to_owned(), + priority: 99, + ..Indexer::default() + } + } + + pub fn indexer() -> Indexer { + Indexer { + id: 8, + name: Some("Test Indexer".to_owned()), + implementation: Some("Torznab".to_owned()), + implementation_name: Some("Torznab".to_owned()), + config_contract: Some("TorznabSettings".to_owned()), + supports_rss: true, + supports_search: true, + fields: Some(vec![ + IndexerField { + name: Some("baseUrl".to_owned()), + value: Some(json!("https://test.com")), + }, + IndexerField { + name: Some("apiKey".to_owned()), + value: Some(json!("")), + }, + IndexerField { + name: Some("seedCriteria.seedRatio".to_owned()), + value: Some(json!("1.2")), + }, + ]), + enable_rss: true, + enable_automatic_search: true, + enable_interactive_search: true, + protocol: "torrent".to_owned(), + priority: 25, + download_client_id: 0, + tags: vec![Number::from(1)], + } + } } diff --git a/src/network/readarr_network/readarr_network_tests.rs b/src/network/readarr_network/readarr_network_tests.rs index 01f76e60..16ef7bad 100644 --- a/src/network/readarr_network/readarr_network_tests.rs +++ b/src/network/readarr_network/readarr_network_tests.rs @@ -47,6 +47,7 @@ mod tests { #[case(ReadarrEvent::GetAuthorHistory(1), "/history/author")] #[case(ReadarrEvent::GetBookHistory(1, 2), "/history/author")] #[case(ReadarrEvent::MarkHistoryItemAsFailed(1), "/history/failed")] + #[case(ReadarrEvent::GetIndexers, "/indexer")] #[case(ReadarrEvent::GetLogs(500), "/log")] #[case(ReadarrEvent::GetMetadataProfiles, "/metadataprofile")] #[case(ReadarrEvent::GetQualityProfiles, "/qualityprofile")]