feat: Added initial Sonarr CLI support and the initial network handler setup for the TUI
This commit is contained in:
+76
-6
@@ -8,35 +8,42 @@ use regex::Regex;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use sonarr_network::SonarrEvent;
|
||||
use strum_macros::Display;
|
||||
use tokio::select;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::{App, ServarrConfig};
|
||||
use crate::models::Serdeable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
pub mod radarr_network;
|
||||
pub mod sonarr_network;
|
||||
mod utils;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "network_tests.rs"]
|
||||
mod network_tests;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum NetworkEvent {
|
||||
Radarr(RadarrEvent),
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
#[async_trait]
|
||||
pub trait NetworkTrait {
|
||||
async fn handle_network_event(&mut self, network_event: NetworkEvent) -> Result<Serdeable>;
|
||||
}
|
||||
|
||||
pub trait NetworkResource {
|
||||
fn resource(&self) -> &'static str;
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum NetworkEvent {
|
||||
Radarr(RadarrEvent),
|
||||
Sonarr(SonarrEvent),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Network<'a, 'b> {
|
||||
client: Client,
|
||||
@@ -52,6 +59,10 @@ impl<'a, 'b> NetworkTrait for Network<'a, 'b> {
|
||||
.handle_radarr_event(radarr_event)
|
||||
.await
|
||||
.map(Serdeable::from),
|
||||
NetworkEvent::Sonarr(sonarr_event) => self
|
||||
.handle_sonarr_event(sonarr_event)
|
||||
.await
|
||||
.map(Serdeable::from),
|
||||
};
|
||||
|
||||
let mut app = self.app.lock().await;
|
||||
@@ -180,6 +191,65 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.header("X-Api-Key", api_token),
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_props_from<T, N>(
|
||||
&self,
|
||||
network_event: N,
|
||||
method: RequestMethod,
|
||||
body: Option<T>,
|
||||
path: Option<String>,
|
||||
query_params: Option<String>,
|
||||
) -> RequestProps<T>
|
||||
where
|
||||
T: Serialize + Debug,
|
||||
N: Into<NetworkEvent> + NetworkResource,
|
||||
{
|
||||
let app = self.app.lock().await;
|
||||
let resource = network_event.resource();
|
||||
let (
|
||||
ServarrConfig {
|
||||
host,
|
||||
port,
|
||||
uri,
|
||||
api_token,
|
||||
ssl_cert_path,
|
||||
},
|
||||
default_port,
|
||||
) = match network_event.into() {
|
||||
NetworkEvent::Radarr(_) => (&app.config.radarr, 7878),
|
||||
NetworkEvent::Sonarr(_) => (&app.config.sonarr, 8989),
|
||||
};
|
||||
let mut uri = if let Some(servarr_uri) = uri {
|
||||
format!("{servarr_uri}/api/v3{resource}")
|
||||
} else {
|
||||
let protocol = if ssl_cert_path.is_some() {
|
||||
"https"
|
||||
} else {
|
||||
"http"
|
||||
};
|
||||
let host = host.as_ref().unwrap();
|
||||
format!(
|
||||
"{protocol}://{host}:{}/api/v3{resource}",
|
||||
port.unwrap_or(default_port)
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(path) = path {
|
||||
uri = format!("{uri}{path}");
|
||||
}
|
||||
|
||||
if let Some(params) = query_params {
|
||||
uri = format!("{uri}?{params}");
|
||||
}
|
||||
|
||||
RequestProps {
|
||||
uri,
|
||||
method,
|
||||
body,
|
||||
api_token: api_token.to_owned(),
|
||||
ignore_status_code: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq)]
|
||||
|
||||
@@ -12,9 +12,11 @@ mod tests {
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::app::{App, AppConfig, RadarrConfig};
|
||||
use crate::app::{App, AppConfig, ServarrConfig};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::sonarr_network::SonarrEvent;
|
||||
use crate::network::NetworkResource;
|
||||
use crate::network::{Network, NetworkEvent, NetworkTrait, RequestMethod, RequestProps};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -34,12 +36,12 @@ mod tests {
|
||||
);
|
||||
let mut app = App::default();
|
||||
app.is_loading = true;
|
||||
let radarr_config = RadarrConfig {
|
||||
let radarr_config = ServarrConfig {
|
||||
host,
|
||||
api_token: String::new(),
|
||||
port,
|
||||
ssl_cert_path: None,
|
||||
..RadarrConfig::default()
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
app.config.radarr = radarr_config;
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
@@ -395,6 +397,214 @@ mod tests {
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(RadarrEvent::GetMovies, 7878)]
|
||||
#[case(SonarrEvent::ListSeries, 8989)]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_default_config(
|
||||
#[case] network_event: impl Into<NetworkEvent> + NetworkResource,
|
||||
#[case] default_port: u16,
|
||||
) {
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
let resource = network_event.resource();
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(network_event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("http://localhost:{default_port}/api/v3{resource}")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert!(request_props.api_token.is_empty());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_custom_config(
|
||||
#[values(RadarrEvent::GetMovies, SonarrEvent::ListSeries)] network_event: impl Into<NetworkEvent>
|
||||
+ NetworkResource,
|
||||
) {
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let resource = network_event.resource();
|
||||
let servarr_config = ServarrConfig {
|
||||
host: Some("192.168.0.123".to_owned()),
|
||||
port: Some(8080),
|
||||
api_token: api_token.clone(),
|
||||
ssl_cert_path: Some("/test/cert.crt".to_owned()),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.config.radarr = servarr_config.clone();
|
||||
app.config.sonarr = servarr_config;
|
||||
}
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(network_event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("https://192.168.0.123:8080/api/v3{resource}")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert_str_eq!(request_props.api_token, api_token);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_custom_config_using_uri_instead_of_host_and_port(
|
||||
#[values(RadarrEvent::GetMovies, SonarrEvent::ListSeries)] network_event: impl Into<NetworkEvent>
|
||||
+ NetworkResource,
|
||||
) {
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let resource = network_event.resource();
|
||||
let servarr_config = ServarrConfig {
|
||||
uri: Some("https://192.168.0.123:8080".to_owned()),
|
||||
api_token: api_token.clone(),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.config.radarr = servarr_config.clone();
|
||||
app.config.sonarr = servarr_config;
|
||||
}
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(network_event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("https://192.168.0.123:8080/api/v3{resource}")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert_str_eq!(request_props.api_token, api_token);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(RadarrEvent::GetMovies, 7878)]
|
||||
#[case(SonarrEvent::ListSeries, 8989)]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_default_config_with_path_and_query_params(
|
||||
#[case] network_event: impl Into<NetworkEvent> + NetworkResource,
|
||||
#[case] default_port: u16,
|
||||
) {
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
let resource = network_event.resource();
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(
|
||||
network_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some("/test".to_owned()),
|
||||
Some("id=1".to_owned()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("http://localhost:{default_port}/api/v3{resource}/test?id=1")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert!(request_props.api_token.is_empty());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_custom_config_with_path_and_query_params(
|
||||
#[values(RadarrEvent::GetMovies, SonarrEvent::ListSeries)] network_event: impl Into<NetworkEvent>
|
||||
+ NetworkResource,
|
||||
) {
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let resource = network_event.resource();
|
||||
let servarr_config = ServarrConfig {
|
||||
host: Some("192.168.0.123".to_owned()),
|
||||
port: Some(8080),
|
||||
api_token: api_token.clone(),
|
||||
ssl_cert_path: Some("/test/cert.crt".to_owned()),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.config.radarr = servarr_config.clone();
|
||||
app.config.sonarr = servarr_config;
|
||||
}
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(
|
||||
network_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some("/test".to_owned()),
|
||||
Some("id=1".to_owned()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("https://192.168.0.123:8080/api/v3{resource}/test?id=1")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert_str_eq!(request_props.api_token, api_token);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_request_props_from_custom_config_using_uri_instead_of_host_and_port_with_path_and_query_params(
|
||||
#[values(RadarrEvent::GetMovies, SonarrEvent::ListSeries)] network_event: impl Into<NetworkEvent>
|
||||
+ NetworkResource,
|
||||
) {
|
||||
let api_token = "testToken1234".to_owned();
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let resource = network_event.resource();
|
||||
let servarr_config = ServarrConfig {
|
||||
uri: Some("https://192.168.0.123:8080".to_owned()),
|
||||
api_token: api_token.clone(),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.config.radarr = servarr_config.clone();
|
||||
app.config.sonarr = servarr_config;
|
||||
}
|
||||
let network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let request_props = network
|
||||
.request_props_from(
|
||||
network_event,
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
Some("/test".to_owned()),
|
||||
Some("id=1".to_owned()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_str_eq!(
|
||||
request_props.uri,
|
||||
format!("https://192.168.0.123:8080/api/v3{resource}/test?id=1")
|
||||
);
|
||||
assert_eq!(request_props.method, RequestMethod::Get);
|
||||
assert_eq!(request_props.body, None);
|
||||
assert_str_eq!(request_props.api_token, api_token);
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
||||
struct Test {
|
||||
pub value: String,
|
||||
@@ -425,3 +635,78 @@ mod tests {
|
||||
(async_server, app_arc, server)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::network) mod test_utils {
|
||||
use std::sync::Arc;
|
||||
|
||||
use mockito::{Matcher, Mock, Server, ServerGuard};
|
||||
use serde_json::Value;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
app::{App, ServarrConfig},
|
||||
network::{NetworkEvent, NetworkResource, RequestMethod},
|
||||
};
|
||||
|
||||
pub async fn mock_servarr_api<'a>(
|
||||
method: RequestMethod,
|
||||
request_body: Option<Value>,
|
||||
response_body: Option<Value>,
|
||||
response_status: Option<usize>,
|
||||
network_event: impl Into<NetworkEvent> + NetworkResource,
|
||||
path: Option<&str>,
|
||||
query_params: Option<&str>,
|
||||
) -> (Mock, Arc<Mutex<App<'a>>>, ServerGuard) {
|
||||
let status = response_status.unwrap_or(200);
|
||||
let resource = network_event.resource();
|
||||
let mut server = Server::new_async().await;
|
||||
let mut uri = format!("/api/v3{resource}");
|
||||
|
||||
if let Some(path) = path {
|
||||
uri = format!("{uri}{path}");
|
||||
}
|
||||
|
||||
if let Some(params) = query_params {
|
||||
uri = format!("{uri}?{params}");
|
||||
}
|
||||
|
||||
let mut async_server = server
|
||||
.mock(&method.to_string().to_uppercase(), uri.as_str())
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.with_status(status);
|
||||
|
||||
if let Some(body) = request_body {
|
||||
async_server = async_server.match_body(Matcher::Json(body));
|
||||
}
|
||||
|
||||
if let Some(body) = response_body {
|
||||
async_server = async_server.with_body(body.to_string());
|
||||
}
|
||||
|
||||
async_server = async_server.create_async().await;
|
||||
|
||||
let host = Some(server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned());
|
||||
let port = Some(
|
||||
server.host_with_port().split(':').collect::<Vec<&str>>()[1]
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let mut app = App::default();
|
||||
let servarr_config = ServarrConfig {
|
||||
host,
|
||||
port,
|
||||
api_token: "test1234".to_owned(),
|
||||
..ServarrConfig::default()
|
||||
};
|
||||
|
||||
match network_event.into() {
|
||||
NetworkEvent::Radarr(_) => app.config.radarr = servarr_config,
|
||||
NetworkEvent::Sonarr(_) => app.config.sonarr = servarr_config,
|
||||
}
|
||||
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
|
||||
(async_server, app_arc, server)
|
||||
}
|
||||
}
|
||||
|
||||
+209
-302
File diff suppressed because it is too large
Load Diff
+451
-415
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
|
||||
use crate::{
|
||||
models::{
|
||||
servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
|
||||
sonarr_models::{Series, SonarrSerdeable, SystemStatus},
|
||||
Route,
|
||||
},
|
||||
network::RequestMethod,
|
||||
};
|
||||
|
||||
use super::{Network, NetworkEvent, NetworkResource};
|
||||
#[cfg(test)]
|
||||
#[path = "sonarr_network_tests.rs"]
|
||||
mod sonarr_network_tests;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum SonarrEvent {
|
||||
GetStatus,
|
||||
HealthCheck,
|
||||
ListSeries,
|
||||
}
|
||||
|
||||
impl NetworkResource for SonarrEvent {
|
||||
fn resource(&self) -> &'static str {
|
||||
match &self {
|
||||
SonarrEvent::GetStatus => "/system/status",
|
||||
SonarrEvent::HealthCheck => "/health",
|
||||
SonarrEvent::ListSeries => "/series",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SonarrEvent> for NetworkEvent {
|
||||
fn from(sonarr_event: SonarrEvent) -> Self {
|
||||
NetworkEvent::Sonarr(sonarr_event)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Network<'a, 'b> {
|
||||
pub async fn handle_sonarr_event(
|
||||
&mut self,
|
||||
sonarr_event: SonarrEvent,
|
||||
) -> Result<SonarrSerdeable> {
|
||||
match sonarr_event {
|
||||
SonarrEvent::GetStatus => self.get_sonarr_status().await.map(SonarrSerdeable::from),
|
||||
SonarrEvent::HealthCheck => self
|
||||
.get_sonarr_healthcheck()
|
||||
.await
|
||||
.map(SonarrSerdeable::from),
|
||||
SonarrEvent::ListSeries => self.list_series().await.map(SonarrSerdeable::from),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_sonarr_healthcheck(&mut self) -> Result<()> {
|
||||
info!("Performing Sonarr health check");
|
||||
let event = SonarrEvent::HealthCheck;
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_series(&mut self) -> Result<Vec<Series>> {
|
||||
info!("Fetching Sonarr library");
|
||||
let event = SonarrEvent::ListSeries;
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), Vec<Series>>(request_props, |mut series_vec, mut app| {
|
||||
if !matches!(
|
||||
app.get_current_route(),
|
||||
Route::Sonarr(ActiveSonarrBlock::SeriesSortPrompt, _)
|
||||
) {
|
||||
series_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
app.data.sonarr_data.series.set_items(series_vec);
|
||||
app.data.sonarr_data.series.apply_sorting_toggle(false);
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_sonarr_status(&mut self) -> Result<SystemStatus> {
|
||||
info!("Fetching Sonarr system status");
|
||||
let event = SonarrEvent::GetStatus;
|
||||
|
||||
let request_props = self
|
||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<(), SystemStatus>(request_props, |system_status, mut app| {
|
||||
app.data.sonarr_data.version = system_status.version;
|
||||
app.data.sonarr_data.start_time = system_status.start_time;
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use chrono::{DateTime, Utc};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use reqwest::Client;
|
||||
use rstest::rstest;
|
||||
use serde_json::json;
|
||||
use serde_json::{Number, Value};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||
use crate::models::sonarr_models::SystemStatus;
|
||||
use crate::models::{sonarr_models::SonarrSerdeable, stateful_table::SortOption};
|
||||
|
||||
use crate::{
|
||||
models::sonarr_models::{
|
||||
Rating, Season, SeasonStatistics, Series, SeriesStatistics, SeriesStatus, SeriesType,
|
||||
},
|
||||
network::{
|
||||
network_tests::test_utils::mock_servarr_api, sonarr_network::SonarrEvent, Network,
|
||||
NetworkEvent, NetworkResource, RequestMethod,
|
||||
},
|
||||
};
|
||||
|
||||
const SERIES_JSON: &str = r#"{
|
||||
"title": "Test",
|
||||
"status": "continuing",
|
||||
"ended": false,
|
||||
"overview": "Blah blah blah",
|
||||
"network": "HBO",
|
||||
"seasons": [
|
||||
{
|
||||
"seasonNumber": 1,
|
||||
"monitored": true,
|
||||
"statistics": {
|
||||
"previousAiring": "2022-10-24T01:00:00Z",
|
||||
"episodeFileCount": 10,
|
||||
"episodeCount": 10,
|
||||
"totalEpisodeCount": 10,
|
||||
"sizeOnDisk": 36708563419,
|
||||
"percentOfEpisodes": 100.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"year": 2022,
|
||||
"path": "/nfs/tv/Test",
|
||||
"qualityProfileId": 6,
|
||||
"languageProfileId": 1,
|
||||
"seasonFolder": true,
|
||||
"monitored": true,
|
||||
"runtime": 63,
|
||||
"tvdbId": 371572,
|
||||
"seriesType": "standard",
|
||||
"certification": "TV-MA",
|
||||
"genres": ["cool", "family", "fun"],
|
||||
"tags": [3],
|
||||
"ratings": {"votes": 406744, "value": 8.4},
|
||||
"statistics": {
|
||||
"seasonCount": 2,
|
||||
"episodeFileCount": 18,
|
||||
"episodeCount": 18,
|
||||
"totalEpisodeCount": 50,
|
||||
"sizeOnDisk": 63894022699,
|
||||
"percentOfEpisodes": 100.0
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_series(#[values(SonarrEvent::ListSeries)] event: SonarrEvent) {
|
||||
assert_str_eq!(event.resource(), "/series");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(SonarrEvent::HealthCheck, "/health")]
|
||||
#[case(SonarrEvent::GetStatus, "/system/status")]
|
||||
fn test_resource(#[case] event: SonarrEvent, #[case] expected_uri: String) {
|
||||
assert_str_eq!(event.resource(), expected_uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_sonarr_event() {
|
||||
assert_eq!(
|
||||
NetworkEvent::Sonarr(SonarrEvent::HealthCheck),
|
||||
NetworkEvent::from(SonarrEvent::HealthCheck)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_sonarr_healthcheck_event() {
|
||||
let (async_server, app_arc, _server) = mock_servarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
SonarrEvent::HealthCheck,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
let _ = network.handle_sonarr_event(SonarrEvent::HealthCheck).await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_series_event(#[values(true, false)] use_custom_sorting: bool) {
|
||||
let mut series_1: Value = serde_json::from_str(SERIES_JSON).unwrap();
|
||||
let mut series_2: Value = serde_json::from_str(SERIES_JSON).unwrap();
|
||||
*series_1.get_mut("id").unwrap() = json!(1);
|
||||
*series_1.get_mut("title").unwrap() = json!("z test");
|
||||
*series_2.get_mut("id").unwrap() = json!(2);
|
||||
*series_2.get_mut("title").unwrap() = json!("A test");
|
||||
let expected_series = vec![
|
||||
Series {
|
||||
id: 1,
|
||||
title: "z test".into(),
|
||||
..series()
|
||||
},
|
||||
Series {
|
||||
id: 2,
|
||||
title: "A test".into(),
|
||||
..series()
|
||||
},
|
||||
];
|
||||
let mut expected_sorted_series = vec![
|
||||
Series {
|
||||
id: 1,
|
||||
title: "z test".into(),
|
||||
..series()
|
||||
},
|
||||
Series {
|
||||
id: 2,
|
||||
title: "A test".into(),
|
||||
..series()
|
||||
},
|
||||
];
|
||||
let (async_server, app_arc, _server) = mock_servarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(json!([series_1, series_2])),
|
||||
None,
|
||||
SonarrEvent::ListSeries,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
app_arc.lock().await.data.sonarr_data.series.sort_asc = true;
|
||||
if use_custom_sorting {
|
||||
let cmp_fn = |a: &Series, b: &Series| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
};
|
||||
expected_sorted_series.sort_by(cmp_fn);
|
||||
let title_sort_option = SortOption {
|
||||
name: "Title",
|
||||
cmp_fn: Some(cmp_fn),
|
||||
};
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.sonarr_data
|
||||
.series
|
||||
.sorting(vec![title_sort_option]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
if let SonarrSerdeable::SeriesVec(series) = network
|
||||
.handle_sonarr_event(SonarrEvent::ListSeries)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
async_server.assert_async().await;
|
||||
assert_eq!(
|
||||
app_arc.lock().await.data.sonarr_data.series.items,
|
||||
expected_sorted_series
|
||||
);
|
||||
assert!(app_arc.lock().await.data.sonarr_data.series.sort_asc);
|
||||
assert_eq!(series, expected_series);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_series_event_no_op_while_user_is_selecting_sort_options() {
|
||||
let mut series_1: Value = serde_json::from_str(SERIES_JSON).unwrap();
|
||||
let mut series_2: Value = serde_json::from_str(SERIES_JSON).unwrap();
|
||||
*series_1.get_mut("id").unwrap() = json!(1);
|
||||
*series_1.get_mut("title").unwrap() = json!("z test");
|
||||
*series_2.get_mut("id").unwrap() = json!(2);
|
||||
*series_2.get_mut("title").unwrap() = json!("A test");
|
||||
let (async_server, app_arc, _server) = mock_servarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(json!([series_1, series_2])),
|
||||
None,
|
||||
SonarrEvent::ListSeries,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.push_navigation_stack(ActiveSonarrBlock::SeriesSortPrompt.into());
|
||||
app_arc.lock().await.data.sonarr_data.series.sort_asc = true;
|
||||
let cmp_fn = |a: &Series, b: &Series| {
|
||||
a.title
|
||||
.text
|
||||
.to_lowercase()
|
||||
.cmp(&b.title.text.to_lowercase())
|
||||
};
|
||||
let title_sort_option = SortOption {
|
||||
name: "Title",
|
||||
cmp_fn: Some(cmp_fn),
|
||||
};
|
||||
app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.sonarr_data
|
||||
.series
|
||||
.sorting(vec![title_sort_option]);
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
|
||||
assert!(network
|
||||
.handle_sonarr_event(SonarrEvent::ListSeries)
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert!(app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.sonarr_data
|
||||
.series
|
||||
.items
|
||||
.is_empty());
|
||||
assert!(app_arc.lock().await.data.sonarr_data.series.sort_asc);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_get_status_event() {
|
||||
let (async_server, app_arc, _server) = mock_servarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(json!({
|
||||
"version": "v1",
|
||||
"startTime": "2023-02-25T20:16:43Z"
|
||||
})),
|
||||
None,
|
||||
SonarrEvent::GetStatus,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new());
|
||||
let date_time = DateTime::from(DateTime::parse_from_rfc3339("2023-02-25T20:16:43Z").unwrap())
|
||||
as DateTime<Utc>;
|
||||
|
||||
if let SonarrSerdeable::SystemStatus(status) = network
|
||||
.handle_sonarr_event(SonarrEvent::GetStatus)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
async_server.assert_async().await;
|
||||
assert_str_eq!(app_arc.lock().await.data.sonarr_data.version, "v1");
|
||||
assert_eq!(app_arc.lock().await.data.sonarr_data.start_time, date_time);
|
||||
assert_eq!(
|
||||
status,
|
||||
SystemStatus {
|
||||
version: "v1".to_owned(),
|
||||
start_time: date_time
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn rating() -> Rating {
|
||||
Rating {
|
||||
votes: 406744,
|
||||
value: 8.4,
|
||||
}
|
||||
}
|
||||
|
||||
fn season() -> Season {
|
||||
Season {
|
||||
season_number: 1,
|
||||
monitored: true,
|
||||
statistics: season_statistics(),
|
||||
}
|
||||
}
|
||||
|
||||
fn season_statistics() -> SeasonStatistics {
|
||||
SeasonStatistics {
|
||||
previous_airing: Some(DateTime::from(
|
||||
DateTime::parse_from_rfc3339("2022-10-24T01:00:00Z").unwrap(),
|
||||
)),
|
||||
next_airing: None,
|
||||
episode_file_count: 10,
|
||||
episode_count: 10,
|
||||
total_episode_count: 10,
|
||||
size_on_disk: 36708563419,
|
||||
percent_of_episodes: 100.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn series() -> Series {
|
||||
Series {
|
||||
title: "Test".to_owned().into(),
|
||||
status: SeriesStatus::Continuing,
|
||||
ended: false,
|
||||
overview: "Blah blah blah".into(),
|
||||
network: Some("HBO".to_owned()),
|
||||
seasons: Some(vec![season()]),
|
||||
year: 2022,
|
||||
path: "/nfs/tv/Test".to_owned(),
|
||||
quality_profile_id: 6,
|
||||
language_profile_id: 1,
|
||||
season_folder: true,
|
||||
monitored: true,
|
||||
runtime: 63,
|
||||
tvdb_id: 371572,
|
||||
series_type: SeriesType::Standard,
|
||||
certification: Some("TV-MA".to_owned()),
|
||||
genres: vec!["cool".to_owned(), "family".to_owned(), "fun".to_owned()],
|
||||
tags: vec![Number::from(3)],
|
||||
ratings: rating(),
|
||||
statistics: Some(series_statistics()),
|
||||
id: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn series_statistics() -> SeriesStatistics {
|
||||
SeriesStatistics {
|
||||
season_count: 2,
|
||||
episode_file_count: 18,
|
||||
episode_count: 18,
|
||||
total_episode_count: 50,
|
||||
size_on_disk: 63894022699,
|
||||
percent_of_episodes: 100.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user