feat: Full CLI and TUI support for the Lidarr Indexers tab
This commit is contained in:
@@ -0,0 +1,901 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::lidarr_models::LidarrSerdeable;
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_models::{EditIndexerParams, Indexer, IndexerTestResult};
|
||||
use crate::network::NetworkResource;
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||
indexer, indexer_settings,
|
||||
};
|
||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||
use bimap::BiMap;
|
||||
use mockito::Matcher;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_delete_lidarr_indexer_event() {
|
||||
let (mock, app, _server) = MockServarrApi::delete()
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::DeleteIndexer(1))
|
||||
.await;
|
||||
app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexers
|
||||
.set_items(vec![indexer()]);
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::DeleteIndexer(1))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_all_indexer_settings_event() {
|
||||
let indexer_settings_json = json!({
|
||||
"id": 1,
|
||||
"minimumAge": 1,
|
||||
"maximumSize": 12345,
|
||||
"retention": 1,
|
||||
"rssSyncInterval": 60
|
||||
});
|
||||
let (mock, app, _server) = MockServarrApi::put()
|
||||
.with_request_body(indexer_settings_json)
|
||||
.build_for(LidarrEvent::EditAllIndexerSettings(indexer_settings()))
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditAllIndexerSettings(indexer_settings()))
|
||||
.await
|
||||
);
|
||||
|
||||
mock.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event() {
|
||||
let expected_edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
name: Some("Test Update".to_owned()),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: Some("https://localhost:9696/1/".to_owned()),
|
||||
api_key: Some("test1234".to_owned()),
|
||||
seed_ratio: Some("1.3".to_owned()),
|
||||
tag_input_string: Some("usenet, testing".to_owned()),
|
||||
priority: Some(0),
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"priority": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.3",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
let (mock_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let mock_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.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_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
mock_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event_does_not_overwrite_tags_vec_if_tag_input_string_is_none()
|
||||
{
|
||||
let expected_edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
name: Some("Test Update".to_owned()),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: Some("https://localhost:9696/1/".to_owned()),
|
||||
api_key: Some("test1234".to_owned()),
|
||||
seed_ratio: Some("1.3".to_owned()),
|
||||
tags: Some(vec![1, 2]),
|
||||
priority: Some(0),
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"priority": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.3",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
let (mock_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let mock_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
mock_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details()
|
||||
{
|
||||
let expected_edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
name: Some("Test Update".to_owned()),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: Some("https://localhost:9696/1/".to_owned()),
|
||||
api_key: Some("test1234".to_owned()),
|
||||
seed_ratio: Some("1.3".to_owned()),
|
||||
tag_input_string: Some("usenet, testing".to_owned()),
|
||||
priority: Some(0),
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"priority": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let (mock_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let mock_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.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_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
mock_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details()
|
||||
{
|
||||
let expected_edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
name: Some("Test Update".to_owned()),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: Some("https://localhost:9696/1/".to_owned()),
|
||||
api_key: Some("test1234".to_owned()),
|
||||
seed_ratio: Some("1.3".to_owned()),
|
||||
tag_input_string: Some("usenet, testing".to_owned()),
|
||||
priority: Some(0),
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"priority": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.3",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let (mock_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let mock_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(expected_edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.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_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
mock_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event_defaults_to_previous_values() {
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
let (mock_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json.clone())
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let mock_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(indexer_details_json))
|
||||
.create_async()
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
mock_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_lidarr_indexer_event_clears_tags_when_clear_tags_is_true() {
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
let expected_edit_indexer_body = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"priority": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [],
|
||||
"id": 1
|
||||
});
|
||||
let edit_indexer_params = EditIndexerParams {
|
||||
indexer_id: 1,
|
||||
clear_tags: true,
|
||||
..EditIndexerParams::default()
|
||||
};
|
||||
|
||||
let (async_details_server, app, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json)
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!(
|
||||
"/api/v1{}/1?forceSave=true",
|
||||
LidarrEvent::EditIndexer(edit_indexer_params.clone()).resource()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_edit_indexer_body))
|
||||
.create_async()
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_lidarr_event(LidarrEvent::EditIndexer(edit_indexer_params))
|
||||
.await
|
||||
);
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_lidarr_indexers_event() {
|
||||
let indexers_response_json = json!([{
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"supportsRss": true,
|
||||
"supportsSearch": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 25,
|
||||
"downloadClientId": 0,
|
||||
"name": "Test Indexer",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"implementationName": "Torznab",
|
||||
"implementation": "Torznab",
|
||||
"configContract": "TorznabSettings",
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
}]);
|
||||
let response: Vec<Indexer> = serde_json::from_value(indexers_response_json.clone()).unwrap();
|
||||
let (async_server, app, _server) = MockServarrApi::get()
|
||||
.returns(indexers_response_json)
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let LidarrSerdeable::Indexers(indexers) = network
|
||||
.handle_lidarr_event(LidarrEvent::GetIndexers)
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("Expected Indexers")
|
||||
};
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app.lock().await.data.lidarr_data.indexers.items,
|
||||
vec![indexer()]
|
||||
);
|
||||
assert_eq!(indexers, response);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_test_lidarr_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, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json.clone())
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let async_test_server = server
|
||||
.mock(
|
||||
"POST",
|
||||
format!("/api/v1{}", LidarrEvent::TestIndexer(1).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
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexers
|
||||
.set_items(vec![indexer()]);
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let LidarrSerdeable::Value(value) = network
|
||||
.handle_lidarr_event(LidarrEvent::TestIndexer(1))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("Expected Value")
|
||||
};
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_test_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app.lock().await.data.lidarr_data.indexer_test_errors,
|
||||
Some("\"test failure\"".to_owned())
|
||||
);
|
||||
assert_eq!(value, response_json);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_test_lidarr_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, mut server) = MockServarrApi::get()
|
||||
.returns(indexer_details_json.clone())
|
||||
.path("/1")
|
||||
.build_for(LidarrEvent::GetIndexers)
|
||||
.await;
|
||||
let async_test_server = server
|
||||
.mock(
|
||||
"POST",
|
||||
format!("/api/v1{}", LidarrEvent::TestIndexer(1).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
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexers
|
||||
.set_items(vec![indexer()]);
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let LidarrSerdeable::Value(value) = network
|
||||
.handle_lidarr_event(LidarrEvent::TestIndexer(1))
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("Expected Value")
|
||||
};
|
||||
async_details_server.assert_async().await;
|
||||
async_test_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app.lock().await.data.lidarr_data.indexer_test_errors,
|
||||
Some(String::new())
|
||||
);
|
||||
assert_eq!(value, json!({}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_test_all_lidarr_indexers_event() {
|
||||
let indexers = vec![
|
||||
Indexer {
|
||||
id: 1,
|
||||
name: Some("Test 1".to_owned()),
|
||||
..Indexer::default()
|
||||
},
|
||||
Indexer {
|
||||
id: 2,
|
||||
name: Some("Test 2".to_owned()),
|
||||
..Indexer::default()
|
||||
},
|
||||
];
|
||||
let indexer_test_results_modal_items = vec![
|
||||
IndexerTestResultModalItem {
|
||||
name: "Test 1".to_owned(),
|
||||
is_valid: true,
|
||||
validation_failures: HorizontallyScrollableText::default(),
|
||||
},
|
||||
IndexerTestResultModalItem {
|
||||
name: "Test 2".to_owned(),
|
||||
is_valid: false,
|
||||
validation_failures: "Failure for field 'test field 1': test error message, Failure for field 'test field 2': test error message 2".into(),
|
||||
},
|
||||
];
|
||||
let response_json = json!([
|
||||
{
|
||||
"id": 1,
|
||||
"isValid": true,
|
||||
"validationFailures": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"isValid": false,
|
||||
"validationFailures": [
|
||||
{
|
||||
"propertyName": "test field 1",
|
||||
"errorMessage": "test error message",
|
||||
"severity": "error"
|
||||
},
|
||||
{
|
||||
"propertyName": "test field 2",
|
||||
"errorMessage": "test error message 2",
|
||||
"severity": "error"
|
||||
},
|
||||
]
|
||||
}]);
|
||||
let response: Vec<IndexerTestResult> = serde_json::from_value(response_json.clone()).unwrap();
|
||||
let (async_server, app, _server) = MockServarrApi::post()
|
||||
.returns(response_json)
|
||||
.status(400)
|
||||
.build_for(LidarrEvent::TestAllIndexers)
|
||||
.await;
|
||||
app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexers
|
||||
.set_items(indexers);
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let LidarrSerdeable::IndexerTestResults(results) = network
|
||||
.handle_lidarr_event(LidarrEvent::TestAllIndexers)
|
||||
.await
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("Expected IndexerTestResults")
|
||||
};
|
||||
async_server.assert_async().await;
|
||||
assert_some!(&app.lock().await.data.lidarr_data.indexer_test_all_results);
|
||||
assert_eq!(
|
||||
app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexer_test_all_results
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.items,
|
||||
indexer_test_results_modal_items
|
||||
);
|
||||
assert_eq!(results, response);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_test_all_lidarr_indexers_event_sets_empty_table_on_api_error() {
|
||||
let (async_server, app, _server) = MockServarrApi::post()
|
||||
.status(500)
|
||||
.build_for(LidarrEvent::TestAllIndexers)
|
||||
.await;
|
||||
app.lock().await.server_tabs.set_index(2);
|
||||
let mut network = test_network(&app);
|
||||
|
||||
let result = network
|
||||
.handle_lidarr_event(LidarrEvent::TestAllIndexers)
|
||||
.await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert_err!(result);
|
||||
let app = app.lock().await;
|
||||
assert_some!(&app.data.lidarr_data.indexer_test_all_results);
|
||||
assert_is_empty!(
|
||||
app
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexer_test_all_results
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.items
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_models::{
|
||||
EditIndexerParams, Indexer, IndexerSettings, IndexerTestResult,
|
||||
};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::network::lidarr_network::LidarrEvent;
|
||||
use crate::network::{Network, RequestMethod};
|
||||
use anyhow::{Context, Result};
|
||||
use log::{debug, info};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "lidarr_indexers_network_tests.rs"]
|
||||
mod lidarr_indexers_network_tests;
|
||||
|
||||
impl Network<'_, '_> {
|
||||
pub(in crate::network::lidarr_network) async fn delete_lidarr_indexer(
|
||||
&mut self,
|
||||
indexer_id: i64,
|
||||
) -> Result<()> {
|
||||
let event = LidarrEvent::DeleteIndexer(indexer_id);
|
||||
info!("Deleting Lidarr indexer with id: {indexer_id}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Delete,
|
||||
None::<()>,
|
||||
Some(format!("/{indexer_id}")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn edit_all_lidarr_indexer_settings(
|
||||
&mut self,
|
||||
params: IndexerSettings,
|
||||
) -> Result<Value> {
|
||||
info!("Updating Lidarr indexer settings");
|
||||
let event = LidarrEvent::EditAllIndexerSettings(IndexerSettings::default());
|
||||
debug!("Indexer settings body: {params:?}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Put, Some(params), None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<IndexerSettings, Value>(request_props, |_, _| {})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn get_all_lidarr_indexer_settings(
|
||||
&mut self,
|
||||
) -> Result<IndexerSettings> {
|
||||
info!("Fetching Lidarr indexer settings");
|
||||
let event = LidarrEvent::GetAllIndexerSettings;
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), IndexerSettings>(request_props, |indexer_settings, mut app| {
|
||||
if app.data.lidarr_data.indexer_settings.is_none() {
|
||||
app.data.lidarr_data.indexer_settings = Some(indexer_settings);
|
||||
} else {
|
||||
debug!("Indexer Settings are being modified. Ignoring update...");
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn edit_lidarr_indexer(
|
||||
&mut self,
|
||||
mut edit_indexer_params: EditIndexerParams,
|
||||
) -> Result<()> {
|
||||
if let Some(tag_input_str) = edit_indexer_params.tag_input_string.as_ref() {
|
||||
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
|
||||
edit_indexer_params.tags = Some(tag_ids_vec);
|
||||
}
|
||||
let detail_event = LidarrEvent::GetIndexers;
|
||||
let event = LidarrEvent::EditIndexer(EditIndexerParams::default());
|
||||
let id = edit_indexer_params.indexer_id;
|
||||
info!("Updating Lidarr 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 response = String::new();
|
||||
|
||||
self
|
||||
.handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
|
||||
response = detailed_indexer_body.to_string()
|
||||
})
|
||||
.await?;
|
||||
|
||||
info!("Constructing edit indexer body");
|
||||
|
||||
let mut detailed_indexer_body: Value = serde_json::from_str(&response)?;
|
||||
|
||||
let (
|
||||
name,
|
||||
enable_rss,
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
url,
|
||||
api_key,
|
||||
seed_ratio,
|
||||
tags,
|
||||
priority,
|
||||
) = {
|
||||
let priority = detailed_indexer_body["priority"]
|
||||
.as_i64()
|
||||
.context("Failed to deserialize indexer 'priority' field")?;
|
||||
let seed_ratio_field_option = detailed_indexer_body["fields"]
|
||||
.as_array()
|
||||
.context("Failed to get indexer 'fields' array")?
|
||||
.iter()
|
||||
.find(|field| field["name"] == "seedCriteria.seedRatio");
|
||||
let name = edit_indexer_params.name.unwrap_or(
|
||||
detailed_indexer_body["name"]
|
||||
.as_str()
|
||||
.context("Failed to deserialize indexer 'name' field")?
|
||||
.to_owned(),
|
||||
);
|
||||
let enable_rss = edit_indexer_params.enable_rss.unwrap_or(
|
||||
detailed_indexer_body["enableRss"]
|
||||
.as_bool()
|
||||
.context("Failed to deserialize indexer 'enableRss' field")?,
|
||||
);
|
||||
let enable_automatic_search = edit_indexer_params.enable_automatic_search.unwrap_or(
|
||||
detailed_indexer_body["enableAutomaticSearch"]
|
||||
.as_bool()
|
||||
.context("Failed to deserialize indexer 'enableAutomaticSearch' field")?,
|
||||
);
|
||||
let enable_interactive_search = edit_indexer_params.enable_interactive_search.unwrap_or(
|
||||
detailed_indexer_body["enableInteractiveSearch"]
|
||||
.as_bool()
|
||||
.context("Failed to deserialize indexer 'enableInteractiveSearch' field")?,
|
||||
);
|
||||
let url = edit_indexer_params.url.unwrap_or(
|
||||
detailed_indexer_body["fields"]
|
||||
.as_array()
|
||||
.context("Failed to get indexer 'fields' array for baseUrl")?
|
||||
.iter()
|
||||
.find(|field| field["name"] == "baseUrl")
|
||||
.context("Field 'baseUrl' was not found in the indexer fields array")?
|
||||
.get("value")
|
||||
.unwrap_or(&json!(""))
|
||||
.as_str()
|
||||
.context("Failed to deserialize indexer 'baseUrl' value")?
|
||||
.to_owned(),
|
||||
);
|
||||
let api_key = edit_indexer_params.api_key.unwrap_or(
|
||||
detailed_indexer_body["fields"]
|
||||
.as_array()
|
||||
.context("Failed to get indexer 'fields' array for apiKey")?
|
||||
.iter()
|
||||
.find(|field| field["name"] == "apiKey")
|
||||
.context("Field 'apiKey' was not found in the indexer fields array")?
|
||||
.get("value")
|
||||
.unwrap_or(&json!(""))
|
||||
.as_str()
|
||||
.context("Failed to deserialize indexer 'apiKey' value")?
|
||||
.to_owned(),
|
||||
);
|
||||
let seed_ratio = edit_indexer_params.seed_ratio.unwrap_or_else(|| {
|
||||
if let Some(seed_ratio_field) = seed_ratio_field_option {
|
||||
return seed_ratio_field
|
||||
.get("value")
|
||||
.unwrap_or(&json!(""))
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
}
|
||||
|
||||
String::new()
|
||||
});
|
||||
let tags = if edit_indexer_params.clear_tags {
|
||||
vec![]
|
||||
} else {
|
||||
edit_indexer_params.tags.unwrap_or(
|
||||
detailed_indexer_body["tags"]
|
||||
.as_array()
|
||||
.context("Failed to get indexer 'tags' array")?
|
||||
.iter()
|
||||
.map(|item| {
|
||||
item
|
||||
.as_i64()
|
||||
.context("Failed to deserialize indexer tag ID")
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
)
|
||||
};
|
||||
let priority = edit_indexer_params.priority.unwrap_or(priority);
|
||||
|
||||
(
|
||||
name,
|
||||
enable_rss,
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
url,
|
||||
api_key,
|
||||
seed_ratio,
|
||||
tags,
|
||||
priority,
|
||||
)
|
||||
};
|
||||
|
||||
*detailed_indexer_body
|
||||
.get_mut("name")
|
||||
.context("Failed to get mutable reference to indexer 'name' field")? = json!(name);
|
||||
*detailed_indexer_body
|
||||
.get_mut("priority")
|
||||
.context("Failed to get mutable reference to indexer 'priority' field")? = json!(priority);
|
||||
*detailed_indexer_body
|
||||
.get_mut("enableRss")
|
||||
.context("Failed to get mutable reference to indexer 'enableRss' field")? = json!(enable_rss);
|
||||
*detailed_indexer_body
|
||||
.get_mut("enableAutomaticSearch")
|
||||
.context("Failed to get mutable reference to indexer 'enableAutomaticSearch' field")? =
|
||||
json!(enable_automatic_search);
|
||||
*detailed_indexer_body
|
||||
.get_mut("enableInteractiveSearch")
|
||||
.context("Failed to get mutable reference to indexer 'enableInteractiveSearch' field")? =
|
||||
json!(enable_interactive_search);
|
||||
*detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.and_then(|f| f.as_array_mut())
|
||||
.context("Failed to get mutable reference to indexer 'fields' array")?
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "baseUrl")
|
||||
.context("Failed to find 'baseUrl' field in indexer fields array")?
|
||||
.get_mut("value")
|
||||
.context("Failed to get mutable reference to 'baseUrl' value")? = json!(url);
|
||||
*detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.and_then(|f| f.as_array_mut())
|
||||
.context("Failed to get mutable reference to indexer 'fields' array for apiKey")?
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "apiKey")
|
||||
.context("Failed to find 'apiKey' field in indexer fields array")?
|
||||
.get_mut("value")
|
||||
.context("Failed to get mutable reference to 'apiKey' value")? = json!(api_key);
|
||||
*detailed_indexer_body
|
||||
.get_mut("tags")
|
||||
.context("Failed to get mutable reference to indexer 'tags' field")? = json!(tags);
|
||||
let seed_ratio_field_option = detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.and_then(|f| f.as_array_mut())
|
||||
.context("Failed to get mutable reference to indexer 'fields' array for seed ratio")?
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "seedCriteria.seedRatio");
|
||||
if let Some(seed_ratio_field) = seed_ratio_field_option {
|
||||
seed_ratio_field
|
||||
.as_object_mut()
|
||||
.context("Failed to get mutable reference to 'seedCriteria.seedRatio' object")?
|
||||
.insert("value".to_string(), json!(seed_ratio));
|
||||
}
|
||||
|
||||
debug!("Edit indexer body: {detailed_indexer_body:?}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
event,
|
||||
RequestMethod::Put,
|
||||
Some(detailed_indexer_body),
|
||||
Some(format!("/{id}")),
|
||||
Some("forceSave=true".to_owned()),
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn get_lidarr_indexers(
|
||||
&mut self,
|
||||
) -> Result<Vec<Indexer>> {
|
||||
info!("Fetching Lidarr indexers");
|
||||
let event = LidarrEvent::GetIndexers;
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), Vec<Indexer>>(request_props, |indexers, mut app| {
|
||||
app.data.lidarr_data.indexers.set_items(indexers);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn test_lidarr_indexer(
|
||||
&mut self,
|
||||
indexer_id: i64,
|
||||
) -> Result<Value> {
|
||||
let detail_event = LidarrEvent::GetIndexers;
|
||||
let event = LidarrEvent::TestIndexer(indexer_id);
|
||||
info!("Testing Lidarr indexer with ID: {indexer_id}");
|
||||
|
||||
info!("Fetching indexer details for indexer with ID: {indexer_id}");
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(
|
||||
detail_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some(format!("/{indexer_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() {
|
||||
let error_message = test_results
|
||||
.as_array()
|
||||
.and_then(|arr| arr.first())
|
||||
.and_then(|item| item.get("errorMessage"))
|
||||
.map(|msg| msg.to_string())
|
||||
.unwrap_or_else(|| "Unknown indexer test error".to_string());
|
||||
app.data.lidarr_data.indexer_test_errors = Some(error_message);
|
||||
} else {
|
||||
app.data.lidarr_data.indexer_test_errors = Some(String::new());
|
||||
};
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::network::lidarr_network) async fn test_all_lidarr_indexers(
|
||||
&mut self,
|
||||
) -> Result<Vec<IndexerTestResult>> {
|
||||
info!("Testing all Lidarr indexers");
|
||||
let event = LidarrEvent::TestAllIndexers;
|
||||
|
||||
let mut request_props = self
|
||||
.request_props_from(event, RequestMethod::Post, None, None, None)
|
||||
.await;
|
||||
request_props.ignore_status_code = true;
|
||||
|
||||
let result = self
|
||||
.handle_request::<(), Vec<IndexerTestResult>>(request_props, |test_results, mut app| {
|
||||
let mut test_all_indexer_results = StatefulTable::default();
|
||||
let indexers = app.data.lidarr_data.indexers.items.clone();
|
||||
let modal_test_results = test_results
|
||||
.iter()
|
||||
.map(|result| {
|
||||
let name = indexers
|
||||
.iter()
|
||||
.filter(|&indexer| indexer.id == result.id)
|
||||
.map(|indexer| indexer.name.clone())
|
||||
.nth(0)
|
||||
.unwrap_or_default();
|
||||
let validation_failures = result
|
||||
.validation_failures
|
||||
.iter()
|
||||
.map(|failure| {
|
||||
format!(
|
||||
"Failure for field '{}': {}",
|
||||
failure.property_name, failure.error_message
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
IndexerTestResultModalItem {
|
||||
name: name.unwrap_or_default(),
|
||||
is_valid: result.is_valid,
|
||||
validation_failures: validation_failures.into(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
test_all_indexer_results.set_items(modal_test_results);
|
||||
app.data.lidarr_data.indexer_test_all_results = Some(test_all_indexer_results);
|
||||
})
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.lidarr_data
|
||||
.indexer_test_all_results = Some(StatefulTable::default());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,13 @@ pub mod test_utils {
|
||||
LidarrHistoryEventType, LidarrHistoryItem, LidarrHistoryWrapper, Member, MetadataProfile,
|
||||
NewItemMonitorType, Ratings, SystemStatus,
|
||||
};
|
||||
use crate::models::servarr_models::{Quality, QualityProfile, QualityWrapper, RootFolder, Tag};
|
||||
use crate::models::servarr_models::IndexerSettings;
|
||||
use crate::models::servarr_models::{
|
||||
Indexer, IndexerField, Quality, QualityProfile, QualityWrapper, RootFolder, Tag,
|
||||
};
|
||||
use bimap::BiMap;
|
||||
use chrono::DateTime;
|
||||
use serde_json::Number;
|
||||
use serde_json::{Number, json};
|
||||
|
||||
pub const ADD_ARTIST_SEARCH_RESULT_JSON: &str = r#"{
|
||||
"foreignArtistId": "test-foreign-id",
|
||||
@@ -287,4 +290,47 @@ pub mod test_utils {
|
||||
..LidarrHistoryData::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indexer() -> Indexer {
|
||||
Indexer {
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
supports_rss: true,
|
||||
supports_search: true,
|
||||
protocol: "torrent".to_owned(),
|
||||
priority: 25,
|
||||
download_client_id: 0,
|
||||
name: Some("Test Indexer".to_owned()),
|
||||
implementation_name: Some("Torznab".to_owned()),
|
||||
implementation: Some("Torznab".to_owned()),
|
||||
config_contract: Some("TorznabSettings".to_owned()),
|
||||
tags: vec![Number::from(1)],
|
||||
id: 1,
|
||||
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")),
|
||||
},
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indexer_settings() -> IndexerSettings {
|
||||
IndexerSettings {
|
||||
id: 1,
|
||||
minimum_age: 1,
|
||||
retention: 1,
|
||||
maximum_size: 12345,
|
||||
rss_sync_interval: 60,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ mod tests {
|
||||
AddArtistBody, DeleteParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
use crate::models::servarr_models::{QualityProfile, Tag};
|
||||
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings, QualityProfile, Tag};
|
||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||
use crate::network::{NetworkEvent, NetworkResource, lidarr_network::LidarrEvent};
|
||||
use bimap::BiMap;
|
||||
@@ -15,6 +15,17 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_all_indexer_settings(
|
||||
#[values(
|
||||
LidarrEvent::GetAllIndexerSettings,
|
||||
LidarrEvent::EditAllIndexerSettings(IndexerSettings::default())
|
||||
)]
|
||||
event: LidarrEvent,
|
||||
) {
|
||||
assert_str_eq!(event.resource(), "/config/indexer");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_artist(
|
||||
#[values(
|
||||
@@ -37,6 +48,18 @@ mod tests {
|
||||
assert_str_eq!(event.resource(), "/queue");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_indexer(
|
||||
#[values(
|
||||
LidarrEvent::GetIndexers,
|
||||
LidarrEvent::DeleteIndexer(0),
|
||||
LidarrEvent::EditIndexer(EditIndexerParams::default())
|
||||
)]
|
||||
event: LidarrEvent,
|
||||
) {
|
||||
assert_str_eq!(event.resource(), "/indexer");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_history(#[values(LidarrEvent::GetHistory(0))] event: LidarrEvent) {
|
||||
assert_str_eq!(event.resource(), "/history");
|
||||
@@ -107,6 +130,8 @@ mod tests {
|
||||
#[case(LidarrEvent::GetTags, "/tag")]
|
||||
#[case(LidarrEvent::HealthCheck, "/health")]
|
||||
#[case(LidarrEvent::MarkHistoryItemAsFailed(0), "/history/failed")]
|
||||
#[case(LidarrEvent::TestIndexer(0), "/indexer/test")]
|
||||
#[case(LidarrEvent::TestAllIndexers, "/indexer/testall")]
|
||||
fn test_resource(#[case] event: LidarrEvent, #[case] expected_uri: &str) {
|
||||
assert_str_eq!(event.resource(), expected_uri);
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ use crate::models::lidarr_models::{
|
||||
AddArtistBody, AddLidarrRootFolderBody, DeleteParams, EditArtistParams, LidarrSerdeable,
|
||||
MetadataProfile,
|
||||
};
|
||||
use crate::models::servarr_models::{QualityProfile, Tag};
|
||||
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings, QualityProfile, Tag};
|
||||
use crate::network::{Network, RequestMethod};
|
||||
|
||||
mod downloads;
|
||||
mod history;
|
||||
mod indexers;
|
||||
mod library;
|
||||
mod root_folders;
|
||||
mod system;
|
||||
@@ -31,16 +32,21 @@ pub enum LidarrEvent {
|
||||
DeleteAlbum(DeleteParams),
|
||||
DeleteArtist(DeleteParams),
|
||||
DeleteDownload(i64),
|
||||
DeleteIndexer(i64),
|
||||
DeleteRootFolder(i64),
|
||||
DeleteTag(i64),
|
||||
EditArtist(EditArtistParams),
|
||||
EditAllIndexerSettings(IndexerSettings),
|
||||
EditIndexer(EditIndexerParams),
|
||||
GetAlbums(i64),
|
||||
GetAlbumDetails(i64),
|
||||
GetAllIndexerSettings,
|
||||
GetArtistDetails(i64),
|
||||
GetDiskSpace,
|
||||
GetDownloads(u64),
|
||||
GetHistory(u64),
|
||||
GetHostConfig,
|
||||
GetIndexers,
|
||||
MarkHistoryItemAsFailed(i64),
|
||||
GetMetadataProfiles,
|
||||
GetQualityProfiles,
|
||||
@@ -51,6 +57,8 @@ pub enum LidarrEvent {
|
||||
HealthCheck,
|
||||
ListArtists,
|
||||
SearchNewArtist(String),
|
||||
TestIndexer(i64),
|
||||
TestAllIndexers,
|
||||
ToggleAlbumMonitoring(i64),
|
||||
ToggleArtistMonitoring(i64),
|
||||
TriggerAutomaticArtistSearch(i64),
|
||||
@@ -63,6 +71,9 @@ impl NetworkResource for LidarrEvent {
|
||||
fn resource(&self) -> &'static str {
|
||||
match &self {
|
||||
LidarrEvent::AddTag(_) | LidarrEvent::DeleteTag(_) | LidarrEvent::GetTags => "/tag",
|
||||
LidarrEvent::GetAllIndexerSettings | LidarrEvent::EditAllIndexerSettings(_) => {
|
||||
"/config/indexer"
|
||||
}
|
||||
LidarrEvent::DeleteArtist(_)
|
||||
| LidarrEvent::EditArtist(_)
|
||||
| LidarrEvent::GetArtistDetails(_)
|
||||
@@ -78,6 +89,9 @@ impl NetworkResource for LidarrEvent {
|
||||
LidarrEvent::GetHistory(_) => "/history",
|
||||
LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
|
||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||
LidarrEvent::GetIndexers | LidarrEvent::DeleteIndexer(_) | LidarrEvent::EditIndexer(_) => {
|
||||
"/indexer"
|
||||
}
|
||||
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
||||
| LidarrEvent::UpdateAllArtists
|
||||
| LidarrEvent::UpdateAndScanArtist(_)
|
||||
@@ -87,6 +101,8 @@ impl NetworkResource for LidarrEvent {
|
||||
LidarrEvent::GetRootFolders
|
||||
| LidarrEvent::AddRootFolder(_)
|
||||
| LidarrEvent::DeleteRootFolder(_) => "/rootfolder",
|
||||
LidarrEvent::TestIndexer(_) => "/indexer/test",
|
||||
LidarrEvent::TestAllIndexers => "/indexer/testall",
|
||||
LidarrEvent::GetStatus => "/system/status",
|
||||
LidarrEvent::HealthCheck => "/health",
|
||||
LidarrEvent::SearchNewArtist(_) => "/artist/lookup",
|
||||
@@ -121,6 +137,18 @@ impl Network<'_, '_> {
|
||||
.delete_lidarr_download(download_id)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::EditAllIndexerSettings(params) => self
|
||||
.edit_all_lidarr_indexer_settings(params)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::EditIndexer(params) => self
|
||||
.edit_lidarr_indexer(params)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::DeleteIndexer(indexer_id) => self
|
||||
.delete_lidarr_indexer(indexer_id)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::DeleteRootFolder(root_folder_id) => self
|
||||
.delete_lidarr_root_folder(root_folder_id)
|
||||
.await
|
||||
@@ -132,6 +160,10 @@ impl Network<'_, '_> {
|
||||
LidarrEvent::GetAlbums(artist_id) => {
|
||||
self.get_albums(artist_id).await.map(LidarrSerdeable::from)
|
||||
}
|
||||
LidarrEvent::GetAllIndexerSettings => self
|
||||
.get_all_lidarr_indexer_settings()
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::GetArtistDetails(artist_id) => self
|
||||
.get_artist_details(artist_id)
|
||||
.await
|
||||
@@ -145,6 +177,7 @@ impl Network<'_, '_> {
|
||||
.get_lidarr_downloads(count)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::GetIndexers => self.get_lidarr_indexers().await.map(LidarrSerdeable::from),
|
||||
LidarrEvent::GetHistory(events) => self
|
||||
.get_lidarr_history(events)
|
||||
.await
|
||||
@@ -206,6 +239,14 @@ impl Network<'_, '_> {
|
||||
.update_lidarr_downloads()
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::TestAllIndexers => self
|
||||
.test_all_lidarr_indexers()
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
LidarrEvent::TestIndexer(indexer_id) => self
|
||||
.test_lidarr_indexer(indexer_id)
|
||||
.await
|
||||
.map(LidarrSerdeable::from),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_models::{DiskSpace, QueueEvent};
|
||||
use crate::models::servarr_models::{DiskSpace, IndexerSettings, QueueEvent};
|
||||
use chrono::DateTime;
|
||||
|
||||
pub fn diskspace() -> DiskSpace {
|
||||
@@ -9,6 +9,16 @@ pub fn diskspace() -> DiskSpace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indexer_settings() -> IndexerSettings {
|
||||
IndexerSettings {
|
||||
id: 1,
|
||||
minimum_age: 1,
|
||||
retention: 1,
|
||||
maximum_size: 12345,
|
||||
rss_sync_interval: 60,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indexer_test_result() -> IndexerTestResultModalItem {
|
||||
IndexerTestResultModalItem {
|
||||
name: "DrunkenSlug".to_owned(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
|
||||
use crate::models::servarr_models::IndexerSettings;
|
||||
use crate::models::servarr_models::{EditIndexerParams, Indexer, IndexerTestResult};
|
||||
use crate::models::sonarr_models::IndexerSettings;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::network::{Network, RequestMethod};
|
||||
|
||||
@@ -6,10 +6,9 @@ mod tests {
|
||||
use crate::models::sonarr_models::SonarrSerdeable;
|
||||
use crate::network::NetworkResource;
|
||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||
use crate::network::servarr_test_utils::indexer_settings;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::{
|
||||
indexer, indexer_settings,
|
||||
};
|
||||
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::indexer;
|
||||
use bimap::BiMap;
|
||||
use mockito::Matcher;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -31,11 +30,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::DeleteIndexer(1))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock.assert_async().await;
|
||||
@@ -58,11 +56,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditAllIndexerSettings(indexer_settings()))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock.assert_async().await;
|
||||
@@ -153,11 +150,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
@@ -248,11 +244,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
@@ -338,11 +333,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
@@ -435,11 +429,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(expected_edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
@@ -497,11 +490,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
mock_details_server.assert_async().await;
|
||||
@@ -584,11 +576,10 @@ mod tests {
|
||||
app.lock().await.server_tabs.next();
|
||||
let mut network = test_network(&app);
|
||||
|
||||
assert!(
|
||||
assert_ok!(
|
||||
network
|
||||
.handle_sonarr_event(SonarrEvent::EditIndexer(edit_indexer_params))
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
@@ -642,6 +633,7 @@ mod tests {
|
||||
else {
|
||||
panic!("Expected Indexers")
|
||||
};
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app.lock().await.data.sonarr_data.indexers.items,
|
||||
@@ -714,6 +706,7 @@ mod tests {
|
||||
else {
|
||||
panic!("Expected Value")
|
||||
};
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_test_server.assert_async().await;
|
||||
assert_eq!(
|
||||
@@ -780,6 +773,7 @@ mod tests {
|
||||
else {
|
||||
panic!("Expected Value")
|
||||
};
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_test_server.assert_async().await;
|
||||
assert_eq!(
|
||||
@@ -860,16 +854,9 @@ mod tests {
|
||||
else {
|
||||
panic!("Expected IndexerTestResults")
|
||||
};
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert!(
|
||||
app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.sonarr_data
|
||||
.indexer_test_all_results
|
||||
.is_some()
|
||||
);
|
||||
assert_some!(&app.lock().await.data.sonarr_data.indexer_test_all_results);
|
||||
assert_eq!(
|
||||
app
|
||||
.lock()
|
||||
|
||||
@@ -5,10 +5,12 @@ use serde_json::{Value, json};
|
||||
use super::{Network, NetworkEvent, NetworkResource};
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_models::{AddRootFolderBody, EditIndexerParams, Language, QualityProfile, Tag},
|
||||
servarr_models::{
|
||||
AddRootFolderBody, EditIndexerParams, IndexerSettings, Language, QualityProfile, Tag,
|
||||
},
|
||||
sonarr_models::{
|
||||
AddSeriesBody, DeleteSeriesParams, EditSeriesParams, IndexerSettings,
|
||||
SonarrReleaseDownloadBody, SonarrSerdeable, SonarrTaskName,
|
||||
AddSeriesBody, DeleteSeriesParams, EditSeriesParams, SonarrReleaseDownloadBody,
|
||||
SonarrSerdeable, SonarrTaskName,
|
||||
},
|
||||
},
|
||||
network::RequestMethod,
|
||||
|
||||
@@ -5,10 +5,9 @@ pub mod test_utils {
|
||||
};
|
||||
use crate::models::sonarr_models::{
|
||||
AddSeriesSearchResult, AddSeriesSearchResultStatistics, BlocklistItem, DownloadRecord,
|
||||
DownloadStatus, DownloadsResponse, Episode, EpisodeFile, IndexerSettings, MediaInfo, Rating,
|
||||
Season, SeasonStatistics, Series, SeriesStatistics, SeriesStatus, SeriesType,
|
||||
SonarrHistoryData, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease, SonarrTask,
|
||||
SonarrTaskName,
|
||||
DownloadStatus, DownloadsResponse, Episode, EpisodeFile, MediaInfo, Rating, Season,
|
||||
SeasonStatistics, Series, SeriesStatistics, SeriesStatus, SeriesType, SonarrHistoryData,
|
||||
SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease, SonarrTask, SonarrTaskName,
|
||||
};
|
||||
use crate::models::{HorizontallyScrollableText, ScrollableText};
|
||||
use bimap::BiMap;
|
||||
@@ -250,16 +249,6 @@ pub mod test_utils {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn indexer_settings() -> IndexerSettings {
|
||||
IndexerSettings {
|
||||
id: 1,
|
||||
minimum_age: 1,
|
||||
retention: 1,
|
||||
maximum_size: 12345,
|
||||
rss_sync_interval: 60,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language() -> Language {
|
||||
Language {
|
||||
id: 1,
|
||||
|
||||
@@ -3,11 +3,9 @@ mod test {
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
|
||||
use crate::models::servarr_models::{
|
||||
AddRootFolderBody, EditIndexerParams, Language, QualityProfile, Tag,
|
||||
};
|
||||
use crate::models::sonarr_models::{
|
||||
AddSeriesBody, EditSeriesParams, IndexerSettings, SonarrTaskName,
|
||||
AddRootFolderBody, EditIndexerParams, IndexerSettings, Language, QualityProfile, Tag,
|
||||
};
|
||||
use crate::models::sonarr_models::{AddSeriesBody, EditSeriesParams, SonarrTaskName};
|
||||
use crate::models::sonarr_models::{DeleteSeriesParams, SonarrSerdeable};
|
||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::tag;
|
||||
|
||||
Reference in New Issue
Block a user