fix(readarr): attach editions when toggling book monitoring
Toggling book monitoring failed with an HTTP 500, "Value cannot be null.
(Parameter 'source')", every time, in both the CLI and the TUI.
GET /book/{id} omits the editions field entirely, but PUT /book/{id}
requires it, so round-tripping Readarr's own response back as an update
was always rejected. Editions are served separately from
GET /edition?bookId=, so fetch them and attach them to the body before
the PUT.
This commit is contained in:
2 files changed
+135
-4
No files matched your search
@@ -275,10 +275,34 @@ impl Network<'_, '_> {
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
info!("Fetching book editions for book with ID: {book_id}");
|
||||||
|
|
||||||
|
let editions_event = ReadarrEvent::GetBookEditions(book_id);
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
editions_event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
None,
|
||||||
|
Some(format!("bookId={book_id}")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut editions_response = String::new();
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Value>(request_props, |editions_body, _| {
|
||||||
|
editions_response = editions_body.to_string()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
info!("Constructing toggle book monitoring body");
|
info!("Constructing toggle book monitoring body");
|
||||||
|
|
||||||
match serde_json::from_str::<Value>(&response) {
|
match (
|
||||||
Ok(mut detailed_book_body) => {
|
serde_json::from_str::<Value>(&response),
|
||||||
|
serde_json::from_str::<Value>(&editions_response),
|
||||||
|
) {
|
||||||
|
(Ok(mut detailed_book_body), Ok(editions)) => {
|
||||||
let monitored = detailed_book_body
|
let monitored = detailed_book_body
|
||||||
.get("monitored")
|
.get("monitored")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -286,6 +310,10 @@ impl Network<'_, '_> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
*detailed_book_body.get_mut("monitored").unwrap() = json!(!monitored);
|
*detailed_book_body.get_mut("monitored").unwrap() = json!(!monitored);
|
||||||
|
detailed_book_body
|
||||||
|
.as_object_mut()
|
||||||
|
.unwrap()
|
||||||
|
.insert("editions".to_owned(), editions);
|
||||||
|
|
||||||
debug!("Toggle book monitoring body: {detailed_book_body:?}");
|
debug!("Toggle book monitoring body: {detailed_book_body:?}");
|
||||||
|
|
||||||
@@ -303,8 +331,8 @@ impl Network<'_, '_> {
|
|||||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Err(_) => {
|
_ => {
|
||||||
warn!("Request for detailed book body was interrupted");
|
warn!("Request for detailed book body or book editions was interrupted");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1020,15 +1020,53 @@ mod tests {
|
|||||||
#[case] initial_monitored: bool,
|
#[case] initial_monitored: bool,
|
||||||
#[case] expected_monitored: bool,
|
#[case] expected_monitored: bool,
|
||||||
) {
|
) {
|
||||||
|
let editions_json = json!([
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"bookId": 1,
|
||||||
|
"foreignEditionId": "test-foreign-edition-id-2",
|
||||||
|
"monitored": false,
|
||||||
|
"isEbook": false,
|
||||||
|
"title": "Second Edition",
|
||||||
|
"language": "eng",
|
||||||
|
"overview": "the second edition",
|
||||||
|
"format": "Hardcover",
|
||||||
|
"publisher": "Second Publisher",
|
||||||
|
"pageCount": 128,
|
||||||
|
"releaseDate": "2023-02-01T00:00:00Z",
|
||||||
|
"isbn13": "9780000000002",
|
||||||
|
"asin": "B000000002",
|
||||||
|
"ratings": { "votes": 15, "value": 8.4, "popularity": 1.2 }
|
||||||
|
},
|
||||||
|
serde_json::from_str::<Value>(EDITION_JSON).unwrap()
|
||||||
|
]);
|
||||||
let mut book_json: Value = serde_json::from_str(BOOK_JSON).unwrap();
|
let mut book_json: Value = serde_json::from_str(BOOK_JSON).unwrap();
|
||||||
*book_json.get_mut("monitored").unwrap() = json!(initial_monitored);
|
*book_json.get_mut("monitored").unwrap() = json!(initial_monitored);
|
||||||
let mut expected_body = book_json.clone();
|
let mut expected_body = book_json.clone();
|
||||||
*expected_body.get_mut("monitored").unwrap() = json!(expected_monitored);
|
*expected_body.get_mut("monitored").unwrap() = json!(expected_monitored);
|
||||||
|
expected_body
|
||||||
|
.as_object_mut()
|
||||||
|
.unwrap()
|
||||||
|
.insert("editions".to_owned(), editions_json.clone());
|
||||||
let (async_details_server, app, mut server) = MockServarrApi::get()
|
let (async_details_server, app, mut server) = MockServarrApi::get()
|
||||||
.returns(book_json)
|
.returns(book_json)
|
||||||
.path("/1")
|
.path("/1")
|
||||||
.build_for(ReadarrEvent::GetBookDetails(1))
|
.build_for(ReadarrEvent::GetBookDetails(1))
|
||||||
.await;
|
.await;
|
||||||
|
let async_editions_server = server
|
||||||
|
.mock(
|
||||||
|
"GET",
|
||||||
|
format!(
|
||||||
|
"/api/v1{}?bookId=1",
|
||||||
|
ReadarrEvent::GetBookEditions(1).resource()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.with_status(200)
|
||||||
|
.match_header("X-Api-Key", "test1234")
|
||||||
|
.with_body(editions_json.to_string())
|
||||||
|
.create_async()
|
||||||
|
.await;
|
||||||
let async_toggle_server = server
|
let async_toggle_server = server
|
||||||
.mock(
|
.mock(
|
||||||
"PUT",
|
"PUT",
|
||||||
@@ -1051,8 +1089,10 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
async_details_server.assert_async().await;
|
async_details_server.assert_async().await;
|
||||||
|
async_editions_server.assert_async().await;
|
||||||
async_toggle_server.assert_async().await;
|
async_toggle_server.assert_async().await;
|
||||||
assert_ok!(result);
|
assert_ok!(result);
|
||||||
|
assert_none!(app.lock().await.data.readarr_data.book_details_modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1063,6 +1103,20 @@ mod tests {
|
|||||||
.path("/1")
|
.path("/1")
|
||||||
.build_for(ReadarrEvent::GetBookDetails(1))
|
.build_for(ReadarrEvent::GetBookDetails(1))
|
||||||
.await;
|
.await;
|
||||||
|
let async_editions_server = server
|
||||||
|
.mock(
|
||||||
|
"GET",
|
||||||
|
format!(
|
||||||
|
"/api/v1{}?bookId=1",
|
||||||
|
ReadarrEvent::GetBookEditions(1).resource()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.with_status(200)
|
||||||
|
.match_header("X-Api-Key", "test1234")
|
||||||
|
.expect(0)
|
||||||
|
.create_async()
|
||||||
|
.await;
|
||||||
let async_toggle_server = server
|
let async_toggle_server = server
|
||||||
.mock(
|
.mock(
|
||||||
"PUT",
|
"PUT",
|
||||||
@@ -1085,6 +1139,55 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
async_details_server.assert_async().await;
|
async_details_server.assert_async().await;
|
||||||
|
async_editions_server.assert_async().await;
|
||||||
|
async_toggle_server.assert_async().await;
|
||||||
|
assert_err!(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_toggle_book_monitoring_event_editions_failure() {
|
||||||
|
let (async_details_server, app, mut server) = MockServarrApi::get()
|
||||||
|
.returns(serde_json::from_str(BOOK_JSON).unwrap())
|
||||||
|
.path("/1")
|
||||||
|
.build_for(ReadarrEvent::GetBookDetails(1))
|
||||||
|
.await;
|
||||||
|
let async_editions_server = server
|
||||||
|
.mock(
|
||||||
|
"GET",
|
||||||
|
format!(
|
||||||
|
"/api/v1{}?bookId=1",
|
||||||
|
ReadarrEvent::GetBookEditions(1).resource()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.with_status(404)
|
||||||
|
.match_header("X-Api-Key", "test1234")
|
||||||
|
.with_body(json!([serde_json::from_str::<Value>(EDITION_JSON).unwrap()]).to_string())
|
||||||
|
.create_async()
|
||||||
|
.await;
|
||||||
|
let async_toggle_server = server
|
||||||
|
.mock(
|
||||||
|
"PUT",
|
||||||
|
format!(
|
||||||
|
"/api/v1{}/1",
|
||||||
|
ReadarrEvent::ToggleBookMonitoring(1).resource()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.with_status(202)
|
||||||
|
.match_header("X-Api-Key", "test1234")
|
||||||
|
.expect(0)
|
||||||
|
.create_async()
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(3);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_readarr_event(ReadarrEvent::ToggleBookMonitoring(1))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
async_details_server.assert_async().await;
|
||||||
|
async_editions_server.assert_async().await;
|
||||||
async_toggle_server.assert_async().await;
|
async_toggle_server.assert_async().await;
|
||||||
assert_err!(result);
|
assert_err!(result);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user