fix(radarr): model the movie status as an enum

Movie.status and AddMovieSearchResult.status were bare Strings compared
against the literal "released" in the row-style decorator and in the
movie details style function. They now carry a MovieStatus enum with the
five values Radarr actually sends (tba, announced, inCinemas, released,
deleted), shaped like MinimumAvailability and DownloadStatus, and guarded
with from_json_or_default so an unrecognised status degrades to Tba
instead of failing the whole fetch.

Radarr never sends "Downloaded" as a movie status; its web UI derives
that label from released plus hasFile. Every fixture that carried
"Downloaded" now holds released, the value it was standing in for, so
none of them degrade to the default through the guard.

The status is never rendered as text in the Radarr tree, so no snapshot
changes; the CLI JSON output is unchanged since the enum serializes to
the same camelCase strings. The style_from_download_status tests now
exercise the unreleased arm, which previously had no case, in place of a
duplicated unmonitored-missing row.
This commit is contained in:
Dark-Alex-17 committed 2026-09-17 18:06:37 -06:00
1 parent c42a46c7ca
commit 9822597b56
10 files changed
+149 -50

No files matched your search

+9 -11
View File
@@ -13,7 +13,7 @@ use serde_json::Number;
use crate::app::App;
use crate::models::Route;
use crate::models::radarr_models::{Credit, MovieHistoryItem, RadarrRelease};
use crate::models::radarr_models::{Credit, MovieHistoryItem, MovieStatus, RadarrRelease};
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::ui::styles::ManagarrStyle;
@@ -179,13 +179,7 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
match app.data.radarr_data.movie_details_modal.as_ref() {
Some(movie_details_modal) if !app.is_loading => {
let is_monitored = app.data.radarr_data.movies.current_selection().monitored;
let status = app
.data
.radarr_data
.movies
.current_selection()
.status
.clone();
let status = app.data.radarr_data.movies.current_selection().status;
let movie_details = &movie_details_modal.movie_details;
let download_status = movie_details
.items
@@ -201,7 +195,7 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
.map(|line| {
let split = line.split(':').collect::<Vec<&str>>();
let title = format!("{}:", split[0]);
let style = style_from_download_status(download_status, is_monitored, status.clone());
let style = style_from_download_status(download_status, is_monitored, status);
Line::from(vec![
Span::styled(title, style.bold()),
@@ -531,13 +525,17 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>) {
}
}
fn style_from_download_status(download_status: &str, is_monitored: bool, status: String) -> Style {
fn style_from_download_status(
download_status: &str,
is_monitored: bool,
status: MovieStatus,
) -> Style {
match download_status {
"Downloaded" => downloaded_style(),
"Awaiting Import" => awaiting_import_style(),
"Downloading" => downloading_style(),
_ if !is_monitored && download_status == "Missing" => unmonitored_missing_style(),
_ if status != "released" && download_status == "Missing" => unreleased_style(),
_ if status != MovieStatus::Released && download_status == "Missing" => unreleased_style(),
"Missing" => missing_style(),
_ => downloaded_style(),
}
@@ -6,6 +6,7 @@ mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::radarr_models::MovieStatus;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, MOVIE_DETAILS_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::radarr_ui::library::movie_details_ui::{
@@ -13,7 +14,7 @@ mod tests {
};
use crate::ui::styles::{
awaiting_import_style, downloaded_style, downloading_style, missing_style,
unmonitored_missing_style,
unmonitored_missing_style, unreleased_style,
};
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
@@ -29,21 +30,26 @@ mod tests {
}
#[rstest]
#[case("Downloading", true, "", downloading_style())]
#[case("Downloaded", true, "", downloaded_style())]
#[case("Awaiting Import", true, "", awaiting_import_style())]
#[case("Missing", false, "", unmonitored_missing_style())]
#[case("Missing", false, "", unmonitored_missing_style())]
#[case("Missing", true, "released", missing_style())]
#[case("", true, "", downloaded_style())]
#[case("Downloading", true, MovieStatus::Released, downloading_style())]
#[case("Downloaded", true, MovieStatus::Released, downloaded_style())]
#[case(
"Awaiting Import",
true,
MovieStatus::Released,
awaiting_import_style()
)]
#[case("Missing", false, MovieStatus::InCinemas, unmonitored_missing_style())]
#[case("Missing", true, MovieStatus::InCinemas, unreleased_style())]
#[case("Missing", true, MovieStatus::Released, missing_style())]
#[case("", true, MovieStatus::Released, downloaded_style())]
fn test_style_from_download_status(
#[case] download_status: &str,
#[case] is_monitored: bool,
#[case] movie_status: &str,
#[case] movie_status: MovieStatus,
#[case] expected_style: Style,
) {
assert_eq!(
style_from_download_status(download_status, is_monitored, movie_status.to_owned()),
style_from_download_status(download_status, is_monitored, movie_status),
expected_style
);
}
+2 -2
View File
@@ -1,7 +1,7 @@
use crate::app::App;
use crate::logos::RADARR_LOGO;
use crate::models::Route;
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie};
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie, MovieStatus};
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
use crate::models::servarr_models::{DiskSpace, RootFolder};
use crate::ui::DrawUi;
@@ -243,7 +243,7 @@ fn decorate_with_row_style<'a>(
return row.unmonitored_missing();
}
if movie.status != "released" {
if movie.status != MovieStatus::Released {
return row.unreleased();
}
+15 -9
View File
@@ -1,6 +1,6 @@
#[cfg(test)]
mod tests {
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie};
use crate::models::radarr_models::{DownloadRecord, DownloadStatus, Movie, MovieStatus};
use pretty_assertions::assert_eq;
use ratatui::widgets::{Cell, Row};
use rstest::rstest;
@@ -54,26 +54,32 @@ mod tests {
false,
Some(DownloadStatus::Downloading),
false,
"",
MovieStatus::Released,
RowStyle::Downloading
)]
#[case(
false,
Some(DownloadStatus::Completed),
false,
"",
MovieStatus::Released,
RowStyle::AwaitingImport
)]
#[case(false, None, false, "", RowStyle::UnmonitoredMissing)]
#[case(false, None, true, "", RowStyle::Unreleased)]
#[case(false, None, true, "released", RowStyle::Missing)]
#[case(true, None, false, "", RowStyle::Unmonitored)]
#[case(true, None, true, "", RowStyle::Downloaded)]
#[case(
false,
None,
false,
MovieStatus::Released,
RowStyle::UnmonitoredMissing
)]
#[case(false, None, true, MovieStatus::InCinemas, RowStyle::Unreleased)]
#[case(false, None, true, MovieStatus::Released, RowStyle::Missing)]
#[case(true, None, false, MovieStatus::Released, RowStyle::Unmonitored)]
#[case(true, None, true, MovieStatus::Released, RowStyle::Downloaded)]
fn test_decorate_with_row_style(
#[case] has_file: bool,
#[case] download_status: Option<DownloadStatus>,
#[case] is_monitored: bool,
#[case] movie_status: String,
#[case] movie_status: MovieStatus,
#[case] expected_style: RowStyle,
) {
let downloads_vec = if let Some(download_status) = download_status {