diff --git a/src/cli/readarr/trigger_automatic_search_command_handler.rs b/src/cli/readarr/trigger_automatic_search_command_handler.rs index 4722bc6a..16f2daa0 100644 --- a/src/cli/readarr/trigger_automatic_search_command_handler.rs +++ b/src/cli/readarr/trigger_automatic_search_command_handler.rs @@ -27,6 +27,15 @@ pub enum ReadarrTriggerAutomaticSearchCommand { )] author_id: i64, }, + #[command(about = "Trigger an automatic search for the book with the specified ID")] + Book { + #[arg( + long, + help = "The ID of the book you want to trigger an automatic search for", + required = true + )] + book_id: i64, + }, } impl From for Command { @@ -65,6 +74,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, ReadarrTriggerAutomaticSearchCommand> .await?; serde_json::to_string_pretty(&resp)? } + ReadarrTriggerAutomaticSearchCommand::Book { book_id } => { + let resp = self + .network + .handle_network_event(ReadarrEvent::TriggerAutomaticBookSearch(book_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(result) diff --git a/src/cli/readarr/trigger_automatic_search_command_handler_tests.rs b/src/cli/readarr/trigger_automatic_search_command_handler_tests.rs index d678fa52..bf82fe50 100644 --- a/src/cli/readarr/trigger_automatic_search_command_handler_tests.rs +++ b/src/cli/readarr/trigger_automatic_search_command_handler_tests.rs @@ -68,6 +68,45 @@ mod tests { }; assert_eq!(trigger_automatic_search_command, expected_args); } + + #[test] + fn test_trigger_automatic_book_search_requires_book_id() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "readarr", + "trigger-automatic-search", + "book", + ]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_trigger_automatic_book_search_with_book_id() { + let expected_args = ReadarrTriggerAutomaticSearchCommand::Book { book_id: 1 }; + + let result = Cli::try_parse_from([ + "managarr", + "readarr", + "trigger-automatic-search", + "book", + "--book-id", + "1", + ]); + + assert_ok!(&result); + let Some(Command::Readarr(ReadarrCommand::TriggerAutomaticSearch( + trigger_automatic_search_command, + ))) = result.unwrap().command + else { + panic!("Unexpected command type"); + }; + assert_eq!(trigger_automatic_search_command, expected_args); + } } mod handler { @@ -114,5 +153,34 @@ mod tests { assert_ok!(&result); } + + #[tokio::test] + async fn test_handle_trigger_automatic_book_search_command() { + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + ReadarrEvent::TriggerAutomaticBookSearch(1).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Readarr(ReadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let trigger_automatic_search_command = + ReadarrTriggerAutomaticSearchCommand::Book { book_id: 1 }; + + let result = ReadarrTriggerAutomaticSearchCommandHandler::with( + &app_arc, + trigger_automatic_search_command, + &mut mock_network, + ) + .handle() + .await; + + assert_ok!(&result); + } } } diff --git a/src/network/readarr_network/library/books/mod.rs b/src/network/readarr_network/library/books/mod.rs index 46ab67aa..cf696042 100644 --- a/src/network/readarr_network/library/books/mod.rs +++ b/src/network/readarr_network/library/books/mod.rs @@ -1,8 +1,11 @@ use anyhow::Result; use log::info; +use serde_json::Value; use crate::models::Route; -use crate::models::readarr_models::{Book, BookFile, Edition, ReadarrHistoryItem}; +use crate::models::readarr_models::{ + Book, BookFile, Edition, ReadarrCommandBody, ReadarrHistoryItem, +}; use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock; use crate::network::readarr_network::ReadarrEvent; use crate::network::{Network, RequestMethod}; @@ -159,4 +162,25 @@ impl Network<'_, '_> { }) .await } + + pub(in crate::network::readarr_network) async fn trigger_automatic_book_search( + &mut self, + book_id: i64, + ) -> Result { + let event = ReadarrEvent::TriggerAutomaticBookSearch(book_id); + info!("Searching indexers for book with ID: {book_id}"); + let body = ReadarrCommandBody { + name: "BookSearch".to_owned(), + book_ids: Some(vec![book_id]), + ..ReadarrCommandBody::default() + }; + + let request_props = self + .request_props_from(event, RequestMethod::Post, Some(body), None, None) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } } diff --git a/src/network/readarr_network/library/books/readarr_books_network_tests.rs b/src/network/readarr_network/library/books/readarr_books_network_tests.rs index 8dd61c00..7ad77453 100644 --- a/src/network/readarr_network/library/books/readarr_books_network_tests.rs +++ b/src/network/readarr_network/library/books/readarr_books_network_tests.rs @@ -769,4 +769,47 @@ mod tests { stale_history_items ); } + + #[tokio::test] + async fn test_handle_trigger_automatic_book_search_event() { + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "BookSearch", + "bookIds": [1] + })) + .returns(json!({})) + .build_for(ReadarrEvent::TriggerAutomaticBookSearch(1)) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::TriggerAutomaticBookSearch(1)) + .await; + + mock.assert_async().await; + assert_ok!(result); + } + + #[tokio::test] + async fn test_handle_trigger_automatic_book_search_event_failure() { + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "name": "BookSearch", + "bookIds": [1] + })) + .returns(json!({"name": "BookSearch", "status": "queued"})) + .status(500) + .build_for(ReadarrEvent::TriggerAutomaticBookSearch(1)) + .await; + app.lock().await.server_tabs.set_index(3); + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::TriggerAutomaticBookSearch(1)) + .await; + + mock.assert_async().await; + assert_err!(result); + } } diff --git a/src/network/readarr_network/mod.rs b/src/network/readarr_network/mod.rs index 01209638..893fe95a 100644 --- a/src/network/readarr_network/mod.rs +++ b/src/network/readarr_network/mod.rs @@ -56,6 +56,7 @@ pub enum ReadarrEvent { StartTask(ReadarrTaskName), ToggleAuthorMonitoring(i64), TriggerAutomaticAuthorSearch(i64), + TriggerAutomaticBookSearch(i64), UpdateAllAuthors, UpdateAndScanAuthor(i64), } @@ -75,6 +76,7 @@ impl NetworkResource for ReadarrEvent { ReadarrEvent::GetQueuedEvents | ReadarrEvent::StartTask(_) | ReadarrEvent::TriggerAutomaticAuthorSearch(_) + | ReadarrEvent::TriggerAutomaticBookSearch(_) | ReadarrEvent::UpdateAllAuthors | ReadarrEvent::UpdateAndScanAuthor(_) => "/command", ReadarrEvent::GetHostConfig | ReadarrEvent::GetSecurityConfig => "/config/host", @@ -213,6 +215,10 @@ impl Network<'_, '_> { .trigger_automatic_author_search(author_id) .await .map(ReadarrSerdeable::from), + ReadarrEvent::TriggerAutomaticBookSearch(book_id) => self + .trigger_automatic_book_search(book_id) + .await + .map(ReadarrSerdeable::from), ReadarrEvent::UpdateAllAuthors => self.update_all_authors().await.map(ReadarrSerdeable::from), ReadarrEvent::UpdateAndScanAuthor(author_id) => self .update_and_scan_author(author_id) diff --git a/src/network/readarr_network/readarr_network_tests.rs b/src/network/readarr_network/readarr_network_tests.rs index aafda302..64155d60 100644 --- a/src/network/readarr_network/readarr_network_tests.rs +++ b/src/network/readarr_network/readarr_network_tests.rs @@ -28,6 +28,7 @@ mod tests { #[case(ReadarrEvent::GetQueuedEvents, "/command")] #[case(ReadarrEvent::StartTask(Default::default()), "/command")] #[case(ReadarrEvent::TriggerAutomaticAuthorSearch(1), "/command")] + #[case(ReadarrEvent::TriggerAutomaticBookSearch(1), "/command")] #[case(ReadarrEvent::UpdateAllAuthors, "/command")] #[case(ReadarrEvent::UpdateAndScanAuthor(1), "/command")] #[case(ReadarrEvent::GetHostConfig, "/config/host")]