diff --git a/src/models/readarr_models.rs b/src/models/readarr_models.rs index c43963b6..587f5f1b 100644 --- a/src/models/readarr_models.rs +++ b/src/models/readarr_models.rs @@ -455,6 +455,29 @@ pub struct ReadarrRelease { pub quality: QualityWrapper, } +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(untagged)] +pub enum ReadarrReleaseResponse { + Releases(Vec), + // Chaptarr wraps the release list in a filter summary envelope, even behind its Readarr facade + Envelope { releases: Vec }, +} + +impl Default for ReadarrReleaseResponse { + fn default() -> Self { + ReadarrReleaseResponse::Releases(Vec::new()) + } +} + +impl ReadarrReleaseResponse { + pub fn into_releases(self) -> Vec { + match self { + ReadarrReleaseResponse::Releases(releases) + | ReadarrReleaseResponse::Envelope { releases } => releases, + } + } +} + #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct BlocklistItem { diff --git a/src/models/readarr_models_tests.rs b/src/models/readarr_models_tests.rs index 5a0512fa..86457b00 100644 --- a/src/models/readarr_models_tests.rs +++ b/src/models/readarr_models_tests.rs @@ -9,7 +9,7 @@ mod tests { AddAuthorSearchResult, BlocklistItem, BlocklistResponse, Book, BookFile, DownloadRecord, DownloadsResponse, Edition, MediaInfo, MonitorType, NewItemMonitorType, ReadarrHistoryEventType, ReadarrHistoryItem, ReadarrHistoryWrapper, ReadarrRelease, - ReadarrTask, ReadarrTaskName, + ReadarrReleaseResponse, ReadarrTask, ReadarrTaskName, }; use crate::models::servarr_models::{ DiskSpace, DownloadStatus, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Log, @@ -497,6 +497,29 @@ mod tests { assert_eq!(readarr_serdeable, ReadarrSerdeable::Releases(releases)); } + #[test] + fn test_readarr_release_response_default_is_an_empty_release_list() { + assert_eq!( + ReadarrReleaseResponse::default(), + ReadarrReleaseResponse::Releases(Vec::new()) + ); + } + + #[test] + fn test_readarr_release_response_into_releases() { + let releases = vec![ReadarrRelease { + guid: "test".to_owned(), + ..ReadarrRelease::default() + }]; + let list = ReadarrReleaseResponse::Releases(releases.clone()); + let envelope = ReadarrReleaseResponse::Envelope { + releases: releases.clone(), + }; + + assert_eq!(list.into_releases(), releases); + assert_eq!(envelope.into_releases(), releases); + } + #[test] fn test_readarr_serdeable_from_security_config() { let security_config = SecurityConfig { diff --git a/src/network/readarr_network/library/authors/mod.rs b/src/network/readarr_network/library/authors/mod.rs index 567e88b4..9eff5cb5 100644 --- a/src/network/readarr_network/library/authors/mod.rs +++ b/src/network/readarr_network/library/authors/mod.rs @@ -5,7 +5,7 @@ use serde_json::{Value, json}; use crate::models::Route; use crate::models::readarr_models::{ AddAuthorBody, AddAuthorSearchResult, Author, DeleteParams, EditAuthorParams, ReadarrCommandBody, - ReadarrHistoryItem, ReadarrRelease, + ReadarrHistoryItem, ReadarrRelease, ReadarrReleaseResponse, }; use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock; use crate::models::stateful_table::StatefulTable; @@ -235,10 +235,15 @@ impl Network<'_, '_> { .await; self - .handle_request::<(), Vec>(request_props, |release_vec, mut app| { - app.data.readarr_data.author_releases.set_items(release_vec); + .handle_request::<(), ReadarrReleaseResponse>(request_props, |response, mut app| { + app + .data + .readarr_data + .author_releases + .set_items(response.into_releases()); }) .await + .map(ReadarrReleaseResponse::into_releases) } pub(in crate::network::readarr_network) async fn get_readarr_author_history( diff --git a/src/network/readarr_network/library/authors/readarr_authors_network_tests.rs b/src/network/readarr_network/library/authors/readarr_authors_network_tests.rs index 99ef12d8..eb1ed4fb 100644 --- a/src/network/readarr_network/library/authors/readarr_authors_network_tests.rs +++ b/src/network/readarr_network/library/authors/readarr_authors_network_tests.rs @@ -133,6 +133,48 @@ mod tests { ); } + #[tokio::test] + async fn test_handle_get_author_releases_event_unwraps_the_release_envelope() { + let expected_releases = vec![torrent_release()]; + let releases_json = json!({ + "releases": [serde_json::from_str::(RELEASE_JSON).unwrap()], + "hiddenReleases": [], + "filterSummary": { "totalResults": 1, "displayedCount": 1 }, + "siblingToggleEnabled": false + }); + let (mock, app, _server) = MockServarrApi::get() + .returns(releases_json) + .query("authorId=3") + .build_for(ReadarrEvent::GetAuthorReleases(3)) + .await; + { + let mut app = app.lock().await; + app.server_tabs.set_index(3); + app + .data + .readarr_data + .author_releases + .set_items(vec![stale_release()]); + } + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetAuthorReleases(3)) + .await; + + mock.assert_async().await; + + let ReadarrSerdeable::Releases(releases) = result.unwrap() else { + panic!("Expected Releases") + }; + + assert_eq!(releases, expected_releases); + assert_eq!( + app.lock().await.data.readarr_data.author_releases.items, + expected_releases + ); + } + #[tokio::test] async fn test_handle_get_author_releases_event_failure() { let stale_releases = vec![stale_release()]; diff --git a/src/network/readarr_network/library/books/mod.rs b/src/network/readarr_network/library/books/mod.rs index eaa28f47..98d2859f 100644 --- a/src/network/readarr_network/library/books/mod.rs +++ b/src/network/readarr_network/library/books/mod.rs @@ -5,6 +5,7 @@ use serde_json::{Value, json}; use crate::models::Route; use crate::models::readarr_models::{ Book, BookFile, DeleteParams, Edition, ReadarrCommandBody, ReadarrHistoryItem, ReadarrRelease, + ReadarrReleaseResponse, }; use crate::models::servarr_data::readarr::readarr_data::ActiveReadarrBlock; use crate::network::readarr_network::ReadarrEvent; @@ -170,16 +171,19 @@ impl Network<'_, '_> { .await; self - .handle_request::<(), Vec>(request_props, |release_vec, mut app| { + .handle_request::<(), ReadarrReleaseResponse>(request_props, |response, mut app| { let book_details_modal = app .data .readarr_data .book_details_modal .get_or_insert_default(); - book_details_modal.book_releases.set_items(release_vec); + book_details_modal + .book_releases + .set_items(response.into_releases()); }) .await + .map(ReadarrReleaseResponse::into_releases) } pub(in crate::network::readarr_network) async fn get_books( diff --git a/src/network/readarr_network/library/books/readarr_books_network_tests.rs b/src/network/readarr_network/library/books/readarr_books_network_tests.rs index 7be0fdaf..a7aab4d2 100644 --- a/src/network/readarr_network/library/books/readarr_books_network_tests.rs +++ b/src/network/readarr_network/library/books/readarr_books_network_tests.rs @@ -532,6 +532,57 @@ mod tests { ); } + #[tokio::test] + async fn test_handle_get_book_releases_event_unwraps_the_release_envelope() { + let expected_releases = vec![torrent_release()]; + let releases_json = json!({ + "releases": [serde_json::from_str::(RELEASE_JSON).unwrap()], + "hiddenReleases": [], + "filterSummary": { "totalResults": 1, "displayedCount": 1 }, + "siblingToggleEnabled": false + }); + let (mock, app, _server) = MockServarrApi::get() + .returns(releases_json) + .query("bookId=7") + .build_for(ReadarrEvent::GetBookReleases(7)) + .await; + { + let mut app = app.lock().await; + app.server_tabs.set_index(3); + let mut book_details_modal = BookDetailsModal::default(); + book_details_modal + .book_releases + .set_items(vec![stale_release()]); + app.data.readarr_data.book_details_modal = Some(book_details_modal); + } + let mut network = test_network(&app); + + let result = network + .handle_readarr_event(ReadarrEvent::GetBookReleases(7)) + .await; + + mock.assert_async().await; + + let ReadarrSerdeable::Releases(releases) = result.unwrap() else { + panic!("Expected Releases") + }; + + assert_eq!(releases, expected_releases); + assert_eq!( + app + .lock() + .await + .data + .readarr_data + .book_details_modal + .as_ref() + .unwrap() + .book_releases + .items, + expected_releases + ); + } + #[tokio::test] async fn test_handle_get_book_releases_event_empty_book_details_modal() { let expected_releases = vec![torrent_release()];