feat(network): Added the ability to test an individual indexer in Sonarr

This commit is contained in:
2024-11-22 17:18:47 -07:00
parent 9403bdcbcb
commit 2dce587ea8
6 changed files with 278 additions and 5 deletions
@@ -26,6 +26,7 @@ pub struct SonarrData {
pub history: StatefulTable<SonarrHistoryItem>, pub history: StatefulTable<SonarrHistoryItem>,
pub indexers: StatefulTable<Indexer>, pub indexers: StatefulTable<Indexer>,
pub indexer_settings: Option<IndexerSettings>, pub indexer_settings: Option<IndexerSettings>,
pub indexer_test_error: Option<String>,
pub logs: StatefulList<HorizontallyScrollableText>, pub logs: StatefulList<HorizontallyScrollableText>,
pub quality_profile_map: BiMap<i64, String>, pub quality_profile_map: BiMap<i64, String>,
pub queued_events: StatefulTable<QueueEvent>, pub queued_events: StatefulTable<QueueEvent>,
@@ -51,6 +52,7 @@ impl Default for SonarrData {
history: StatefulTable::default(), history: StatefulTable::default(),
indexers: StatefulTable::default(), indexers: StatefulTable::default(),
indexer_settings: None, indexer_settings: None,
indexer_test_error: None,
logs: StatefulList::default(), logs: StatefulList::default(),
quality_profile_map: BiMap::new(), quality_profile_map: BiMap::new(),
queued_events: StatefulTable::default(), queued_events: StatefulTable::default(),
@@ -41,6 +41,7 @@ mod tests {
assert!(sonarr_data.history.is_empty()); assert!(sonarr_data.history.is_empty());
assert!(sonarr_data.indexers.is_empty()); assert!(sonarr_data.indexers.is_empty());
assert!(sonarr_data.indexer_settings.is_none()); assert!(sonarr_data.indexer_settings.is_none());
assert!(sonarr_data.indexer_test_error.is_none());
assert!(sonarr_data.logs.is_empty()); assert!(sonarr_data.logs.is_empty());
assert!(sonarr_data.quality_profile_map.is_empty()); assert!(sonarr_data.quality_profile_map.is_empty());
assert!(sonarr_data.queued_events.is_empty()); assert!(sonarr_data.queued_events.is_empty());
+2 -2
View File
@@ -256,7 +256,7 @@ impl<'a, 'b> Network<'a, 'b> {
.await .await
.map(RadarrSerdeable::from), .map(RadarrSerdeable::from),
RadarrEvent::TestIndexer(indexer_id) => self RadarrEvent::TestIndexer(indexer_id) => self
.test_indexer(indexer_id) .test_radarr_indexer(indexer_id)
.await .await
.map(RadarrSerdeable::from), .map(RadarrSerdeable::from),
RadarrEvent::TestAllIndexers => self.test_all_indexers().await.map(RadarrSerdeable::from), RadarrEvent::TestAllIndexers => self.test_all_indexers().await.map(RadarrSerdeable::from),
@@ -2036,7 +2036,7 @@ impl<'a, 'b> Network<'a, 'b> {
.await .await
} }
async fn test_indexer(&mut self, indexer_id: Option<i64>) -> Result<Value> { async fn test_radarr_indexer(&mut self, indexer_id: Option<i64>) -> Result<Value> {
let detail_event = RadarrEvent::GetIndexers; let detail_event = RadarrEvent::GetIndexers;
let event = RadarrEvent::TestIndexer(None); let event = RadarrEvent::TestIndexer(None);
let id = if let Some(i_id) = indexer_id { let id = if let Some(i_id) = indexer_id {
+3 -3
View File
@@ -862,7 +862,7 @@ mod test {
} }
#[tokio::test] #[tokio::test]
async fn test_handle_test_indexer_event_error() { async fn test_handle_test_radarr_indexer_event_error() {
let indexer_details_json = json!({ let indexer_details_json = json!({
"enableRss": true, "enableRss": true,
"enableAutomaticSearch": true, "enableAutomaticSearch": true,
@@ -938,7 +938,7 @@ mod test {
} }
#[tokio::test] #[tokio::test]
async fn test_handle_test_indexer_event_success() { async fn test_handle_test_radarr_indexer_event_success() {
let indexer_details_json = json!({ let indexer_details_json = json!({
"enableRss": true, "enableRss": true,
"enableAutomaticSearch": true, "enableAutomaticSearch": true,
@@ -1007,7 +1007,7 @@ mod test {
} }
#[tokio::test] #[tokio::test]
async fn test_handle_test_indexer_event_success_uses_provided_id() { async fn test_handle_test_radarr_indexer_event_success_uses_provided_id() {
let indexer_details_json = json!({ let indexer_details_json = json!({
"enableRss": true, "enableRss": true,
"enableAutomaticSearch": true, "enableAutomaticSearch": true,
+65
View File
@@ -67,6 +67,7 @@ pub enum SonarrEvent {
ListSeries, ListSeries,
MarkHistoryItemAsFailed(i64), MarkHistoryItemAsFailed(i64),
StartTask(Option<SonarrTaskName>), StartTask(Option<SonarrTaskName>),
TestIndexer(Option<i64>),
} }
impl NetworkResource for SonarrEvent { impl NetworkResource for SonarrEvent {
@@ -98,6 +99,7 @@ impl NetworkResource for SonarrEvent {
SonarrEvent::ListSeries | SonarrEvent::GetSeriesDetails(_) => "/series", SonarrEvent::ListSeries | SonarrEvent::GetSeriesDetails(_) => "/series",
SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
SonarrEvent::StartTask(_) => "/command", SonarrEvent::StartTask(_) => "/command",
SonarrEvent::TestIndexer(_) => "/indexer/test",
} }
} }
} }
@@ -224,6 +226,10 @@ impl<'a, 'b> Network<'a, 'b> {
.start_sonarr_task(task_name) .start_sonarr_task(task_name)
.await .await
.map(SonarrSerdeable::from), .map(SonarrSerdeable::from),
SonarrEvent::TestIndexer(indexer_id) => self
.test_sonarr_indexer(indexer_id)
.await
.map(SonarrSerdeable::from),
} }
} }
@@ -1311,6 +1317,65 @@ impl<'a, 'b> Network<'a, 'b> {
.await .await
} }
async fn test_sonarr_indexer(&mut self, indexer_id: Option<i64>) -> Result<Value> {
let detail_event = SonarrEvent::GetIndexers;
let event = SonarrEvent::TestIndexer(None);
let id = if let Some(i_id) = indexer_id {
i_id
} else {
self
.app
.lock()
.await
.data
.sonarr_data
.indexers
.current_selection()
.id
};
info!("Testing Sonarr indexer with ID: {id}");
info!("Fetching indexer details for indexer with ID: {id}");
let request_props = self
.request_props_from(
detail_event,
RequestMethod::Get,
None::<()>,
Some(format!("/{id}")),
None,
)
.await;
let mut test_body: Value = Value::default();
self
.handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
test_body = detailed_indexer_body;
})
.await?;
info!("Testing indexer");
let mut request_props = self
.request_props_from(event, RequestMethod::Post, Some(test_body), None, None)
.await;
request_props.ignore_status_code = true;
self
.handle_request::<Value, Value>(request_props, |test_results, mut app| {
if test_results.as_object().is_none() {
app.data.sonarr_data.indexer_test_error = Some(
test_results.as_array().unwrap()[0]
.get("errorMessage")
.unwrap()
.to_string(),
);
};
})
.await
}
async fn extract_series_id(&mut self, series_id: Option<i64>) -> (i64, String) { async fn extract_series_id(&mut self, series_id: Option<i64>) -> (i64, String) {
let series_id = if let Some(id) = series_id { let series_id = if let Some(id) = series_id {
id id
+205
View File
@@ -5,6 +5,7 @@ mod test {
use bimap::BiMap; use bimap::BiMap;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use indoc::formatdoc; use indoc::formatdoc;
use mockito::Matcher;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use reqwest::Client; use reqwest::Client;
use rstest::rstest; use rstest::rstest;
@@ -223,6 +224,7 @@ mod test {
#[case(SonarrEvent::GetTasks, "/system/task")] #[case(SonarrEvent::GetTasks, "/system/task")]
#[case(SonarrEvent::GetUpdates, "/update")] #[case(SonarrEvent::GetUpdates, "/update")]
#[case(SonarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")] #[case(SonarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")]
#[case(SonarrEvent::TestIndexer(None), "/indexer/test")]
fn test_resource(#[case] event: SonarrEvent, #[case] expected_uri: String) { fn test_resource(#[case] event: SonarrEvent, #[case] expected_uri: String) {
assert_str_eq!(event.resource(), expected_uri); assert_str_eq!(event.resource(), expected_uri);
} }
@@ -3923,6 +3925,209 @@ mod test {
} }
} }
#[tokio::test]
async fn test_handle_test_sonarr_indexer_event_error() {
let indexer_details_json = json!({
"enableRss": true,
"enableAutomaticSearch": true,
"enableInteractiveSearch": true,
"name": "Test Indexer",
"fields": [
{
"name": "baseUrl",
"value": "https://test.com",
},
{
"name": "apiKey",
"value": "",
},
{
"name": "seedCriteria.seedRatio",
"value": "1.2",
},
],
"tags": [1],
"id": 1
});
let response_json = json!([
{
"isWarning": false,
"propertyName": "",
"errorMessage": "test failure",
"severity": "error"
}]);
let (async_details_server, app_arc, mut server) = mock_servarr_api(
RequestMethod::Get,
None,
Some(indexer_details_json.clone()),
None,
SonarrEvent::GetIndexers,
Some("/1"),
None,
)
.await;
let async_test_server = server
.mock(
"POST",
format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(),
)
.with_status(400)
.match_header("X-Api-Key", "test1234")
.match_body(Matcher::Json(indexer_details_json.clone()))
.with_body(response_json.to_string())
.create_async()
.await;
app_arc
.lock()
.await
.data
.sonarr_data
.indexers
.set_items(vec![indexer()]);
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
if let SonarrSerdeable::Value(value) = network
.handle_sonarr_event(SonarrEvent::TestIndexer(None))
.await
.unwrap()
{
async_details_server.assert_async().await;
async_test_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.sonarr_data.indexer_test_error,
Some("\"test failure\"".to_owned())
);
assert_eq!(value, response_json)
}
}
#[tokio::test]
async fn test_handle_test_sonarr_indexer_event_success() {
let indexer_details_json = json!({
"enableRss": true,
"enableAutomaticSearch": true,
"enableInteractiveSearch": true,
"name": "Test Indexer",
"fields": [
{
"name": "baseUrl",
"value": "https://test.com",
},
{
"name": "apiKey",
"value": "",
},
{
"name": "seedCriteria.seedRatio",
"value": "1.2",
},
],
"tags": [1],
"id": 1
});
let (async_details_server, app_arc, mut server) = mock_servarr_api(
RequestMethod::Get,
None,
Some(indexer_details_json.clone()),
None,
SonarrEvent::GetIndexers,
Some("/1"),
None,
)
.await;
let async_test_server = server
.mock(
"POST",
format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(),
)
.with_status(200)
.match_header("X-Api-Key", "test1234")
.match_body(Matcher::Json(indexer_details_json.clone()))
.with_body("{}")
.create_async()
.await;
app_arc
.lock()
.await
.data
.sonarr_data
.indexers
.set_items(vec![indexer()]);
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
if let SonarrSerdeable::Value(value) = network
.handle_sonarr_event(SonarrEvent::TestIndexer(None))
.await
.unwrap()
{
async_details_server.assert_async().await;
async_test_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.sonarr_data.indexer_test_error,
None
);
assert_eq!(value, json!({}));
}
}
#[tokio::test]
async fn test_handle_test_sonarr_indexer_event_success_uses_provided_id() {
let indexer_details_json = json!({
"enableRss": true,
"enableAutomaticSearch": true,
"enableInteractiveSearch": true,
"name": "Test Indexer",
"fields": [
{
"name": "baseUrl",
"value": "https://test.com",
},
{
"name": "apiKey",
"value": "",
},
{
"name": "seedCriteria.seedRatio",
"value": "1.2",
},
],
"tags": [1],
"id": 1
});
let (async_details_server, app_arc, mut server) = mock_servarr_api(
RequestMethod::Get,
None,
Some(indexer_details_json.clone()),
None,
SonarrEvent::GetIndexers,
Some("/1"),
None,
)
.await;
let async_test_server = server
.mock(
"POST",
format!("/api/v3{}", SonarrEvent::TestIndexer(None).resource()).as_str(),
)
.with_status(200)
.match_header("X-Api-Key", "test1234")
.match_body(Matcher::Json(indexer_details_json.clone()))
.with_body("{}")
.create_async()
.await;
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
if let SonarrSerdeable::Value(value) = network
.handle_sonarr_event(SonarrEvent::TestIndexer(Some(1)))
.await
.unwrap()
{
async_details_server.assert_async().await;
async_test_server.assert_async().await;
assert_eq!(value, json!({}));
}
}
#[tokio::test] #[tokio::test]
async fn test_extract_series_id() { async fn test_extract_series_id() {
let app_arc = Arc::new(Mutex::new(App::default())); let app_arc = Arc::new(Mutex::new(App::default()));