diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 4eb89c8..5fbdef0 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -40,6 +40,7 @@ pub enum SonarrEvent { GetIndexers, GetEpisodeDetails(Option), GetEpisodes(Option), + GetEpisodeHistory(Option), GetLogs(Option), GetQualityProfiles, GetQueuedEvents, @@ -62,7 +63,7 @@ impl NetworkResource for SonarrEvent { SonarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000", SonarrEvent::GetDownloads => "/queue", SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode", - SonarrEvent::GetHistory(_) => "/history", + SonarrEvent::GetHistory(_) | SonarrEvent::GetEpisodeHistory(_) => "/history", SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host", SonarrEvent::GetIndexers => "/indexer", SonarrEvent::GetLogs(_) => "/log", @@ -111,6 +112,10 @@ impl<'a, 'b> Network<'a, 'b> { .get_episode_details(episode_id) .await .map(SonarrSerdeable::from), + SonarrEvent::GetEpisodeHistory(episode_id) => self + .get_sonarr_episode_history(episode_id) + .await + .map(SonarrSerdeable::from), SonarrEvent::GetHistory(events) => self .get_sonarr_history(events) .await @@ -346,6 +351,71 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn get_sonarr_episode_history( + &mut self, + episode_id: Option, + ) -> Result { + let id = self.extract_episode_id(episode_id).await; + info!("Fetching Sonarr history for episode with ID: {id}"); + let event = SonarrEvent::GetEpisodeHistory(episode_id); + + let params = format!("episodeId={id}&pageSize=1000&sortDirection=descending&sortKey=date",); + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params)) + .await; + + self + .handle_request::<(), SonarrHistoryWrapper>(request_props, |history_response, mut app| { + if app.data.sonarr_data.season_details_modal.is_none() { + app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default()); + } + + if app + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .is_none() + { + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal = Some(EpisodeDetailsModal::default()); + } + + let mut history_vec = history_response.records; + history_vec.sort_by(|a, b| a.id.cmp(&b.id)); + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal + .as_mut() + .unwrap() + .episode_history + .set_items(history_vec); + app + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal + .as_mut() + .unwrap() + .episode_history + .apply_sorting_toggle(false); + }) + .await + } + async fn get_episode_details(&mut self, episode_id: Option) -> Result { info!("Fetching Sonarr episode details"); let event = SonarrEvent::GetEpisodeDetails(None); diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 050ea84..12b0bbd 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -156,7 +156,10 @@ mod test { } #[rstest] - fn test_resource_history(#[values(SonarrEvent::GetHistory(None))] event: SonarrEvent) { + fn test_resource_history( + #[values(SonarrEvent::GetHistory(None), SonarrEvent::GetEpisodeHistory(None))] + event: SonarrEvent, + ) { assert_str_eq!(event.resource(), "/history"); } @@ -1280,6 +1283,470 @@ mod test { } } + #[tokio::test] + async fn test_handle_get_sonarr_episode_history_event() { + let history_json = json!({"records": [{ + "id": 123, + "sourceTitle": "z episode", + "episodeId": 1007, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }, + { + "id": 456, + "sourceTitle": "A Episode", + "episodeId": 2001, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }]}); + let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); + let expected_history_items = vec![ + SonarrHistoryItem { + id: 123, + episode_id: 1007, + source_title: "z episode".into(), + ..history_item() + }, + SonarrHistoryItem { + id: 456, + episode_id: 2001, + source_title: "A Episode".into(), + ..history_item() + }, + ]; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(history_json), + None, + SonarrEvent::GetEpisodeHistory(None), + None, + Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), + ) + .await; + app_arc.lock().await.data.sonarr_data.season_details_modal = + Some(SeasonDetailsModal::default()); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal = Some(EpisodeDetailsModal::default()); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal + .as_mut() + .unwrap() + .episode_history + .sort_asc = true; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::SonarrHistoryWrapper(history) = network + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(None)) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .items, + expected_history_items + ); + assert!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .sort_asc + ); + assert_eq!(history, response); + } + } + + #[tokio::test] + async fn test_handle_get_sonarr_episode_history_event_uses_provided_episode_id() { + let history_json = json!({"records": [{ + "id": 123, + "sourceTitle": "z episode", + "episodeId": 1007, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }, + { + "id": 456, + "sourceTitle": "A Episode", + "episodeId": 2001, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }]}); + let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); + let expected_history_items = vec![ + SonarrHistoryItem { + id: 123, + episode_id: 1007, + source_title: "z episode".into(), + ..history_item() + }, + SonarrHistoryItem { + id: 456, + episode_id: 2001, + source_title: "A Episode".into(), + ..history_item() + }, + ]; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(history_json), + None, + SonarrEvent::GetEpisodeHistory(Some(2)), + None, + Some("episodeId=2&pageSize=1000&sortDirection=descending&sortKey=date"), + ) + .await; + app_arc.lock().await.data.sonarr_data.season_details_modal = + Some(SeasonDetailsModal::default()); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal = Some(EpisodeDetailsModal::default()); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episode_details_modal + .as_mut() + .unwrap() + .episode_history + .sort_asc = true; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::SonarrHistoryWrapper(history) = network + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(Some(2))) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .items, + expected_history_items + ); + assert!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .sort_asc + ); + assert_eq!(history, response); + } + } + + #[tokio::test] + async fn test_handle_get_sonarr_episode_history_event_empty_episode_details_modal() { + let history_json = json!({"records": [{ + "id": 123, + "sourceTitle": "z episode", + "episodeId": 1007, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }, + { + "id": 456, + "sourceTitle": "A Episode", + "episodeId": 2001, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }]}); + let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); + let expected_history_items = vec![ + SonarrHistoryItem { + id: 123, + episode_id: 1007, + source_title: "z episode".into(), + ..history_item() + }, + SonarrHistoryItem { + id: 456, + episode_id: 2001, + source_title: "A Episode".into(), + ..history_item() + }, + ]; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(history_json), + None, + SonarrEvent::GetEpisodeHistory(None), + None, + Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), + ) + .await; + app_arc.lock().await.data.sonarr_data.season_details_modal = + Some(SeasonDetailsModal::default()); + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_mut() + .unwrap() + .episodes + .set_items(vec![episode()]); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::SonarrHistoryWrapper(history) = network + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(None)) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .items, + expected_history_items + ); + assert!( + !app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .sort_asc + ); + assert_eq!(history, response); + } + } + + #[tokio::test] + async fn test_handle_get_sonarr_episode_history_event_empty_season_details_modal() { + let history_json = json!({"records": [{ + "id": 123, + "sourceTitle": "z episode", + "episodeId": 1007, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }, + { + "id": 456, + "sourceTitle": "A Episode", + "episodeId": 2001, + "quality": { "quality": { "name": "Bluray-1080p" } }, + "language": { "name": "English" }, + "date": "2024-02-10T07:28:45Z", + "eventType": "grabbed", + "data": { + "droppedPath": "/nfs/nzbget/completed/series/Coolness/something.cool.mkv", + "importedPath": "/nfs/tv/Coolness/Season 1/Coolness - S01E01 - Something Cool Bluray-1080p.mkv" + } + }]}); + let response: SonarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap(); + let expected_history_items = vec![ + SonarrHistoryItem { + id: 123, + episode_id: 1007, + source_title: "z episode".into(), + ..history_item() + }, + SonarrHistoryItem { + id: 456, + episode_id: 2001, + source_title: "A Episode".into(), + ..history_item() + }, + ]; + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(history_json), + None, + SonarrEvent::GetEpisodeHistory(Some(1)), + None, + Some("episodeId=1&pageSize=1000&sortDirection=descending&sortKey=date"), + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::SonarrHistoryWrapper(history) = network + .handle_sonarr_event(SonarrEvent::GetEpisodeHistory(Some(1))) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .items, + expected_history_items + ); + assert!( + !app_arc + .lock() + .await + .data + .sonarr_data + .season_details_modal + .as_ref() + .unwrap() + .episode_details_modal + .as_ref() + .unwrap() + .episode_history + .sort_asc + ); + assert_eq!(history, response); + } + } + #[tokio::test] async fn test_handle_get_episode_details_event_season_details_modal_not_required_in_cli_mode() { let response: Episode = serde_json::from_str(EPISODE_JSON).unwrap();