fix: keep history readable when an event type is unrecognised

A single history record whose event type the Servarr build has no name
for made the entire history response fail to decode, so the view died
with "Failed to parse response!" and showed nothing at all.

The event type is stored as an integer and is only serialized as a
camelCase string when the build can name it. For values it cannot name,
it is emitted as a bare JSON number instead, which is a type mismatch
rather than merely an unknown name. Serde's other attribute does not
help: it is rejected outright unless it sits on the last variant, and
even there it only catches unrecognised strings, leaving a number to
fail exactly as before.

from_json_or_default buffers the field and falls back to the type's
default for any input the enum does not recognise, whether that is an
unknown string, a number, or any other JSON value. It is applied to the
event type of all four history item types. Known values still map
exactly as they did, and serialization is untouched.

Verified against a live Readarr carrying such a record: the history
listing failed to decode before the change, and afterwards lists every
record, rendering the unnamed one as Unknown.

Verified by mutation. Seven mutations all fail the new suite: dropping
the fallback, discarding the decoded value, removing the attribute from
each of the four history items, and renaming a variant on serialize
only. The five per-tree and serialize-only mutations were each invisible
to every pre-existing test.
This commit is contained in:
Dark-Alex-17 committed 2026-09-11 12:27:53 -06:00
1 parent 9dbd985506
commit 86b91ce6dd
10 files changed
+578 -6

No files matched your search

+1
View File
@@ -404,6 +404,7 @@ pub struct LidarrHistoryItem {
#[serde(default)] #[serde(default)]
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
#[serde(deserialize_with = "super::from_json_or_default")]
pub event_type: LidarrHistoryEventType, pub event_type: LidarrHistoryEventType,
#[serde(default)] #[serde(default)]
pub data: LidarrHistoryData, pub data: LidarrHistoryData,
+149 -1
View File
@@ -2,7 +2,7 @@
mod tests { mod tests {
use chrono::Utc; use chrono::Utc;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::json; use serde_json::{Value, json};
use crate::models::lidarr_models::{ use crate::models::lidarr_models::{
AddArtistSearchResult, Album, AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord, AddArtistSearchResult, Album, AudioTags, BlocklistItem, BlocklistResponse, DownloadRecord,
@@ -773,6 +773,126 @@ mod tests {
); );
} }
#[test]
fn test_lidarr_history_event_type_deserialization() {
assert_eq!(
deserialize_history_event_type(json!("unknown")),
LidarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!("grabbed")),
LidarrHistoryEventType::Grabbed
);
assert_eq!(
deserialize_history_event_type(json!("artistFolderImported")),
LidarrHistoryEventType::ArtistFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("albumImportIncomplete")),
LidarrHistoryEventType::AlbumImportIncomplete
);
assert_eq!(
deserialize_history_event_type(json!("downloadIgnored")),
LidarrHistoryEventType::DownloadIgnored
);
assert_eq!(
deserialize_history_event_type(json!("downloadImported")),
LidarrHistoryEventType::DownloadImported
);
assert_eq!(
deserialize_history_event_type(json!("downloadFailed")),
LidarrHistoryEventType::DownloadFailed
);
assert_eq!(
deserialize_history_event_type(json!("trackFileDeleted")),
LidarrHistoryEventType::TrackFileDeleted
);
assert_eq!(
deserialize_history_event_type(json!("trackFileImported")),
LidarrHistoryEventType::TrackFileImported
);
assert_eq!(
deserialize_history_event_type(json!("trackFileRenamed")),
LidarrHistoryEventType::TrackFileRenamed
);
assert_eq!(
deserialize_history_event_type(json!("trackFileRetagged")),
LidarrHistoryEventType::TrackFileRetagged
);
}
#[test]
fn test_lidarr_history_event_type_deserialization_falls_back_to_unknown() {
assert_eq!(
deserialize_history_event_type(json!("albumImportPartiallyComplete")),
LidarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(2)),
LidarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(11)),
LidarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(null)),
LidarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!({ "id": 2 })),
LidarrHistoryEventType::Unknown
);
}
#[test]
fn test_lidarr_history_event_type_serialization() {
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::Unknown),
"unknown"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::Grabbed),
"grabbed"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::ArtistFolderImported),
"artistFolderImported"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::AlbumImportIncomplete),
"albumImportIncomplete"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::DownloadIgnored),
"downloadIgnored"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::DownloadImported),
"downloadImported"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::DownloadFailed),
"downloadFailed"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::TrackFileDeleted),
"trackFileDeleted"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::TrackFileImported),
"trackFileImported"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::TrackFileRenamed),
"trackFileRenamed"
);
assert_str_eq!(
serialize_history_event_type(LidarrHistoryEventType::TrackFileRetagged),
"trackFileRetagged"
);
}
#[test] #[test]
fn test_add_artist_search_result_deserialization() { fn test_add_artist_search_result_deserialization() {
let search_result_json = json!({ let search_result_json = json!({
@@ -825,4 +945,32 @@ mod tests {
assert!(search_result.genres.is_empty()); assert!(search_result.genres.is_empty());
assert_none!(&search_result.ratings); assert_none!(&search_result.ratings);
} }
fn deserialize_history_event_type(event_type: Value) -> LidarrHistoryEventType {
let history_item_json = json!({
"id": 1,
"sourceTitle": "Test Source Title",
"albumId": 1,
"artistId": 1,
"trackId": 1,
"date": "2024-01-01T00:00:00Z",
"eventType": event_type
});
serde_json::from_value::<LidarrHistoryItem>(history_item_json)
.unwrap()
.event_type
}
fn serialize_history_event_type(event_type: LidarrHistoryEventType) -> String {
let history_item = LidarrHistoryItem {
event_type,
..LidarrHistoryItem::default()
};
serde_json::to_value(history_item).unwrap()["eventType"]
.as_str()
.unwrap()
.to_owned()
}
} }
+10 -1
View File
@@ -11,7 +11,7 @@ use radarr_models::RadarrSerdeable;
use readarr_models::ReadarrSerdeable; use readarr_models::ReadarrSerdeable;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_json::Number; use serde_json::{Number, Value};
use servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use sonarr_models::SonarrSerdeable; use sonarr_models::SonarrSerdeable;
@@ -442,6 +442,15 @@ where
))) )))
} }
pub fn from_json_or_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
let value: Value = Deserialize::deserialize(deserializer)?;
Ok(T::deserialize(value).unwrap_or_default())
}
pub fn strip_non_search_characters(input: &str) -> String { pub fn strip_non_search_characters(input: &str) -> String {
Regex::new(r"[^a-zA-Z0-9.,/'\-:\s]") Regex::new(r"[^a-zA-Z0-9.,/'\-:\s]")
.unwrap() .unwrap()
+16 -1
View File
@@ -11,12 +11,13 @@ mod tests {
use crate::models::{ use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, TabRoute, TabState, BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, TabRoute, TabState,
}; };
use crate::models::{from_i64, strip_non_search_characters}; use crate::models::{from_i64, from_json_or_default, strip_non_search_characters};
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde::de::IntoDeserializer; use serde::de::IntoDeserializer;
use serde::de::value::Error as ValueError; use serde::de::value::Error as ValueError;
use serde::de::value::F64Deserializer; use serde::de::value::F64Deserializer;
use serde::de::value::I64Deserializer; use serde::de::value::I64Deserializer;
use serde::de::value::StrDeserializer;
use serde_json::to_string; use serde_json::to_string;
const BLOCKS: &[&[i32]] = &[&[11, 12], &[21, 22], &[31, 32]]; const BLOCKS: &[&[i32]] = &[&[11, 12], &[21, 22], &[31, 32]];
@@ -803,6 +804,20 @@ mod tests {
assert_ok_eq_x!(from_f64(deserializer), 1.0); assert_ok_eq_x!(from_f64(deserializer), 1.0);
} }
#[test]
fn test_from_json_or_default() {
let deserializer: I64Deserializer<ValueError> = 1i64.into_deserializer();
assert_ok_eq_x!(from_json_or_default::<_, i64>(deserializer), 1);
}
#[test]
fn test_from_json_or_default_falls_back_to_default() {
let deserializer: StrDeserializer<'_, ValueError> = "unexpected".into_deserializer();
assert_ok_eq_x!(from_json_or_default::<_, i64>(deserializer), 0);
}
#[test] #[test]
fn test_horizontally_scrollable_serialize() { fn test_horizontally_scrollable_serialize() {
let text = HorizontallyScrollableText::from("Test"); let text = HorizontallyScrollableText::from("Test");
+1
View File
@@ -459,6 +459,7 @@ pub struct RadarrHistoryItem {
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub languages: Vec<Language>, pub languages: Vec<Language>,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
#[serde(deserialize_with = "super::from_json_or_default")]
pub event_type: RadarrHistoryEventType, pub event_type: RadarrHistoryEventType,
#[serde(default)] #[serde(default)]
pub data: RadarrHistoryData, pub data: RadarrHistoryData,
+125 -1
View File
@@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::json; use serde_json::{Value, json};
use crate::models::radarr_models::{ use crate::models::radarr_models::{
RadarrHistoryEventType, RadarrHistoryItem, RadarrHistoryWrapper, RadarrHistoryEventType, RadarrHistoryItem, RadarrHistoryWrapper,
@@ -126,6 +126,102 @@ mod tests {
); );
} }
#[test]
fn test_radarr_history_event_type_deserialization() {
assert_eq!(
deserialize_history_event_type(json!("unknown")),
RadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!("grabbed")),
RadarrHistoryEventType::Grabbed
);
assert_eq!(
deserialize_history_event_type(json!("downloadFolderImported")),
RadarrHistoryEventType::DownloadFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("downloadFailed")),
RadarrHistoryEventType::DownloadFailed
);
assert_eq!(
deserialize_history_event_type(json!("movieFileDeleted")),
RadarrHistoryEventType::MovieFileDeleted
);
assert_eq!(
deserialize_history_event_type(json!("movieFolderImported")),
RadarrHistoryEventType::MovieFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("movieFileRenamed")),
RadarrHistoryEventType::MovieFileRenamed
);
assert_eq!(
deserialize_history_event_type(json!("downloadIgnored")),
RadarrHistoryEventType::DownloadIgnored
);
}
#[test]
fn test_radarr_history_event_type_deserialization_falls_back_to_unknown() {
assert_eq!(
deserialize_history_event_type(json!("movieFileImportFailed")),
RadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(2)),
RadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(11)),
RadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(null)),
RadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!({ "id": 2 })),
RadarrHistoryEventType::Unknown
);
}
#[test]
fn test_radarr_history_event_type_serialization() {
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::Unknown),
"unknown"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::Grabbed),
"grabbed"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::DownloadFolderImported),
"downloadFolderImported"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::DownloadFailed),
"downloadFailed"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::MovieFileDeleted),
"movieFileDeleted"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::MovieFolderImported),
"movieFolderImported"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::MovieFileRenamed),
"movieFileRenamed"
);
assert_str_eq!(
serialize_history_event_type(RadarrHistoryEventType::DownloadIgnored),
"downloadIgnored"
);
}
#[test] #[test]
fn test_download_record_default_indexer_value() { fn test_download_record_default_indexer_value() {
let json = r#"{ let json = r#"{
@@ -517,4 +613,32 @@ mod tests {
RadarrSerdeable::IndexerTestResults(indexer_test_results) RadarrSerdeable::IndexerTestResults(indexer_test_results)
); );
} }
fn deserialize_history_event_type(event_type: Value) -> RadarrHistoryEventType {
let history_item_json = json!({
"id": 1,
"sourceTitle": "Test Source Title",
"movieId": 1,
"quality": { "quality": { "name": "HD - 1080p" } },
"languages": [],
"date": "2024-01-01T00:00:00Z",
"eventType": event_type
});
serde_json::from_value::<RadarrHistoryItem>(history_item_json)
.unwrap()
.event_type
}
fn serialize_history_event_type(event_type: RadarrHistoryEventType) -> String {
let history_item = RadarrHistoryItem {
event_type,
..RadarrHistoryItem::default()
};
serde_json::to_value(history_item).unwrap()["eventType"]
.as_str()
.unwrap()
.to_owned()
}
} }
+1
View File
@@ -413,6 +413,7 @@ pub struct ReadarrHistoryItem {
#[serde(default)] #[serde(default)]
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
#[serde(deserialize_with = "super::from_json_or_default")]
pub event_type: ReadarrHistoryEventType, pub event_type: ReadarrHistoryEventType,
#[serde(default)] #[serde(default)]
pub data: ReadarrHistoryData, pub data: ReadarrHistoryData,
+148 -1
View File
@@ -2,7 +2,7 @@
mod tests { mod tests {
use chrono::Utc; use chrono::Utc;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::json; use serde_json::{Value, json};
use crate::models::readarr_models::{ use crate::models::readarr_models::{
AddAuthorSearchResult, BlocklistItem, BlocklistResponse, Book, BookFile, DownloadRecord, AddAuthorSearchResult, BlocklistItem, BlocklistResponse, Book, BookFile, DownloadRecord,
@@ -758,6 +758,126 @@ mod tests {
); );
} }
#[test]
fn test_readarr_history_event_type_deserialization() {
assert_eq!(
deserialize_history_event_type(json!("unknown")),
ReadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!("grabbed")),
ReadarrHistoryEventType::Grabbed
);
assert_eq!(
deserialize_history_event_type(json!("authorFolderImported")),
ReadarrHistoryEventType::AuthorFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("bookImportIncomplete")),
ReadarrHistoryEventType::BookImportIncomplete
);
assert_eq!(
deserialize_history_event_type(json!("downloadIgnored")),
ReadarrHistoryEventType::DownloadIgnored
);
assert_eq!(
deserialize_history_event_type(json!("downloadImported")),
ReadarrHistoryEventType::DownloadImported
);
assert_eq!(
deserialize_history_event_type(json!("downloadFailed")),
ReadarrHistoryEventType::DownloadFailed
);
assert_eq!(
deserialize_history_event_type(json!("bookFileDeleted")),
ReadarrHistoryEventType::BookFileDeleted
);
assert_eq!(
deserialize_history_event_type(json!("bookFileImported")),
ReadarrHistoryEventType::BookFileImported
);
assert_eq!(
deserialize_history_event_type(json!("bookFileRenamed")),
ReadarrHistoryEventType::BookFileRenamed
);
assert_eq!(
deserialize_history_event_type(json!("bookFileRetagged")),
ReadarrHistoryEventType::BookFileRetagged
);
}
#[test]
fn test_readarr_history_event_type_deserialization_falls_back_to_unknown() {
assert_eq!(
deserialize_history_event_type(json!("bookImportPartiallyComplete")),
ReadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(2)),
ReadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(11)),
ReadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(null)),
ReadarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!({ "id": 2 })),
ReadarrHistoryEventType::Unknown
);
}
#[test]
fn test_readarr_history_event_type_serialization() {
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::Unknown),
"unknown"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::Grabbed),
"grabbed"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::AuthorFolderImported),
"authorFolderImported"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::BookImportIncomplete),
"bookImportIncomplete"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::DownloadIgnored),
"downloadIgnored"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::DownloadImported),
"downloadImported"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::DownloadFailed),
"downloadFailed"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::BookFileDeleted),
"bookFileDeleted"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::BookFileImported),
"bookFileImported"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::BookFileRenamed),
"bookFileRenamed"
);
assert_str_eq!(
serialize_history_event_type(ReadarrHistoryEventType::BookFileRetagged),
"bookFileRetagged"
);
}
#[test] #[test]
fn test_readarr_task_name_display() { fn test_readarr_task_name_display() {
assert_str_eq!( assert_str_eq!(
@@ -1019,4 +1139,31 @@ mod tests {
assert!(search_result.genres.is_empty()); assert!(search_result.genres.is_empty());
assert_none!(&search_result.ratings); assert_none!(&search_result.ratings);
} }
fn deserialize_history_event_type(event_type: Value) -> ReadarrHistoryEventType {
let history_item_json = json!({
"id": 1,
"authorId": 1,
"bookId": 1,
"sourceTitle": "Test Source Title",
"date": "2024-01-01T00:00:00Z",
"eventType": event_type
});
serde_json::from_value::<ReadarrHistoryItem>(history_item_json)
.unwrap()
.event_type
}
fn serialize_history_event_type(event_type: ReadarrHistoryEventType) -> String {
let history_item = ReadarrHistoryItem {
event_type,
..ReadarrHistoryItem::default()
};
serde_json::to_value(history_item).unwrap()["eventType"]
.as_str()
.unwrap()
.to_owned()
}
} }
+1
View File
@@ -499,6 +499,7 @@ pub struct SonarrHistoryItem {
pub quality: QualityWrapper, pub quality: QualityWrapper,
pub languages: Vec<Option<Language>>, pub languages: Vec<Option<Language>>,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
#[serde(deserialize_with = "super::from_json_or_default")]
pub event_type: SonarrHistoryEventType, pub event_type: SonarrHistoryEventType,
pub data: SonarrHistoryData, pub data: SonarrHistoryData,
} }
+126 -1
View File
@@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::json; use serde_json::{Value, json};
use crate::models::{ use crate::models::{
Serdeable, Serdeable,
@@ -213,6 +213,102 @@ mod tests {
); );
} }
#[test]
fn test_sonarr_history_event_type_deserialization() {
assert_eq!(
deserialize_history_event_type(json!("unknown")),
SonarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!("grabbed")),
SonarrHistoryEventType::Grabbed
);
assert_eq!(
deserialize_history_event_type(json!("seriesFolderImported")),
SonarrHistoryEventType::SeriesFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("downloadFolderImported")),
SonarrHistoryEventType::DownloadFolderImported
);
assert_eq!(
deserialize_history_event_type(json!("downloadFailed")),
SonarrHistoryEventType::DownloadFailed
);
assert_eq!(
deserialize_history_event_type(json!("episodeFileDeleted")),
SonarrHistoryEventType::EpisodeFileDeleted
);
assert_eq!(
deserialize_history_event_type(json!("episodeFileRenamed")),
SonarrHistoryEventType::EpisodeFileRenamed
);
assert_eq!(
deserialize_history_event_type(json!("downloadIgnored")),
SonarrHistoryEventType::DownloadIgnored
);
}
#[test]
fn test_sonarr_history_event_type_deserialization_falls_back_to_unknown() {
assert_eq!(
deserialize_history_event_type(json!("episodeFileImportFailed")),
SonarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(2)),
SonarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(11)),
SonarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!(null)),
SonarrHistoryEventType::Unknown
);
assert_eq!(
deserialize_history_event_type(json!({ "id": 2 })),
SonarrHistoryEventType::Unknown
);
}
#[test]
fn test_sonarr_history_event_type_serialization() {
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::Unknown),
"unknown"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::Grabbed),
"grabbed"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::SeriesFolderImported),
"seriesFolderImported"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::DownloadFolderImported),
"downloadFolderImported"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::DownloadFailed),
"downloadFailed"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::EpisodeFileDeleted),
"episodeFileDeleted"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::EpisodeFileRenamed),
"episodeFileRenamed"
);
assert_str_eq!(
serialize_history_event_type(SonarrHistoryEventType::DownloadIgnored),
"downloadIgnored"
);
}
#[test] #[test]
fn test_task_name_display() { fn test_task_name_display() {
assert_str_eq!( assert_str_eq!(
@@ -601,4 +697,33 @@ mod tests {
SonarrSerdeable::IndexerTestResults(indexer_test_results) SonarrSerdeable::IndexerTestResults(indexer_test_results)
); );
} }
fn deserialize_history_event_type(event_type: Value) -> SonarrHistoryEventType {
let history_item_json = json!({
"id": 1,
"sourceTitle": "Test Source Title",
"episodeId": 1,
"quality": { "quality": { "name": "HDTV - 1080p" } },
"languages": [],
"date": "2024-01-01T00:00:00Z",
"eventType": event_type,
"data": {}
});
serde_json::from_value::<SonarrHistoryItem>(history_item_json)
.unwrap()
.event_type
}
fn serialize_history_event_type(event_type: SonarrHistoryEventType) -> String {
let history_item = SonarrHistoryItem {
event_type,
..SonarrHistoryItem::default()
};
serde_json::to_value(history_item).unwrap()["eventType"]
.as_str()
.unwrap()
.to_owned()
}
} }