From 0ee275d58fe1c0f97cfd6e2a5a494b941f566ce6 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 15 Jan 2026 12:43:16 -0700 Subject: [PATCH] fix: Sonarr manual search TUI and CLI incorrectly displaying the same unfiltered results for both season and episode searches --- .../sonarr/manual_search_command_handler.rs | 24 +++- src/models/sonarr_models.rs | 1 + .../sonarr_network/library/episodes/mod.rs | 7 +- .../episodes/sonarr_episodes_network_tests.rs | 134 +++++++++++++----- .../library/sonarr_library_network_tests.rs | 2 +- 5 files changed, 125 insertions(+), 43 deletions(-) diff --git a/src/cli/sonarr/manual_search_command_handler.rs b/src/cli/sonarr/manual_search_command_handler.rs index 22dafa2..f0e4f87 100644 --- a/src/cli/sonarr/manual_search_command_handler.rs +++ b/src/cli/sonarr/manual_search_command_handler.rs @@ -2,16 +2,18 @@ use std::sync::Arc; use anyhow::Result; use clap::Subcommand; +use serde_json::json; use tokio::sync::Mutex; +use super::SonarrCommand; +use crate::models::Serdeable; +use crate::models::sonarr_models::{SonarrRelease, SonarrSerdeable}; use crate::{ app::App, cli::{CliCommandHandler, Command}, network::{NetworkTrait, sonarr_network::SonarrEvent}, }; -use super::SonarrCommand; - #[cfg(test)] #[path = "manual_search_command_handler_tests.rs"] mod manual_search_command_handler_tests; @@ -28,7 +30,7 @@ pub enum SonarrManualSearchCommand { episode_id: i64, }, #[command( - about = "Trigger a manual search of releases for the given season corresponding to the series with the given ID.\nNote that when downloading a season release, ensure that the release includes 'fullSeason: true', otherwise you'll run into issues" + about = "Trigger a manual search of releases for the given season corresponding to the series with the given ID" )] Season { #[arg( @@ -84,11 +86,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand> season_number, } => { println!("Searching for season releases. This may take a minute..."); - let resp = self + match self .network .handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into()) - .await?; - serde_json::to_string_pretty(&resp)? + .await + { + Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(releases_vec))) => { + let seasons_vec: Vec = releases_vec + .into_iter() + .filter(|release| release.full_season) + .collect(); + serde_json::to_string_pretty(&seasons_vec)? + } + Err(e) => return Err(e), + _ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?, + } } }; diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 73a4453..3de2d11 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -537,6 +537,7 @@ pub struct SonarrRelease { pub quality: QualityWrapper, pub full_season: bool, } + #[derive(Default, Serialize, Debug, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct SonarrReleaseDownloadBody { diff --git a/src/network/sonarr_network/library/episodes/mod.rs b/src/network/sonarr_network/library/episodes/mod.rs index 1973c70..4d1ea2f 100644 --- a/src/network/sonarr_network/library/episodes/mod.rs +++ b/src/network/sonarr_network/library/episodes/mod.rs @@ -370,6 +370,11 @@ impl Network<'_, '_> { app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default()); } + let episode_releases_vec = release_vec + .into_iter() + .filter(|release| !release.full_season) + .collect(); + if app .data .sonarr_data @@ -398,7 +403,7 @@ impl Network<'_, '_> { .as_mut() .unwrap() .episode_releases - .set_items(release_vec); + .set_items(episode_releases_vec); }) .await } diff --git a/src/network/sonarr_network/library/episodes/sonarr_episodes_network_tests.rs b/src/network/sonarr_network/library/episodes/sonarr_episodes_network_tests.rs index efd44a2..0a79044 100644 --- a/src/network/sonarr_network/library/episodes/sonarr_episodes_network_tests.rs +++ b/src/network/sonarr_network/library/episodes/sonarr_episodes_network_tests.rs @@ -4,7 +4,7 @@ mod tests { use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::sonarr_models::{ DownloadRecord, DownloadStatus, Episode, MonitorEpisodeBody, Season, Series, SonarrHistoryItem, - SonarrHistoryWrapper, SonarrSerdeable, + SonarrHistoryWrapper, SonarrRelease, SonarrSerdeable, }; use crate::models::stateful_table::SortOption; use crate::network::NetworkResource; @@ -1067,21 +1067,53 @@ mod tests { #[tokio::test] async fn test_handle_get_episode_releases_event() { - let release_json = json!([{ - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }} - }]); + let release_json = json!([ + { + "guid": "1234", + "protocol": "torrent", + "age": 1, + "title": "Test Release", + "indexer": "kickass torrents", + "indexerId": 2, + "size": 1234, + "rejected": true, + "rejections": [ "Unknown quality profile", "Release is already mapped" ], + "seeders": 2, + "leechers": 1, + "languages": [ { "id": 1, "name": "English" } ], + "quality": { "quality": { "name": "Bluray-1080p" }}, + "fullSeason": true + }, + { + "guid": "4567", + "protocol": "torrent", + "age": 1, + "title": "Test Release", + "indexer": "kickass torrents", + "indexerId": 2, + "size": 1234, + "rejected": true, + "rejections": [ "Unknown quality profile", "Release is already mapped" ], + "seeders": 2, + "leechers": 1, + "languages": [ { "id": 1, "name": "English" } ], + "quality": { "quality": { "name": "Bluray-1080p" }}, + } + ]); + let expected_filtered_sonarr_release = SonarrRelease { + guid: "4567".to_owned(), + ..torrent_release() + }; + let expected_raw_sonarr_releases = vec![ + SonarrRelease { + full_season: true, + ..torrent_release() + }, + SonarrRelease { + guid: "4567".to_owned(), + ..torrent_release() + }, + ]; let (async_server, app_arc, _server) = MockServarrApi::get() .returns(release_json) .query("episodeId=1") @@ -1124,28 +1156,60 @@ mod tests { .unwrap() .episode_releases .items, - vec![torrent_release()] + vec![expected_filtered_sonarr_release] ); - assert_eq!(releases_vec, vec![torrent_release()]); + assert_eq!(releases_vec, expected_raw_sonarr_releases); } #[tokio::test] async fn test_handle_get_episode_releases_event_empty_episode_details_modal() { - let release_json = json!([{ - "guid": "1234", - "protocol": "torrent", - "age": 1, - "title": "Test Release", - "indexer": "kickass torrents", - "indexerId": 2, - "size": 1234, - "rejected": true, - "rejections": [ "Unknown quality profile", "Release is already mapped" ], - "seeders": 2, - "leechers": 1, - "languages": [ { "id": 1, "name": "English" } ], - "quality": { "quality": { "name": "Bluray-1080p" }} - }]); + let release_json = json!([ + { + "guid": "1234", + "protocol": "torrent", + "age": 1, + "title": "Test Release", + "indexer": "kickass torrents", + "indexerId": 2, + "size": 1234, + "rejected": true, + "rejections": [ "Unknown quality profile", "Release is already mapped" ], + "seeders": 2, + "leechers": 1, + "languages": [ { "id": 1, "name": "English" } ], + "quality": { "quality": { "name": "Bluray-1080p" }}, + "fullSeason": true + }, + { + "guid": "4567", + "protocol": "torrent", + "age": 1, + "title": "Test Release", + "indexer": "kickass torrents", + "indexerId": 2, + "size": 1234, + "rejected": true, + "rejections": [ "Unknown quality profile", "Release is already mapped" ], + "seeders": 2, + "leechers": 1, + "languages": [ { "id": 1, "name": "English" } ], + "quality": { "quality": { "name": "Bluray-1080p" }}, + } + ]); + let expected_filtered_sonarr_release = SonarrRelease { + guid: "4567".to_owned(), + ..torrent_release() + }; + let expected_raw_sonarr_releases = vec![ + SonarrRelease { + full_season: true, + ..torrent_release() + }, + SonarrRelease { + guid: "4567".to_owned(), + ..torrent_release() + }, + ]; let (async_server, app_arc, _server) = MockServarrApi::get() .returns(release_json) .query("episodeId=1") @@ -1179,9 +1243,9 @@ mod tests { .unwrap() .episode_releases .items, - vec![torrent_release()] + vec![expected_filtered_sonarr_release] ); - assert_eq!(releases_vec, vec![torrent_release()]); + assert_eq!(releases_vec, expected_raw_sonarr_releases); } #[tokio::test] diff --git a/src/network/sonarr_network/library/sonarr_library_network_tests.rs b/src/network/sonarr_network/library/sonarr_library_network_tests.rs index 0323dd2..68562d3 100644 --- a/src/network/sonarr_network/library/sonarr_library_network_tests.rs +++ b/src/network/sonarr_network/library/sonarr_library_network_tests.rs @@ -32,6 +32,6 @@ mod tests { .await; mock.assert_async().await; - assert!(result.is_ok()); + assert_ok!(result); } }