From e9a509555734604f5d3dc6542ea3b6e34d5b1483 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sun, 23 Aug 2026 20:55:16 +0100 Subject: [PATCH] feat(api): reject empty episode titles --- crates/arr-api/src/series.rs | 31 +++++++++++++++++++++++++++++++ crates/arr-meta/src/lib.rs | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/arr-api/src/series.rs b/crates/arr-api/src/series.rs index e474d2c..0d51176 100644 --- a/crates/arr-api/src/series.rs +++ b/crates/arr-api/src/series.rs @@ -721,6 +721,15 @@ pub async fn create_season( "episode number cannot be negative".into(), )); } + // The column rejects the empty string (#153); say so before the database + // has to. + if input + .episodes + .iter() + .any(|episode| episode.title.trim().is_empty()) + { + return Err(ApiError::Invalid("episode title cannot be empty".into())); + } let mut numbers: Vec = input .episodes .iter() @@ -1394,6 +1403,28 @@ mod tests { assert_eq!(season["episodes"][0]["wanted"], false); } + /// #153. An empty episode title is rejected up front — the column and the + /// TMDB boundary both refuse it, so the API must too. + #[tokio::test] + async fn an_empty_episode_title_is_rejected() { + let (_dir, state, base) = application().await; + let root_id = tv_root(&state, "main").await; + let series = add_series(&base, root_id, false).await; + let series_id = series["id"].as_i64().expect("id"); + + for title in ["", " "] { + let response = reqwest::Client::new() + .post(format!("{base}/api/series/{series_id}/seasons")) + .json(&serde_json::json!({"number": 1, "episodes": [ + {"number": 1, "title": title} + ]})) + .send() + .await + .expect("create season"); + assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); + } + } + #[tokio::test] async fn a_rejected_season_leaves_nothing_behind_to_retry_over() { let (_dir, state, base) = application().await; diff --git a/crates/arr-meta/src/lib.rs b/crates/arr-meta/src/lib.rs index e15fcd6..dfca90a 100644 --- a/crates/arr-meta/src/lib.rs +++ b/crates/arr-meta/src/lib.rs @@ -24,6 +24,6 @@ mod model; pub use client::{TmdbClient, TmdbClientBuilder, DEFAULT_BASE_URL, DEFAULT_CACHE_TTL}; pub use error::{Error, Result}; pub use model::{ - Episode, ExternalIds, FindResults, Movie, MovieSearchResult, Season, Series, SeriesSearchResult, - UNTITLED_EPISODE, + Episode, ExternalIds, FindResults, Movie, MovieSearchResult, Season, Series, + SeriesSearchResult, UNTITLED_EPISODE, };