Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d936c5cb5
|
||
|
|
7b6d595397
|
||
|
|
25da032f02
|
||
|
|
e9c323c4e8
|
||
|
|
289abbd8eb
|
||
|
|
a68de41025
|
||
|
|
9d0973352f
|
||
|
|
76c490f45b
|
||
|
|
9b4b3003b5
|
||
|
|
d678f5777a
|
No files matched your search
@@ -5,3 +5,4 @@
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 56330c025ad79db641d0eb9f429ab74e95822e1fb015b58f0e158ea674cd42a1 # shrinks to list_size = 1, page_ops = 1
|
||||
cc 29df5ff471eb388b0ffde573a959a539e4cf32acd9e1cea5187bba2268dd57f7 # shrinks to list_size = 1, filter_term = "𑛀"
|
||||
@@ -3,13 +3,16 @@ mod property_tests {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::handle_events;
|
||||
use crate::handlers::handler_test_utils::test_utils::proptest_helpers::{
|
||||
list_size, text_input_string,
|
||||
};
|
||||
use crate::models::radarr_models::Movie;
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::models::{Paginated, Scrollable};
|
||||
use crate::models::{Paginated, Scrollable, strip_non_search_characters};
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
@@ -104,13 +107,29 @@ mod property_tests {
|
||||
|
||||
#[test]
|
||||
fn test_string_input_safety(input in text_input_string()) {
|
||||
let _lowercase = input.to_lowercase();
|
||||
let _uppercase = input.to_uppercase();
|
||||
let _trimmed = input.trim();
|
||||
let _len = input.len();
|
||||
let _chars: Vec<char> = input.chars().collect();
|
||||
let mut app = App::test_default();
|
||||
app.data.radarr_data.movies.set_items(vec![Movie::default()]);
|
||||
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||
handle_events(DEFAULT_KEYBINDINGS.filter.key, &mut app);
|
||||
|
||||
prop_assert!(true);
|
||||
for character in input.chars() {
|
||||
handle_events(Key::Char(character), &mut app);
|
||||
}
|
||||
|
||||
prop_assert_eq!(app.get_current_route(), ActiveRadarrBlock::FilterMovies.into());
|
||||
prop_assert_eq!(
|
||||
app.data.radarr_data.movies.filter.as_ref().unwrap().text.as_str(),
|
||||
input.as_str()
|
||||
);
|
||||
|
||||
for _ in 0..input.chars().count() {
|
||||
handle_events(DEFAULT_KEYBINDINGS.backspace.key, &mut app);
|
||||
}
|
||||
|
||||
prop_assert_eq!(
|
||||
app.data.radarr_data.movies.filter.as_ref().unwrap().text.as_str(),
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -177,22 +196,36 @@ mod property_tests {
|
||||
..Movie::default()
|
||||
})
|
||||
.collect();
|
||||
|
||||
table.set_items(movies.clone());
|
||||
let original_size = table.items.len();
|
||||
table.filter = Some(filter_term.clone().into());
|
||||
let scrubbed_filter = strip_non_search_characters(&filter_term);
|
||||
let matches_filter = |movie: &Movie| {
|
||||
strip_non_search_characters(&movie.title.text).contains(&scrubbed_filter)
|
||||
};
|
||||
|
||||
if !filter_term.is_empty() {
|
||||
let filtered: Vec<Movie> = movies.into_iter()
|
||||
.filter(|m| m.title.text.to_lowercase().contains(&filter_term.to_lowercase()))
|
||||
.collect();
|
||||
table.set_items(filtered);
|
||||
}
|
||||
let has_match = table.apply_filter(|movie| &movie.title.text);
|
||||
|
||||
prop_assert!(table.items.len() <= original_size);
|
||||
prop_assert_eq!(&table.items, &movies);
|
||||
prop_assert!(table.filter.is_none());
|
||||
|
||||
if !table.items.is_empty() {
|
||||
let current = table.current_selection();
|
||||
prop_assert!(current.id >= 0);
|
||||
if has_match {
|
||||
let filtered_items = table.filtered_items.as_ref().unwrap();
|
||||
|
||||
prop_assert!(!filtered_items.is_empty());
|
||||
prop_assert!(filtered_items.len() <= movies.len());
|
||||
|
||||
for movie in filtered_items {
|
||||
prop_assert!(matches_filter(movie));
|
||||
}
|
||||
|
||||
for movie in movies.iter().filter(|movie| !filtered_items.contains(movie)) {
|
||||
prop_assert!(!matches_filter(movie));
|
||||
}
|
||||
} else {
|
||||
prop_assert!(table.filtered_items.is_none());
|
||||
prop_assert!(
|
||||
filter_term.is_empty() || movies.iter().all(|movie| !matches_filter(movie))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ mod tests {
|
||||
#[case(
|
||||
ActiveLidarrBlock::Downloads,
|
||||
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||
LidarrEvent::DeleteDownload(1)
|
||||
LidarrEvent::DeleteDownload(9)
|
||||
)]
|
||||
#[case(
|
||||
ActiveLidarrBlock::Downloads,
|
||||
@@ -338,7 +338,7 @@ mod tests {
|
||||
#[case(
|
||||
ActiveLidarrBlock::Downloads,
|
||||
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||
LidarrEvent::DeleteDownload(1)
|
||||
LidarrEvent::DeleteDownload(9)
|
||||
)]
|
||||
#[case(
|
||||
ActiveLidarrBlock::Downloads,
|
||||
@@ -423,7 +423,7 @@ mod tests {
|
||||
)
|
||||
.extract_download_id();
|
||||
|
||||
assert_eq!(download_id, 1);
|
||||
assert_eq!(download_id, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -4,8 +4,8 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie,
|
||||
DownloadRecord, IndexerSettings, MediaInfo, MinimumAvailability, Movie, MovieCollection,
|
||||
MovieFile, MovieMonitor, RadarrRelease, Rating, RatingsList,
|
||||
DownloadRecord, DownloadStatus, IndexerSettings, MediaInfo, MinimumAvailability, Movie,
|
||||
MovieCollection, MovieFile, MovieMonitor, RadarrRelease, Rating, RatingsList,
|
||||
};
|
||||
use crate::models::servarr_models::{
|
||||
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
||||
@@ -407,7 +407,7 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
||||
pub fn download_record() -> DownloadRecord {
|
||||
DownloadRecord {
|
||||
title: "Test Download Title".to_owned(),
|
||||
status: "downloading".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
movie_id: 1,
|
||||
size: 3543348019,
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct Artist {
|
||||
pub id: i64,
|
||||
pub artist_name: HorizontallyScrollableText,
|
||||
pub foreign_artist_id: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: ArtistStatus,
|
||||
pub overview: Option<String>,
|
||||
pub artist_type: Option<String>,
|
||||
@@ -38,6 +39,7 @@ pub struct Artist {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub metadata_profile_id: i64,
|
||||
pub monitored: bool,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub monitor_new_items: NewItemMonitorType,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<Number>,
|
||||
@@ -161,6 +163,7 @@ pub enum MonitorType {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
@@ -219,6 +222,7 @@ pub struct DownloadsResponse {
|
||||
pub struct AddArtistSearchResult {
|
||||
pub foreign_artist_id: String,
|
||||
pub artist_name: HorizontallyScrollableText,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: ArtistStatus,
|
||||
pub overview: Option<String>,
|
||||
pub artist_type: Option<String>,
|
||||
@@ -414,6 +418,7 @@ pub struct LidarrHistoryItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LidarrTask {
|
||||
pub name: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub task_name: LidarrTaskName,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub interval: i64,
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
mod tests {
|
||||
use chrono::Utc;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::lidarr_models::{
|
||||
AddArtistSearchResult, Album, AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord,
|
||||
DownloadStatus, DownloadsResponse, LidarrHistoryEventType, LidarrHistoryItem,
|
||||
LidarrHistoryWrapper, LidarrRelease, LidarrTask, MediaInfo, Member, MonitorType,
|
||||
NewItemMonitorType, Track, TrackFile,
|
||||
LidarrHistoryWrapper, LidarrRelease, LidarrTask, LidarrTaskName, MediaInfo, Member,
|
||||
MonitorType, NewItemMonitorType, Track, TrackFile,
|
||||
};
|
||||
use crate::models::servarr_models::{
|
||||
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse,
|
||||
@@ -19,6 +20,9 @@ mod tests {
|
||||
Serdeable,
|
||||
lidarr_models::{Artist, ArtistStatistics, ArtistStatus, LidarrSerdeable, Ratings},
|
||||
};
|
||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||
ADD_ARTIST_SEARCH_RESULT_JSON, ARTIST_JSON, download_record, task,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_artist_status_default() {
|
||||
@@ -946,6 +950,108 @@ mod tests {
|
||||
assert_none!(&search_result.ratings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_status_deserialization_falls_back_to_default() {
|
||||
let expected = Artist {
|
||||
status: ArtistStatus::default(),
|
||||
..serde_json::from_str(ARTIST_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Artist>(ARTIST_JSON, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Artist>(ARTIST_JSON, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_monitor_new_items_deserialization_falls_back_to_default() {
|
||||
let expected = Artist {
|
||||
monitor_new_items: NewItemMonitorType::default(),
|
||||
..serde_json::from_str(ARTIST_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Artist>(ARTIST_JSON, "monitorNewItems", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Artist>(ARTIST_JSON, "monitorNewItems", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_search_result_status_deserialization_falls_back_to_default() {
|
||||
let expected = AddArtistSearchResult {
|
||||
status: ArtistStatus::default(),
|
||||
..serde_json::from_str(ADD_ARTIST_SEARCH_RESULT_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<AddArtistSearchResult>(
|
||||
ADD_ARTIST_SEARCH_RESULT_JSON,
|
||||
"status",
|
||||
json!(2)
|
||||
),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<AddArtistSearchResult>(
|
||||
ADD_ARTIST_SEARCH_RESULT_JSON,
|
||||
"status",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_record_status_deserialization_falls_back_to_default() {
|
||||
let download_record_json = serde_json::to_string(&download_record()).unwrap();
|
||||
let expected = DownloadRecord {
|
||||
status: DownloadStatus::default(),
|
||||
..download_record()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_task_task_name_deserialization_falls_back_to_default() {
|
||||
let task_json = serde_json::to_string(&task()).unwrap();
|
||||
let expected = LidarrTask {
|
||||
task_name: LidarrTaskName::default(),
|
||||
..task()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<LidarrTask>(&task_json, "taskName", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<LidarrTask>(&task_json, "taskName", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||
fixture_json[field] = value;
|
||||
|
||||
serde_json::from_value(fixture_json).unwrap()
|
||||
}
|
||||
|
||||
fn deserialize_history_event_type(event_type: Value) -> LidarrHistoryEventType {
|
||||
let history_item_json = json!({
|
||||
"id": 1,
|
||||
|
||||
@@ -98,6 +98,7 @@ pub struct Collection {
|
||||
pub search_on_add: bool,
|
||||
pub monitored: bool,
|
||||
pub overview: Option<String>,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub minimum_availability: MinimumAvailability,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub quality_profile_id: i64,
|
||||
@@ -126,7 +127,7 @@ pub struct Credit {
|
||||
pub character: Option<String>,
|
||||
pub department: Option<String>,
|
||||
pub job: Option<String>,
|
||||
#[serde(rename(deserialize = "type"))]
|
||||
#[serde(rename = "type", deserialize_with = "super::from_json_or_default")]
|
||||
pub credit_type: CreditType,
|
||||
}
|
||||
|
||||
@@ -150,7 +151,8 @@ pub struct DeleteMovieParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
pub status: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
@@ -165,6 +167,36 @@ pub struct DownloadRecord {
|
||||
pub download_client: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum DownloadStatus {
|
||||
#[default]
|
||||
Unknown,
|
||||
Queued,
|
||||
Paused,
|
||||
Downloading,
|
||||
Completed,
|
||||
Failed,
|
||||
Warning,
|
||||
Delay,
|
||||
#[display_style(name = "Download Client Unavailable")]
|
||||
DownloadClientUnavailable,
|
||||
Fallback,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadsResponse {
|
||||
@@ -316,6 +348,7 @@ pub struct Movie {
|
||||
pub tmdb_id: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub quality_profile_id: i64,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub minimum_availability: MinimumAvailability,
|
||||
pub certification: Option<String>,
|
||||
pub tags: Vec<Number>,
|
||||
@@ -469,6 +502,7 @@ pub struct RadarrHistoryItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RadarrTask {
|
||||
pub name: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub task_name: RadarrTaskName,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub interval: i64,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::radarr_models::{
|
||||
@@ -9,15 +10,18 @@ mod tests {
|
||||
use crate::models::{
|
||||
Serdeable,
|
||||
radarr_models::{
|
||||
AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, DiskSpace,
|
||||
DownloadRecord, DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult,
|
||||
MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, QualityProfile, RadarrRelease,
|
||||
RadarrSerdeable, RadarrTask, RadarrTaskName, Tag, Update,
|
||||
AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, CreditType,
|
||||
DiskSpace, DownloadRecord, DownloadStatus, DownloadsResponse, Indexer, IndexerSettings,
|
||||
IndexerTestResult, MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor,
|
||||
QualityProfile, RadarrRelease, RadarrSerdeable, RadarrTask, RadarrTaskName, Tag, Update,
|
||||
},
|
||||
servarr_models::{
|
||||
HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig, SystemStatus,
|
||||
},
|
||||
};
|
||||
use crate::network::radarr_network::radarr_network_test_utils::test_utils::{
|
||||
MOVIE_JSON, collection, crew_credit, download_record, task,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_task_name_display() {
|
||||
@@ -66,6 +70,40 @@ mod tests {
|
||||
assert_str_eq!(MovieMonitor::None.to_display_str(), "None");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_status_display() {
|
||||
assert_str_eq!(DownloadStatus::Unknown.to_string(), "unknown");
|
||||
assert_str_eq!(DownloadStatus::Queued.to_string(), "queued");
|
||||
assert_str_eq!(DownloadStatus::Paused.to_string(), "paused");
|
||||
assert_str_eq!(DownloadStatus::Downloading.to_string(), "downloading");
|
||||
assert_str_eq!(DownloadStatus::Completed.to_string(), "completed");
|
||||
assert_str_eq!(DownloadStatus::Failed.to_string(), "failed");
|
||||
assert_str_eq!(DownloadStatus::Warning.to_string(), "warning");
|
||||
assert_str_eq!(DownloadStatus::Delay.to_string(), "delay");
|
||||
assert_str_eq!(
|
||||
DownloadStatus::DownloadClientUnavailable.to_string(),
|
||||
"downloadClientUnavailable"
|
||||
);
|
||||
assert_str_eq!(DownloadStatus::Fallback.to_string(), "fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_status_to_display_str() {
|
||||
assert_str_eq!(DownloadStatus::Unknown.to_display_str(), "Unknown");
|
||||
assert_str_eq!(DownloadStatus::Queued.to_display_str(), "Queued");
|
||||
assert_str_eq!(DownloadStatus::Paused.to_display_str(), "Paused");
|
||||
assert_str_eq!(DownloadStatus::Downloading.to_display_str(), "Downloading");
|
||||
assert_str_eq!(DownloadStatus::Completed.to_display_str(), "Completed");
|
||||
assert_str_eq!(DownloadStatus::Failed.to_display_str(), "Failed");
|
||||
assert_str_eq!(DownloadStatus::Warning.to_display_str(), "Warning");
|
||||
assert_str_eq!(DownloadStatus::Delay.to_display_str(), "Delay");
|
||||
assert_str_eq!(
|
||||
DownloadStatus::DownloadClientUnavailable.to_display_str(),
|
||||
"Download Client Unavailable"
|
||||
);
|
||||
assert_str_eq!(DownloadStatus::Fallback.to_display_str(), "Fallback");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_history_event_type_display() {
|
||||
assert_str_eq!(RadarrHistoryEventType::Unknown.to_string(), "unknown");
|
||||
@@ -222,11 +260,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_serializes_credit_type_under_the_type_key() {
|
||||
let credit_json = serde_json::to_value(crew_credit()).unwrap();
|
||||
|
||||
assert_eq!(credit_json["type"], json!("crew"));
|
||||
assert_none!(credit_json.get("creditType"));
|
||||
assert_eq!(
|
||||
serde_json::from_value::<Credit>(credit_json).unwrap(),
|
||||
crew_credit()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_record_default_indexer_value() {
|
||||
let json = r#"{
|
||||
"title": "test",
|
||||
"status": "test",
|
||||
"status": "downloading",
|
||||
"id": 0,
|
||||
"movieId": 0,
|
||||
"size": 0,
|
||||
@@ -235,7 +285,7 @@ mod tests {
|
||||
}"#;
|
||||
let expected_record = DownloadRecord {
|
||||
title: "test".to_owned(),
|
||||
status: "test".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 0,
|
||||
movie_id: 0,
|
||||
size: 0,
|
||||
@@ -614,6 +664,106 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movie_minimum_availability_deserialization_falls_back_to_default() {
|
||||
let expected = Movie {
|
||||
minimum_availability: MinimumAvailability::default(),
|
||||
..serde_json::from_str(MOVIE_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Movie>(MOVIE_JSON, "minimumAvailability", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Movie>(MOVIE_JSON, "minimumAvailability", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collection_minimum_availability_deserialization_falls_back_to_default() {
|
||||
let collection_json = serde_json::to_string(&collection()).unwrap();
|
||||
let expected = Collection {
|
||||
minimum_availability: MinimumAvailability::default(),
|
||||
..collection()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Collection>(&collection_json, "minimumAvailability", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Collection>(
|
||||
&collection_json,
|
||||
"minimumAvailability",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credit_credit_type_deserialization_falls_back_to_default() {
|
||||
let credit_json = serde_json::to_string(&crew_credit()).unwrap();
|
||||
let expected = Credit {
|
||||
credit_type: CreditType::default(),
|
||||
..crew_credit()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Credit>(&credit_json, "type", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Credit>(&credit_json, "type", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_radarr_task_task_name_deserialization_falls_back_to_default() {
|
||||
let task_json = serde_json::to_string(&task()).unwrap();
|
||||
let expected = RadarrTask {
|
||||
task_name: RadarrTaskName::default(),
|
||||
..task()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<RadarrTask>(&task_json, "taskName", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<RadarrTask>(&task_json, "taskName", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_record_status_deserialization_falls_back_to_default() {
|
||||
let download_record_json = serde_json::to_string(&download_record()).unwrap();
|
||||
let expected = DownloadRecord {
|
||||
status: DownloadStatus::default(),
|
||||
..download_record()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||
fixture_json[field] = value;
|
||||
|
||||
serde_json::from_value(fixture_json).unwrap()
|
||||
}
|
||||
|
||||
fn deserialize_history_event_type(event_type: Value) -> RadarrHistoryEventType {
|
||||
let history_item_json = json!({
|
||||
"id": 1,
|
||||
|
||||
@@ -27,6 +27,7 @@ pub struct Author {
|
||||
pub id: i64,
|
||||
pub author_name: HorizontallyScrollableText,
|
||||
pub foreign_author_id: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: AuthorStatus,
|
||||
pub overview: Option<String>,
|
||||
pub author_type: Option<String>,
|
||||
@@ -38,6 +39,7 @@ pub struct Author {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub metadata_profile_id: i64,
|
||||
pub monitored: bool,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub monitor_new_items: NewItemMonitorType,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<Number>,
|
||||
@@ -152,6 +154,7 @@ pub enum MonitorType {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
@@ -210,6 +213,7 @@ pub struct DownloadsResponse {
|
||||
pub struct AddAuthorSearchResult {
|
||||
pub foreign_author_id: String,
|
||||
pub author_name: HorizontallyScrollableText,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: AuthorStatus,
|
||||
#[serde(default)]
|
||||
pub ended: bool,
|
||||
@@ -423,6 +427,7 @@ pub struct ReadarrHistoryItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadarrTask {
|
||||
pub name: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub task_name: ReadarrTaskName,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub interval: i64,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
mod tests {
|
||||
use chrono::Utc;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::readarr_models::{
|
||||
@@ -19,6 +20,9 @@ mod tests {
|
||||
Serdeable,
|
||||
readarr_models::{Author, AuthorStatistics, AuthorStatus, Ratings, ReadarrSerdeable},
|
||||
};
|
||||
use crate::network::readarr_network::readarr_network_test_utils::test_utils::{
|
||||
ADD_AUTHOR_SEARCH_RESULT_JSON, AUTHOR_JSON, download_record,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_author_status_default() {
|
||||
@@ -1140,6 +1144,113 @@ mod tests {
|
||||
assert_none!(&search_result.ratings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_status_deserialization_falls_back_to_default() {
|
||||
let expected = Author {
|
||||
status: AuthorStatus::default(),
|
||||
..serde_json::from_str(AUTHOR_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Author>(AUTHOR_JSON, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Author>(AUTHOR_JSON, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_monitor_new_items_deserialization_falls_back_to_default() {
|
||||
let expected = Author {
|
||||
monitor_new_items: NewItemMonitorType::default(),
|
||||
..serde_json::from_str(AUTHOR_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Author>(AUTHOR_JSON, "monitorNewItems", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Author>(AUTHOR_JSON, "monitorNewItems", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_author_search_result_status_deserialization_falls_back_to_default() {
|
||||
let expected = AddAuthorSearchResult {
|
||||
status: AuthorStatus::default(),
|
||||
..serde_json::from_str(ADD_AUTHOR_SEARCH_RESULT_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<AddAuthorSearchResult>(
|
||||
ADD_AUTHOR_SEARCH_RESULT_JSON,
|
||||
"status",
|
||||
json!(2)
|
||||
),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<AddAuthorSearchResult>(
|
||||
ADD_AUTHOR_SEARCH_RESULT_JSON,
|
||||
"status",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_record_status_deserialization_falls_back_to_default() {
|
||||
let download_record_json = serde_json::to_string(&download_record()).unwrap();
|
||||
let expected = DownloadRecord {
|
||||
status: DownloadStatus::default(),
|
||||
..download_record()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readarr_task_task_name_deserialization_falls_back_to_default() {
|
||||
let task = ReadarrTask {
|
||||
name: "Backup".to_owned(),
|
||||
task_name: ReadarrTaskName::Backup,
|
||||
..ReadarrTask::default()
|
||||
};
|
||||
let task_json = serde_json::to_string(&task).unwrap();
|
||||
let expected = ReadarrTask {
|
||||
task_name: ReadarrTaskName::default(),
|
||||
..task
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<ReadarrTask>(&task_json, "taskName", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<ReadarrTask>(&task_json, "taskName", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||
fixture_json[field] = value;
|
||||
|
||||
serde_json::from_value(fixture_json).unwrap()
|
||||
}
|
||||
|
||||
fn deserialize_history_event_type(event_type: Value) -> ReadarrHistoryEventType {
|
||||
let history_item_json = json!({
|
||||
"id": 1,
|
||||
|
||||
@@ -31,8 +31,8 @@ use strum::EnumIter;
|
||||
use {
|
||||
super::modals::EditionDetailsModal,
|
||||
crate::models::readarr_models::{
|
||||
AuthorStatus, BookFile, DownloadStatus, Edition, MediaInfo, MonitorType, NewItemMonitorType,
|
||||
Ratings, ReadarrHistoryEventType, ReadarrTaskName,
|
||||
AuthorStatus, BookFile, BookStatistics, DownloadStatus, Edition, MediaInfo, MonitorType,
|
||||
NewItemMonitorType, Ratings, ReadarrHistoryEventType, ReadarrTaskName,
|
||||
},
|
||||
crate::models::servarr_models::{IndexerField, Quality, QualityWrapper},
|
||||
crate::models::stateful_table::SortOption,
|
||||
@@ -292,6 +292,12 @@ impl ReadarrData<'_> {
|
||||
any_edition_ok: true,
|
||||
page_count: Some(288),
|
||||
release_date: Some(date),
|
||||
statistics: Some(BookStatistics {
|
||||
book_file_count: 3,
|
||||
total_book_count: 7,
|
||||
size_on_disk: 2469606195,
|
||||
percent_of_books: 42.86,
|
||||
}),
|
||||
..Book::default()
|
||||
};
|
||||
let readarr_history_item = ReadarrHistoryItem {
|
||||
|
||||
@@ -315,7 +315,9 @@ pub struct RootFolder {
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SecurityConfig {
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub authentication_method: AuthenticationMethod,
|
||||
#[serde(default, deserialize_with = "super::from_json_or_default")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub authentication_required: Option<AuthenticationRequired>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -323,6 +325,7 @@ pub struct SecurityConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub password: Option<String>,
|
||||
pub api_key: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub certificate_validation: CertificateValidation,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::servarr_models::{
|
||||
AuthenticationMethod, AuthenticationRequired, CertificateValidation, Indexer, QualityProfile,
|
||||
Update, UpdateChanges,
|
||||
SecurityConfig, Update, UpdateChanges,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -90,4 +92,118 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_security_config_authentication_method_deserialization_falls_back_to_default() {
|
||||
let security_config_json = serde_json::to_string(&security_config()).unwrap();
|
||||
let expected = SecurityConfig {
|
||||
authentication_method: AuthenticationMethod::default(),
|
||||
..security_config()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"authenticationMethod",
|
||||
json!(2)
|
||||
),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"authenticationMethod",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_security_config_authentication_required_deserialization_falls_back_to_none() {
|
||||
let security_config_json = serde_json::to_string(&security_config()).unwrap();
|
||||
let expected = SecurityConfig {
|
||||
authentication_required: None,
|
||||
..security_config()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"authenticationRequired",
|
||||
json!(2)
|
||||
),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"authenticationRequired",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_security_config_deserializes_when_authentication_required_is_absent() {
|
||||
let mut security_config_json = serde_json::to_value(security_config()).unwrap();
|
||||
security_config_json
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.remove("authenticationRequired");
|
||||
let expected = SecurityConfig {
|
||||
authentication_required: None,
|
||||
..security_config()
|
||||
};
|
||||
|
||||
let deserialized = serde_json::from_value::<SecurityConfig>(security_config_json);
|
||||
|
||||
assert_ok!(&deserialized);
|
||||
assert_eq!(deserialized.unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_security_config_certificate_validation_deserialization_falls_back_to_default() {
|
||||
let security_config_json = serde_json::to_string(&security_config()).unwrap();
|
||||
let expected = SecurityConfig {
|
||||
certificate_validation: CertificateValidation::default(),
|
||||
..security_config()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"certificateValidation",
|
||||
json!(2)
|
||||
),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SecurityConfig>(
|
||||
&security_config_json,
|
||||
"certificateValidation",
|
||||
json!("notAThing")
|
||||
),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||
fixture_json[field] = value;
|
||||
|
||||
serde_json::from_value(fixture_json).unwrap()
|
||||
}
|
||||
|
||||
fn security_config() -> SecurityConfig {
|
||||
SecurityConfig {
|
||||
authentication_method: AuthenticationMethod::Forms,
|
||||
authentication_required: Some(AuthenticationRequired::Enabled),
|
||||
username: Some("admin".to_owned()),
|
||||
password: Some("password".to_owned()),
|
||||
api_key: "test-api-key".to_owned(),
|
||||
certificate_validation: CertificateValidation::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,7 @@ pub struct DeleteSeriesParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
pub title: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: DownloadStatus,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub id: i64,
|
||||
@@ -314,12 +315,14 @@ pub struct Series {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub year: i64,
|
||||
pub monitored: bool,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub series_type: SeriesType,
|
||||
pub path: String,
|
||||
pub genres: Vec<String>,
|
||||
pub tags: Vec<Number>,
|
||||
pub ratings: Rating,
|
||||
pub ended: bool,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub status: SeriesStatus,
|
||||
pub overview: Option<String>,
|
||||
pub network: Option<String>,
|
||||
@@ -556,6 +559,7 @@ pub struct SonarrReleaseDownloadBody {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SonarrTask {
|
||||
pub name: String,
|
||||
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||
pub task_name: SonarrTaskName,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub interval: i64,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::models::{
|
||||
@@ -17,6 +18,9 @@ mod tests {
|
||||
SonarrTaskName,
|
||||
},
|
||||
};
|
||||
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::{
|
||||
SERIES_JSON, download_record, task,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_episode_display() {
|
||||
@@ -698,6 +702,83 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_series_series_type_deserialization_falls_back_to_default() {
|
||||
let expected = Series {
|
||||
series_type: SeriesType::default(),
|
||||
..serde_json::from_str(SERIES_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Series>(SERIES_JSON, "seriesType", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Series>(SERIES_JSON, "seriesType", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_series_status_deserialization_falls_back_to_default() {
|
||||
let expected = Series {
|
||||
status: SeriesStatus::default(),
|
||||
..serde_json::from_str(SERIES_JSON).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Series>(SERIES_JSON, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<Series>(SERIES_JSON, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_download_record_status_deserialization_falls_back_to_default() {
|
||||
let download_record_json = serde_json::to_string(&download_record()).unwrap();
|
||||
let expected = DownloadRecord {
|
||||
status: DownloadStatus::default(),
|
||||
..download_record()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<DownloadRecord>(&download_record_json, "status", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sonarr_task_task_name_deserialization_falls_back_to_default() {
|
||||
let task_json = serde_json::to_string(&task()).unwrap();
|
||||
let expected = SonarrTask {
|
||||
task_name: SonarrTaskName::default(),
|
||||
..task()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SonarrTask>(&task_json, "taskName", json!(2)),
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
deserialize_with_field::<SonarrTask>(&task_json, "taskName", json!("notAThing")),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||
fixture_json[field] = value;
|
||||
|
||||
serde_json::from_value(fixture_json).unwrap()
|
||||
}
|
||||
|
||||
fn deserialize_history_event_type(event_type: Value) -> SonarrHistoryEventType {
|
||||
let history_item_json = json!({
|
||||
"id": 1,
|
||||
|
||||
@@ -184,9 +184,9 @@ pub mod test_utils {
|
||||
DownloadRecord {
|
||||
title: "Test download title".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
id: 9,
|
||||
album_id: Some(Number::from(1i64)),
|
||||
artist_id: Some(Number::from(1i64)),
|
||||
artist_id: Some(Number::from(3i64)),
|
||||
size: 3543348019f64,
|
||||
sizeleft: 1771674009f64,
|
||||
output_path: Some(HorizontallyScrollableText::from("/nfs/music/alex/album")),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieBody, AddMovieSearchResult, Credit, CreditType, DeleteMovieParams, DownloadRecord,
|
||||
EditMovieParams, Movie, MovieCommandBody, MovieHistoryItem, RadarrRelease,
|
||||
DownloadStatus, EditMovieParams, Movie, MovieCommandBody, MovieHistoryItem, RadarrRelease,
|
||||
RadarrReleaseDownloadBody,
|
||||
};
|
||||
use crate::models::servarr_data::Notification;
|
||||
@@ -664,11 +664,11 @@ pub(in crate::network::radarr_network::library) fn get_movie_status(
|
||||
.iter()
|
||||
.find(|&download| download.movie_id == movie_id)
|
||||
{
|
||||
if download.status == "downloading" {
|
||||
if download.status == DownloadStatus::Downloading {
|
||||
return "Downloading".to_owned();
|
||||
}
|
||||
|
||||
if download.status == "completed" {
|
||||
if download.status == DownloadStatus::Completed {
|
||||
return "Awaiting Import".to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, EditMovieParams,
|
||||
MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, RadarrReleaseDownloadBody,
|
||||
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, DownloadStatus,
|
||||
EditMovieParams, MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor,
|
||||
RadarrReleaseDownloadBody,
|
||||
};
|
||||
use crate::models::servarr_data::Notification;
|
||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||
@@ -1199,7 +1200,7 @@ mod tests {
|
||||
false,
|
||||
&[DownloadRecord {
|
||||
movie_id: 1,
|
||||
status: "downloading".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
..DownloadRecord::default()
|
||||
}],
|
||||
1
|
||||
@@ -1215,7 +1216,7 @@ mod tests {
|
||||
false,
|
||||
&[DownloadRecord {
|
||||
movie_id: 1,
|
||||
status: "completed".to_owned(),
|
||||
status: DownloadStatus::Completed,
|
||||
..DownloadRecord::default()
|
||||
}],
|
||||
1
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
pub mod test_utils {
|
||||
use crate::models::radarr_models::{
|
||||
AddMovieSearchResult, BlocklistItem, BlocklistItemMovie, Collection, CollectionMovie, Credit,
|
||||
CreditType, DownloadRecord, DownloadsResponse, IndexerSettings, MediaInfo, MinimumAvailability,
|
||||
Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrHistoryData, RadarrHistoryEventType,
|
||||
RadarrHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName, Rating, RatingsList,
|
||||
CreditType, DownloadRecord, DownloadStatus, DownloadsResponse, IndexerSettings, MediaInfo,
|
||||
MinimumAvailability, Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrHistoryData,
|
||||
RadarrHistoryEventType, RadarrHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName, Rating,
|
||||
RatingsList,
|
||||
};
|
||||
use crate::models::servarr_models::{
|
||||
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
||||
@@ -339,7 +340,7 @@ pub mod test_utils {
|
||||
pub fn download_record() -> DownloadRecord {
|
||||
DownloadRecord {
|
||||
title: "Test Download Title".to_owned(),
|
||||
status: "downloading".to_owned(),
|
||||
status: DownloadStatus::Downloading,
|
||||
id: 1,
|
||||
movie_id: 1,
|
||||
size: 3543348019,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrRelease, Track};
|
||||
use crate::models::lidarr_models::{
|
||||
DownloadRecord, DownloadStatus, LidarrHistoryItem, LidarrRelease, Track,
|
||||
};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ALBUM_DETAILS_BLOCKS, ActiveLidarrBlock};
|
||||
use crate::ui::lidarr_ui::library::track_details_ui::TrackDetailsUi;
|
||||
use crate::ui::lidarr_ui::lidarr_ui_utils::create_history_event_details;
|
||||
@@ -150,6 +152,7 @@ fn draw_tracks_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.expect("Album details modal is unpopulated")
|
||||
.tracks,
|
||||
);
|
||||
let downloads_vec = &app.data.lidarr_data.downloads.items;
|
||||
|
||||
let track_row_mapping = |track: &Track| {
|
||||
let Track {
|
||||
@@ -187,6 +190,7 @@ fn draw_tracks_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let duration_str = format!("{mins}:{secs:02}");
|
||||
|
||||
decorate_track_row_with_style(
|
||||
downloads_vec,
|
||||
track,
|
||||
Row::new(vec![
|
||||
Cell::from(track_number.clone()),
|
||||
@@ -221,12 +225,35 @@ fn draw_tracks_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
}
|
||||
}
|
||||
|
||||
fn decorate_track_row_with_style<'a>(track: &Track, row: Row<'a>) -> Row<'a> {
|
||||
if track.has_file {
|
||||
row.downloaded()
|
||||
} else {
|
||||
row.missing()
|
||||
fn decorate_track_row_with_style<'a>(
|
||||
downloads_vec: &[DownloadRecord],
|
||||
track: &Track,
|
||||
row: Row<'a>,
|
||||
) -> Row<'a> {
|
||||
if !track.has_file {
|
||||
let default_album_id = Number::from(-1i64);
|
||||
if let Some(download) = downloads_vec.iter().find(|&download| {
|
||||
download
|
||||
.album_id
|
||||
.as_ref()
|
||||
.unwrap_or(&default_album_id)
|
||||
.as_i64()
|
||||
.unwrap()
|
||||
== track.album_id
|
||||
}) {
|
||||
if download.status == DownloadStatus::Downloading {
|
||||
return row.downloading();
|
||||
}
|
||||
|
||||
if download.status == DownloadStatus::Completed {
|
||||
return row.awaiting_import();
|
||||
}
|
||||
}
|
||||
|
||||
return row.missing();
|
||||
}
|
||||
|
||||
row.downloaded()
|
||||
}
|
||||
|
||||
fn draw_album_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use serde_json::Number;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::lidarr_models::Track;
|
||||
use crate::models::lidarr_models::{DownloadRecord, DownloadStatus, Track};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ALBUM_DETAILS_BLOCKS, ActiveLidarrBlock, TRACK_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::lidarr_ui::library::album_details_ui::{
|
||||
AlbumDetailsUi, decorate_track_row_with_style,
|
||||
@@ -39,7 +41,7 @@ mod tests {
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&track, row.clone());
|
||||
let style = decorate_track_row_with_style(&[], &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
@@ -52,11 +54,89 @@ mod tests {
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&track, row.clone());
|
||||
let style = decorate_track_row_with_style(&[], &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_track_row_with_style_downloading_when_track_has_no_file_and_album_is_downloading()
|
||||
{
|
||||
let downloads_vec = vec![album_download(7, DownloadStatus::Downloading)];
|
||||
let track = Track {
|
||||
has_file: false,
|
||||
album_id: 7,
|
||||
..Track::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&downloads_vec, &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloading());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_track_row_with_style_awaiting_import_when_track_has_no_file_and_album_download_is_completed()
|
||||
{
|
||||
let downloads_vec = vec![album_download(7, DownloadStatus::Completed)];
|
||||
let track = Track {
|
||||
has_file: false,
|
||||
album_id: 7,
|
||||
..Track::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&downloads_vec, &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.awaiting_import());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_track_row_with_style_missing_when_album_download_is_neither_downloading_nor_completed()
|
||||
{
|
||||
let downloads_vec = vec![album_download(7, DownloadStatus::Queued)];
|
||||
let track = Track {
|
||||
has_file: false,
|
||||
album_id: 7,
|
||||
..Track::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&downloads_vec, &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_track_row_with_style_missing_when_only_a_different_album_is_downloading() {
|
||||
let downloads_vec = vec![album_download(8, DownloadStatus::Downloading)];
|
||||
let track = Track {
|
||||
has_file: false,
|
||||
album_id: 7,
|
||||
..Track::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&downloads_vec, &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_track_row_with_style_downloaded_when_track_has_file_and_album_is_downloading() {
|
||||
let downloads_vec = vec![album_download(7, DownloadStatus::Downloading)];
|
||||
let track = Track {
|
||||
has_file: true,
|
||||
album_id: 7,
|
||||
..Track::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_track_row_with_style(&downloads_vec, &track, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||
use rstest::rstest;
|
||||
@@ -177,4 +257,13 @@ mod tests {
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
}
|
||||
|
||||
fn album_download(album_id: i64, status: DownloadStatus) -> DownloadRecord {
|
||||
DownloadRecord {
|
||||
status,
|
||||
album_id: Some(Number::from(album_id)),
|
||||
artist_id: Some(Number::from(3i64)),
|
||||
..download_record()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,13 +279,16 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.release_date
|
||||
.map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string());
|
||||
let track_count = album.statistics.as_ref().map_or_else(
|
||||
|| "0/0".to_owned(),
|
||||
|| "N/A".to_owned(),
|
||||
|s| format!("{}/{}", s.track_file_count, s.total_track_count),
|
||||
);
|
||||
let size = album
|
||||
.statistics
|
||||
.as_ref()
|
||||
.map_or(0f64, |s| convert_to_gb(s.size_on_disk));
|
||||
let size = album.statistics.as_ref().map_or_else(
|
||||
|| "N/A".to_owned(),
|
||||
|s| {
|
||||
let size = convert_to_gb(s.size_on_disk);
|
||||
format!("{size:.2} GB")
|
||||
},
|
||||
);
|
||||
let duration_mins = album.duration / 60000;
|
||||
|
||||
decorate_album_row_with_style(
|
||||
@@ -297,7 +300,7 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
Cell::from(track_count),
|
||||
Cell::from(format!("{duration_mins} min")),
|
||||
Cell::from(release_date),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
Cell::from(size),
|
||||
]),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -249,6 +249,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_ui_renders_album_without_statistics() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.lidarr_data.albums.items[0].statistics = None;
|
||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
ArtistDetailsUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_artist_details_ui_renders_delete_album_prompt_over_artist_details() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
|
||||
|
||||
|
||||
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Artist: Alex │
|
||||
│Overview: some interesting description of the artist She was born in Madison, Wisconsin: a city she has never really left. Her first album took her│
|
||||
│seven years to finish. │
|
||||
│Disambiguation: American pianist │
|
||||
│Type: Person │
|
||||
│Status: Continuing │
|
||||
│Genres: soundtrack │
|
||||
│Rating: 84% │
|
||||
│Path: /nfs/music/test-artist │
|
||||
│Quality Profile: Lossless │
|
||||
│Metadata Profile: Standard │
|
||||
│Monitored: Yes │
|
||||
│Albums: 1 │
|
||||
│Tracks: 12/15 │
|
||||
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||
││ Albums │ History │ Manual Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||
││=> 🏷 Test Album Album N/A 0 min 2023-01-01 N/A ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
@@ -1,11 +1,14 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::lidarr_models::Track;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, TRACK_DETAILS_BLOCKS};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::lidarr_ui::library::track_details_ui::TrackDetailsUi;
|
||||
use crate::ui::lidarr_ui::library::track_details_ui::{TrackDetailsUi, style_from_status};
|
||||
use crate::ui::styles::{downloaded_style, missing_style};
|
||||
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||
|
||||
#[test]
|
||||
@@ -19,6 +22,30 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_downloaded_when_track_has_file() {
|
||||
let track = Track {
|
||||
has_file: true,
|
||||
..Track::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(&track);
|
||||
|
||||
assert_eq!(style, downloaded_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_missing_when_track_has_no_file() {
|
||||
let track = Track {
|
||||
has_file: false,
|
||||
..Track::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(&track);
|
||||
|
||||
assert_eq!(style, missing_style());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::logos::RADARR_LOGO;
|
||||
use crate::models::Route;
|
||||
use crate::models::radarr_models::{DownloadRecord, Movie};
|
||||
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie};
|
||||
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
|
||||
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
||||
use crate::ui::DrawUi;
|
||||
@@ -230,11 +230,11 @@ fn decorate_with_row_style<'a>(
|
||||
.iter()
|
||||
.find(|&download| download.movie_id == movie.id)
|
||||
{
|
||||
if download.status == "downloading" {
|
||||
if download.status == DownloadStatus::Downloading {
|
||||
return row.downloading();
|
||||
}
|
||||
|
||||
if download.status == "completed" {
|
||||
if download.status == DownloadStatus::Completed {
|
||||
return row.awaiting_import();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::radarr_models::{DownloadRecord, Movie};
|
||||
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use rstest::rstest;
|
||||
@@ -50,8 +50,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(false, Some("downloading"), false, "", RowStyle::Downloading)]
|
||||
#[case(false, Some("completed"), false, "", RowStyle::AwaitingImport)]
|
||||
#[case(
|
||||
false,
|
||||
Some(DownloadStatus::Downloading),
|
||||
false,
|
||||
"",
|
||||
RowStyle::Downloading
|
||||
)]
|
||||
#[case(
|
||||
false,
|
||||
Some(DownloadStatus::Completed),
|
||||
false,
|
||||
"",
|
||||
RowStyle::AwaitingImport
|
||||
)]
|
||||
#[case(false, None, false, "", RowStyle::UnmonitoredMissing)]
|
||||
#[case(false, None, true, "", RowStyle::Unreleased)]
|
||||
#[case(false, None, true, "released", RowStyle::Missing)]
|
||||
@@ -59,7 +71,7 @@ mod tests {
|
||||
#[case(true, None, true, "", RowStyle::Downloaded)]
|
||||
fn test_decorate_with_row_style(
|
||||
#[case] has_file: bool,
|
||||
#[case] download_status: Option<&str>,
|
||||
#[case] download_status: Option<DownloadStatus>,
|
||||
#[case] is_monitored: bool,
|
||||
#[case] movie_status: String,
|
||||
#[case] expected_style: RowStyle,
|
||||
@@ -67,7 +79,7 @@ mod tests {
|
||||
let downloads_vec = if let Some(download_status) = download_status {
|
||||
vec![DownloadRecord {
|
||||
movie_id: 1,
|
||||
status: download_status.to_owned(),
|
||||
status: download_status,
|
||||
..DownloadRecord::default()
|
||||
}]
|
||||
} else {
|
||||
|
||||
@@ -275,13 +275,16 @@ fn draw_books_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
.release_date
|
||||
.map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string());
|
||||
let book_file_count = book.statistics.as_ref().map_or_else(
|
||||
|| "0/0".to_owned(),
|
||||
|| "N/A".to_owned(),
|
||||
|s| format!("{}/{}", s.book_file_count, s.total_book_count),
|
||||
);
|
||||
let size = book
|
||||
.statistics
|
||||
.as_ref()
|
||||
.map_or(0f64, |s| convert_to_gb(s.size_on_disk));
|
||||
let size = book.statistics.as_ref().map_or_else(
|
||||
|| "N/A".to_owned(),
|
||||
|s| {
|
||||
let size = convert_to_gb(s.size_on_disk);
|
||||
format!("{size:.2} GB")
|
||||
},
|
||||
);
|
||||
|
||||
decorate_book_row_with_style(
|
||||
book,
|
||||
@@ -290,7 +293,7 @@ fn draw_books_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
Cell::from(book.title.to_string()),
|
||||
Cell::from(book_file_count),
|
||||
Cell::from(release_date),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
Cell::from(size),
|
||||
]),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -271,6 +271,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_details_ui_renders_book_without_statistics() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.readarr_data.books.items[0].statistics = None;
|
||||
app.push_navigation_stack(ActiveReadarrBlock::AuthorDetails.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
AuthorDetailsUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_author_details_ui_renders_update_and_scan_prompt_over_author_details() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Files Release Date Size ││
|
||||
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
|
||||
││=> 🏷 Test Book 3/7 2023-05-20 2.30 GB ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
||||
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
||||
││ Monitored Title │ │Date Size ││
|
||||
││=> 🏷 Test Book │ │20 0.00 GB ││
|
||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Files Release Date Size ││
|
||||
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
|
||||
││=> 🏷 Test Book 3/7 2023-05-20 2.30 GB ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Files Release Date Size ││
|
||||
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
|
||||
││=> 🏷 Test Book 3/7 2023-05-20 2.30 GB ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search │ Do you want to trigger an update and disk scan for the │ ││
|
||||
││─────────────────────────────────────────│ author: Test Author? │───────────────────────────────────────────││
|
||||
││ Monitored Title │ │Date Size ││
|
||||
││=> 🏷 Test Book │ │20 0.00 GB ││
|
||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search │Do you want to trigger an automatic search of your indexers│ ││
|
||||
││─────────────────────────────────────────│ for all monitored book(s) for the author: Test Author? │───────────────────────────────────────────││
|
||||
││ Monitored Title │ │Date Size ││
|
||||
││=> 🏷 Test Book │ │20 0.00 GB ││
|
||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: src/ui/readarr_ui/library/author_details_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
|
||||
|
||||
|
||||
╭ Test Author ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Author: Test Author │
|
||||
│Status: Continuing │
|
||||
│Genres: science fiction │
|
||||
│Rating: N/A │
|
||||
│Path: /nfs/books/Test Author │
|
||||
│Quality Profile: Standard │
|
||||
│Metadata Profile: Comprehensive │
|
||||
│Monitored: Yes │
|
||||
│Overview: some interesting description of the author He was born in Madison, Wisconsin: a city he has never really left. His first novel took him │
|
||||
│seven years to finish. │
|
||||
│ │
|
||||
│ │
|
||||
│╭ Author Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||
││ Books │ History │ Author Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Files Release Date Size ││
|
||||
││=> 🏷 Test Book N/A 2023-05-20 N/A ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search │ Do you really want to delete the book: │ ││
|
||||
││─────────────────────────────────────────│ Test Book? │───────────────────────────────────────────││
|
||||
││ Monitored Title │ │Date Size ││
|
||||
││=> 🏷 Test Book │ │20 0.00 GB ││
|
||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
||||
││ │ ╭───╮ │ ││
|
||||
││ │ Delete Book Files: │ ✔ │ │ ││
|
||||
││ │ ╰───╯ │ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search │ Do you want to trigger an update and disk scan for the │ ││
|
||||
││─────────────────────────────────────────│ author: Test Author? │───────────────────────────────────────────││
|
||||
││ Monitored Title │ │Date Size ││
|
||||
││=> 🏷 Test Book │ │20 0.00 GB ││
|
||||
││=> 🏷 Test Book │ │20 2.30 GB ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
││ │ │ ││
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ expression: output
|
||||
││ Books │ History │ Author Search ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Title Files Release Date Size ││
|
||||
││=> 🏷 Test Book 0/0 2023-05-20 0.00 GB ││
|
||||
││=> 🏷 Test Book 3/7 2023-05-20 2.30 GB ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{Duration, Utc};
|
||||
use pretty_assertions::assert_eq;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::sonarr_models::{DownloadRecord, DownloadStatus, Episode};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::sonarr_ui::library::episode_details_ui::EpisodeDetailsUi;
|
||||
use crate::ui::sonarr_ui::library::episode_details_ui::{EpisodeDetailsUi, style_from_status};
|
||||
use crate::ui::styles::{
|
||||
awaiting_import_style, downloaded_style, downloading_style, missing_style,
|
||||
unmonitored_missing_style, unmonitored_style, unreleased_style,
|
||||
};
|
||||
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||
|
||||
#[test]
|
||||
@@ -21,6 +28,157 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_downloading_when_episode_has_no_file_and_download_is_downloading() {
|
||||
let download = DownloadRecord {
|
||||
status: DownloadStatus::Downloading,
|
||||
..DownloadRecord::default()
|
||||
};
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(Some(&download), &episode);
|
||||
|
||||
assert_eq!(style, downloading_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_awaiting_import_when_episode_has_no_file_and_download_is_completed() {
|
||||
let download = DownloadRecord {
|
||||
status: DownloadStatus::Completed,
|
||||
..DownloadRecord::default()
|
||||
};
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(Some(&download), &episode);
|
||||
|
||||
assert_eq!(style, awaiting_import_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_ignores_download_that_is_neither_downloading_nor_completed() {
|
||||
let download = DownloadRecord {
|
||||
status: DownloadStatus::Queued,
|
||||
..DownloadRecord::default()
|
||||
};
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: None,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(Some(&download), &episode);
|
||||
|
||||
assert_eq!(style, missing_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_unmonitored_missing_when_episode_has_no_file_and_is_unmonitored() {
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: false,
|
||||
air_date_utc: Some(Utc::now() + Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, unmonitored_missing_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_unreleased_when_episode_has_no_file_and_air_date_is_future() {
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: Some(Utc::now() + Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, unreleased_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_missing_when_episode_has_no_file_and_air_date_has_passed() {
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: Some(Utc::now() - Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, missing_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_missing_when_episode_has_no_file_and_no_air_date() {
|
||||
let episode = Episode {
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: None,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, missing_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_unmonitored_when_episode_has_file_and_is_unmonitored() {
|
||||
let episode = Episode {
|
||||
has_file: true,
|
||||
monitored: false,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, unmonitored_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_downloaded_when_episode_has_file_and_is_monitored() {
|
||||
let episode = Episode {
|
||||
has_file: true,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(None, &episode);
|
||||
|
||||
assert_eq!(style, downloaded_style());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_style_from_status_ignores_download_when_episode_has_file() {
|
||||
let download = DownloadRecord {
|
||||
status: DownloadStatus::Downloading,
|
||||
..DownloadRecord::default()
|
||||
};
|
||||
let episode = Episode {
|
||||
has_file: true,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
|
||||
let style = style_from_status(Some(&download), &episode);
|
||||
|
||||
assert_eq!(style, downloaded_style());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||
use rstest::rstest;
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{Duration, Utc};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use serde_json::Number;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, SEASON_DETAILS_BLOCKS,
|
||||
};
|
||||
use crate::models::sonarr_models::{DownloadRecord, DownloadStatus, Episode};
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::download_record;
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::sonarr_ui::library::season_details_ui::SeasonDetailsUi;
|
||||
use crate::ui::sonarr_ui::library::season_details_ui::{
|
||||
SeasonDetailsUi, decorate_with_row_style,
|
||||
};
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||
|
||||
#[test]
|
||||
@@ -25,6 +34,235 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_downloaded_when_episode_has_file() {
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: true,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_unmonitored_when_episode_has_file_and_is_unmonitored() {
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: true,
|
||||
monitored: false,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.unmonitored());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_missing_when_episode_has_no_file() {
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_unmonitored_missing_when_episode_has_no_file_and_is_unmonitored()
|
||||
{
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: false,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.unmonitored_missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_unreleased_when_episode_has_no_file_and_air_date_is_in_the_future()
|
||||
{
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: Some(Utc::now() + Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.unreleased());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_missing_when_episode_has_no_file_and_air_date_is_in_the_past() {
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
air_date_utc: Some(Utc::now() - Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_unmonitored_missing_when_unmonitored_episode_has_no_file_and_air_date_is_in_the_future()
|
||||
{
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: false,
|
||||
air_date_utc: Some(Utc::now() + Duration::days(1)),
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&[], &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.unmonitored_missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_downloading_when_episode_has_no_file_and_is_downloading() {
|
||||
let downloads_vec = vec![episode_download(5, DownloadStatus::Downloading)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloading());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_awaiting_import_when_episode_has_no_file_and_download_is_completed()
|
||||
{
|
||||
let downloads_vec = vec![episode_download(5, DownloadStatus::Completed)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.awaiting_import());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_missing_when_episode_download_is_neither_downloading_nor_completed()
|
||||
{
|
||||
let downloads_vec = vec![episode_download(5, DownloadStatus::Queued)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_missing_when_only_a_different_episode_is_downloading() {
|
||||
let downloads_vec = vec![episode_download(6, DownloadStatus::Downloading)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_missing_when_download_has_no_episode_id() {
|
||||
let downloads_vec = vec![DownloadRecord {
|
||||
episode_id: None,
|
||||
..episode_download(5, DownloadStatus::Downloading)
|
||||
}];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.missing());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_downloading_when_unmonitored_episode_has_no_file_and_is_downloading()
|
||||
{
|
||||
let downloads_vec = vec![episode_download(5, DownloadStatus::Downloading)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: false,
|
||||
monitored: false,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloading());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_with_row_style_downloaded_when_episode_has_file_and_is_downloading() {
|
||||
let downloads_vec = vec![episode_download(5, DownloadStatus::Downloading)];
|
||||
let episode = Episode {
|
||||
id: 5,
|
||||
has_file: true,
|
||||
monitored: true,
|
||||
..Episode::default()
|
||||
};
|
||||
let row = Row::new(vec![Cell::from("test".to_owned())]);
|
||||
|
||||
let style = decorate_with_row_style(&downloads_vec, &episode, row.clone());
|
||||
|
||||
assert_eq!(style, row.downloaded());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||
use rstest::rstest;
|
||||
@@ -145,4 +383,13 @@ mod tests {
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
}
|
||||
|
||||
fn episode_download(episode_id: i64, status: DownloadStatus) -> DownloadRecord {
|
||||
DownloadRecord {
|
||||
status,
|
||||
id: 9,
|
||||
episode_id: Some(Number::from(episode_id)),
|
||||
..download_record()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||
use crate::app::App;
|
||||
use crate::models::Route;
|
||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SERIES_DETAILS_BLOCKS};
|
||||
use crate::models::sonarr_models::{Season, SeasonStatistics, SonarrHistoryItem};
|
||||
use crate::models::sonarr_models::{Season, SonarrHistoryItem};
|
||||
use crate::ui::sonarr_ui::library::episode_details_ui::EpisodeDetailsUi;
|
||||
use crate::ui::sonarr_ui::library::season_details_ui::SeasonDetailsUi;
|
||||
use crate::ui::sonarr_ui::library::series_overview_ui::SeriesOverviewUi;
|
||||
@@ -229,29 +229,26 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
if let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() {
|
||||
let content = Some(&mut app.data.sonarr_data.seasons);
|
||||
let season_row_mapping = |season: &Season| {
|
||||
let Season {
|
||||
title, statistics, ..
|
||||
} = season;
|
||||
let SeasonStatistics {
|
||||
episode_file_count,
|
||||
episode_count,
|
||||
size_on_disk,
|
||||
..
|
||||
} = if let Some(stats) = statistics {
|
||||
stats
|
||||
} else {
|
||||
&SeasonStatistics::default()
|
||||
};
|
||||
let season_monitored = if season.monitored { "🏷" } else { "" };
|
||||
let size = convert_to_gb(*size_on_disk);
|
||||
let episode_count = season.statistics.as_ref().map_or_else(
|
||||
|| "N/A".to_owned(),
|
||||
|s| format!("{}/{}", s.episode_file_count, s.episode_count),
|
||||
);
|
||||
let size = season.statistics.as_ref().map_or_else(
|
||||
|| "N/A".to_owned(),
|
||||
|s| {
|
||||
let size = convert_to_gb(s.size_on_disk);
|
||||
format!("{size:.2} GB")
|
||||
},
|
||||
);
|
||||
|
||||
decorate_season_row_with_style(
|
||||
season,
|
||||
Row::new(vec![
|
||||
Cell::from(season_monitored.to_owned()),
|
||||
Cell::from(title.clone().unwrap_or_default()),
|
||||
Cell::from(format!("{episode_file_count}/{episode_count}")),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
Cell::from(season.title.clone().unwrap_or_default()),
|
||||
Cell::from(episode_count),
|
||||
Cell::from(size),
|
||||
]),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -239,6 +239,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_series_details_ui_renders_season_without_statistics() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
app.data.sonarr_data.seasons.items[0].statistics = None;
|
||||
app.push_navigation_stack(ActiveSonarrBlock::SeriesDetails.into());
|
||||
|
||||
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||
SeriesDetailsUi::draw(f, app, f.area());
|
||||
});
|
||||
|
||||
insta::assert_snapshot!(output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_series_details_ui_renders_season_details_over_series_details() {
|
||||
let mut app = App::test_default_fully_populated();
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: src/ui/sonarr_ui/library/series_details_ui_tests.rs
|
||||
expression: output
|
||||
---
|
||||
|
||||
|
||||
|
||||
╭ Test ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│Title: Test │
|
||||
│Overview: Blah blah blah It was filmed in Madison, Wisconsin: a city the crew never really left. The first season took seven years to finish. │
|
||||
│Network: HBO │
|
||||
│Status: Continuing │
|
||||
│Genres: cool, family, fun │
|
||||
│Rating: 84% │
|
||||
│Year: 2022 │
|
||||
│Runtime: 63 minutes │
|
||||
│Path: /nfs/tv/Test │
|
||||
│Quality Profile: Bluray-1080p │
|
||||
│Language Profile: English │
|
||||
│Monitored: Yes │
|
||||
│Size on Disk: 59.51 GB │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│╭ Series Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||
││ Seasons │ History ││
|
||||
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||
││ Monitored Season Episode Count Size on Disk ││
|
||||
││=> 🏷 Season title N/A N/A ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
││ ││
|
||||
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
Reference in new issue
Block a user