fix(radarr): model the download status as a DownloadStatus enum
Radarr was the only tree still holding DownloadRecord.status as a bare String and comparing it against literals. It now carries its own DownloadStatus enum, shaped exactly like the Sonarr, Lidarr and Readarr copies, and is guarded with from_json_or_default so an unrecognised status degrades to Unknown instead of failing the whole queue fetch. The row-style decorator and get_movie_status compare against the enum variants. The status cell is never rendered in the Radarr tree, so no snapshot changes; the CLI JSON output is unchanged for every real value since the enum serializes to the same camelCase strings.
This commit is contained in:
8 files changed
+126
-29
No files matched your search
@@ -4,8 +4,8 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
|||||||
use crate::models::HorizontallyScrollableText;
|
use crate::models::HorizontallyScrollableText;
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie,
|
AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie,
|
||||||
DownloadRecord, IndexerSettings, MediaInfo, MinimumAvailability, Movie, MovieCollection,
|
DownloadRecord, DownloadStatus, IndexerSettings, MediaInfo, MinimumAvailability, Movie,
|
||||||
MovieFile, MovieMonitor, RadarrRelease, Rating, RatingsList,
|
MovieCollection, MovieFile, MovieMonitor, RadarrRelease, Rating, RatingsList,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
||||||
@@ -407,7 +407,7 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
|||||||
pub fn download_record() -> DownloadRecord {
|
pub fn download_record() -> DownloadRecord {
|
||||||
DownloadRecord {
|
DownloadRecord {
|
||||||
title: "Test Download Title".to_owned(),
|
title: "Test Download Title".to_owned(),
|
||||||
status: "downloading".to_owned(),
|
status: DownloadStatus::Downloading,
|
||||||
id: 1,
|
id: 1,
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
size: 3543348019,
|
size: 3543348019,
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ pub struct DeleteMovieParams {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DownloadRecord {
|
pub struct DownloadRecord {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub status: String,
|
#[serde(deserialize_with = "super::from_json_or_default")]
|
||||||
|
pub status: DownloadStatus,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
#[serde(deserialize_with = "super::from_i64")]
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
@@ -169,6 +170,36 @@ pub struct DownloadRecord {
|
|||||||
pub download_client: String,
|
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)]
|
#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DownloadsResponse {
|
pub struct DownloadsResponse {
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ mod tests {
|
|||||||
Serdeable,
|
Serdeable,
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, CreditType,
|
AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, CreditType,
|
||||||
DiskSpace, DownloadRecord, DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult,
|
DiskSpace, DownloadRecord, DownloadStatus, DownloadsResponse, Indexer, IndexerSettings,
|
||||||
MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, QualityProfile, RadarrRelease,
|
IndexerTestResult, MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor,
|
||||||
RadarrSerdeable, RadarrTask, RadarrTaskName, Tag, Update,
|
QualityProfile, RadarrRelease, RadarrSerdeable, RadarrTask, RadarrTaskName, Tag, Update,
|
||||||
},
|
},
|
||||||
servarr_models::{
|
servarr_models::{
|
||||||
HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig, SystemStatus,
|
HostConfig, Log, LogResponse, QueueEvent, RootFolder, SecurityConfig, SystemStatus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::network::radarr_network::radarr_network_test_utils::test_utils::{
|
use crate::network::radarr_network::radarr_network_test_utils::test_utils::{
|
||||||
MOVIE_JSON, collection, crew_credit, task,
|
MOVIE_JSON, collection, crew_credit, download_record, task,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -70,6 +70,40 @@ mod tests {
|
|||||||
assert_str_eq!(MovieMonitor::None.to_display_str(), "None");
|
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]
|
#[test]
|
||||||
fn test_radarr_history_event_type_display() {
|
fn test_radarr_history_event_type_display() {
|
||||||
assert_str_eq!(RadarrHistoryEventType::Unknown.to_string(), "unknown");
|
assert_str_eq!(RadarrHistoryEventType::Unknown.to_string(), "unknown");
|
||||||
@@ -230,7 +264,7 @@ mod tests {
|
|||||||
fn test_download_record_default_indexer_value() {
|
fn test_download_record_default_indexer_value() {
|
||||||
let json = r#"{
|
let json = r#"{
|
||||||
"title": "test",
|
"title": "test",
|
||||||
"status": "test",
|
"status": "downloading",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"movieId": 0,
|
"movieId": 0,
|
||||||
"size": 0,
|
"size": 0,
|
||||||
@@ -239,7 +273,7 @@ mod tests {
|
|||||||
}"#;
|
}"#;
|
||||||
let expected_record = DownloadRecord {
|
let expected_record = DownloadRecord {
|
||||||
title: "test".to_owned(),
|
title: "test".to_owned(),
|
||||||
status: "test".to_owned(),
|
status: DownloadStatus::Downloading,
|
||||||
id: 0,
|
id: 0,
|
||||||
movie_id: 0,
|
movie_id: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -693,6 +727,24 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 {
|
fn deserialize_with_field<T: DeserializeOwned>(json: &str, field: &str, value: Value) -> T {
|
||||||
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
let mut fixture_json: Value = serde_json::from_str(json).unwrap();
|
||||||
fixture_json[field] = value;
|
fixture_json[field] = value;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieSearchResult, Credit, CreditType, DeleteMovieParams, DownloadRecord,
|
AddMovieBody, AddMovieSearchResult, Credit, CreditType, DeleteMovieParams, DownloadRecord,
|
||||||
EditMovieParams, Movie, MovieCommandBody, MovieHistoryItem, RadarrRelease,
|
DownloadStatus, EditMovieParams, Movie, MovieCommandBody, MovieHistoryItem, RadarrRelease,
|
||||||
RadarrReleaseDownloadBody,
|
RadarrReleaseDownloadBody,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::Notification;
|
use crate::models::servarr_data::Notification;
|
||||||
@@ -664,11 +664,11 @@ pub(in crate::network::radarr_network::library) fn get_movie_status(
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|&download| download.movie_id == movie_id)
|
.find(|&download| download.movie_id == movie_id)
|
||||||
{
|
{
|
||||||
if download.status == "downloading" {
|
if download.status == DownloadStatus::Downloading {
|
||||||
return "Downloading".to_owned();
|
return "Downloading".to_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
if download.status == "completed" {
|
if download.status == DownloadStatus::Completed {
|
||||||
return "Awaiting Import".to_owned();
|
return "Awaiting Import".to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, EditMovieParams,
|
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, DownloadStatus,
|
||||||
MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, RadarrReleaseDownloadBody,
|
EditMovieParams, MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor,
|
||||||
|
RadarrReleaseDownloadBody,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::Notification;
|
use crate::models::servarr_data::Notification;
|
||||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||||
@@ -1199,7 +1200,7 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
&[DownloadRecord {
|
&[DownloadRecord {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
status: "downloading".to_owned(),
|
status: DownloadStatus::Downloading,
|
||||||
..DownloadRecord::default()
|
..DownloadRecord::default()
|
||||||
}],
|
}],
|
||||||
1
|
1
|
||||||
@@ -1215,7 +1216,7 @@ mod tests {
|
|||||||
false,
|
false,
|
||||||
&[DownloadRecord {
|
&[DownloadRecord {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
status: "completed".to_owned(),
|
status: DownloadStatus::Completed,
|
||||||
..DownloadRecord::default()
|
..DownloadRecord::default()
|
||||||
}],
|
}],
|
||||||
1
|
1
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieSearchResult, BlocklistItem, BlocklistItemMovie, Collection, CollectionMovie, Credit,
|
AddMovieSearchResult, BlocklistItem, BlocklistItemMovie, Collection, CollectionMovie, Credit,
|
||||||
CreditType, DownloadRecord, DownloadsResponse, IndexerSettings, MediaInfo, MinimumAvailability,
|
CreditType, DownloadRecord, DownloadStatus, DownloadsResponse, IndexerSettings, MediaInfo,
|
||||||
Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrHistoryData, RadarrHistoryEventType,
|
MinimumAvailability, Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrHistoryData,
|
||||||
RadarrHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName, Rating, RatingsList,
|
RadarrHistoryEventType, RadarrHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName, Rating,
|
||||||
|
RatingsList,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
||||||
@@ -339,7 +340,7 @@ pub mod test_utils {
|
|||||||
pub fn download_record() -> DownloadRecord {
|
pub fn download_record() -> DownloadRecord {
|
||||||
DownloadRecord {
|
DownloadRecord {
|
||||||
title: "Test Download Title".to_owned(),
|
title: "Test Download Title".to_owned(),
|
||||||
status: "downloading".to_owned(),
|
status: DownloadStatus::Downloading,
|
||||||
id: 1,
|
id: 1,
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
size: 3543348019,
|
size: 3543348019,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::logos::RADARR_LOGO;
|
use crate::logos::RADARR_LOGO;
|
||||||
use crate::models::Route;
|
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_data::radarr::radarr_data::RadarrData;
|
||||||
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
||||||
use crate::ui::DrawUi;
|
use crate::ui::DrawUi;
|
||||||
@@ -230,11 +230,11 @@ fn decorate_with_row_style<'a>(
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|&download| download.movie_id == movie.id)
|
.find(|&download| download.movie_id == movie.id)
|
||||||
{
|
{
|
||||||
if download.status == "downloading" {
|
if download.status == DownloadStatus::Downloading {
|
||||||
return row.downloading();
|
return row.downloading();
|
||||||
}
|
}
|
||||||
|
|
||||||
if download.status == "completed" {
|
if download.status == DownloadStatus::Completed {
|
||||||
return row.awaiting_import();
|
return row.awaiting_import();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::radarr_models::{DownloadRecord, Movie};
|
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use ratatui::widgets::{Cell, Row};
|
use ratatui::widgets::{Cell, Row};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
@@ -50,8 +50,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(false, Some("downloading"), false, "", RowStyle::Downloading)]
|
#[case(
|
||||||
#[case(false, Some("completed"), false, "", RowStyle::AwaitingImport)]
|
false,
|
||||||
|
Some(DownloadStatus::Downloading),
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
RowStyle::Downloading
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
false,
|
||||||
|
Some(DownloadStatus::Completed),
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
RowStyle::AwaitingImport
|
||||||
|
)]
|
||||||
#[case(false, None, false, "", RowStyle::UnmonitoredMissing)]
|
#[case(false, None, false, "", RowStyle::UnmonitoredMissing)]
|
||||||
#[case(false, None, true, "", RowStyle::Unreleased)]
|
#[case(false, None, true, "", RowStyle::Unreleased)]
|
||||||
#[case(false, None, true, "released", RowStyle::Missing)]
|
#[case(false, None, true, "released", RowStyle::Missing)]
|
||||||
@@ -59,7 +71,7 @@ mod tests {
|
|||||||
#[case(true, None, true, "", RowStyle::Downloaded)]
|
#[case(true, None, true, "", RowStyle::Downloaded)]
|
||||||
fn test_decorate_with_row_style(
|
fn test_decorate_with_row_style(
|
||||||
#[case] has_file: bool,
|
#[case] has_file: bool,
|
||||||
#[case] download_status: Option<&str>,
|
#[case] download_status: Option<DownloadStatus>,
|
||||||
#[case] is_monitored: bool,
|
#[case] is_monitored: bool,
|
||||||
#[case] movie_status: String,
|
#[case] movie_status: String,
|
||||||
#[case] expected_style: RowStyle,
|
#[case] expected_style: RowStyle,
|
||||||
@@ -67,7 +79,7 @@ mod tests {
|
|||||||
let downloads_vec = if let Some(download_status) = download_status {
|
let downloads_vec = if let Some(download_status) = download_status {
|
||||||
vec![DownloadRecord {
|
vec![DownloadRecord {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
status: download_status.to_owned(),
|
status: download_status,
|
||||||
..DownloadRecord::default()
|
..DownloadRecord::default()
|
||||||
}]
|
}]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in new issue
Block a user