Skip to content

Commit 0e5ca6b

Browse files
Merge pull request #671 from DataDog/dd/fix/on-call-pages-get-empty-body
fix(on-call): handle empty 200 body in pages get
2 parents f72e537 + d3465c3 commit 0e5ca6b

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

src/commands/on_call.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,29 @@ mod tests {
885885
cleanup_env();
886886
}
887887

888+
// Regression test for #638: the on-call pages GET endpoint can return a 200
889+
// with an empty body (content-length: 0). pages_get must succeed instead of
890+
// failing with "EOF while parsing value at line 1 column 0".
891+
#[tokio::test]
892+
async fn test_on_call_pages_get_empty_body() {
893+
let _lock = lock_env().await;
894+
let mut s = mockito::Server::new_async().await;
895+
let cfg = test_config(&s.url());
896+
s.mock("GET", "/api/v2/on-call/pages/12345")
897+
.with_status(200)
898+
.with_header("content-type", "application/json")
899+
.with_body("")
900+
.create_async()
901+
.await;
902+
let result = super::pages_get(&cfg, "12345").await;
903+
assert!(
904+
result.is_ok(),
905+
"pages_get with empty body failed: {:?}",
906+
result.err()
907+
);
908+
cleanup_env();
909+
}
910+
888911
#[tokio::test]
889912
async fn test_on_call_pages_get_not_found() {
890913
let _lock = lock_env().await;

src/raw_client.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ impl std::error::Error for HttpError {}
3737
async fn parse_response_json(resp: reqwest::Response) -> anyhow::Result<serde_json::Value> {
3838
use serde::Deserialize;
3939
let bytes = resp.bytes().await?;
40+
// Some endpoints return a success status (e.g. 200) with an empty body, such
41+
// as GET /api/v2/on-call/pages/{id} which responds with content-length: 0.
42+
// Treat an empty or whitespace-only body as JSON null rather than failing
43+
// with "EOF while parsing value at line 1 column 0".
44+
if bytes.iter().all(u8::is_ascii_whitespace) {
45+
return Ok(serde_json::Value::Null);
46+
}
4047
let mut de = serde_json::Deserializer::from_slice(&bytes);
4148
de.disable_recursion_limit();
4249
let de = serde_stacker::Deserializer::new(&mut de);
@@ -1106,4 +1113,72 @@ mod tests {
11061113
);
11071114
cleanup_env();
11081115
}
1116+
1117+
/// Regression test: a 200 response with an empty body must parse as JSON null
1118+
/// instead of failing with "EOF while parsing value at line 1 column 0".
1119+
#[tokio::test]
1120+
async fn test_raw_get_empty_body_returns_null() {
1121+
let _lock = lock_env().await;
1122+
let mut server = mockito::Server::new_async().await;
1123+
let cfg = test_config(&server.url());
1124+
let _mock = server
1125+
.mock("GET", "/api/v2/on-call/pages/12345")
1126+
.with_status(200)
1127+
.with_header("content-type", "application/json")
1128+
.with_body("")
1129+
.create_async()
1130+
.await;
1131+
let resp = super::raw_get(&cfg, "/api/v2/on-call/pages/12345", &[]).await;
1132+
assert!(
1133+
resp.is_ok(),
1134+
"raw_get with empty body failed: {:?}",
1135+
resp.err()
1136+
);
1137+
assert_eq!(resp.unwrap(), serde_json::Value::Null);
1138+
cleanup_env();
1139+
}
1140+
1141+
/// A whitespace-only body is also unparseable JSON and must be treated as null.
1142+
#[tokio::test]
1143+
async fn test_raw_get_whitespace_body_returns_null() {
1144+
let _lock = lock_env().await;
1145+
let mut server = mockito::Server::new_async().await;
1146+
let cfg = test_config(&server.url());
1147+
let _mock = server
1148+
.mock("GET", "/api/v2/on-call/pages/12345")
1149+
.with_status(200)
1150+
.with_header("content-type", "application/json")
1151+
.with_body(" \n\t ")
1152+
.create_async()
1153+
.await;
1154+
let resp = super::raw_get(&cfg, "/api/v2/on-call/pages/12345", &[]).await;
1155+
assert!(
1156+
resp.is_ok(),
1157+
"raw_get with whitespace body failed: {:?}",
1158+
resp.err()
1159+
);
1160+
assert_eq!(resp.unwrap(), serde_json::Value::Null);
1161+
cleanup_env();
1162+
}
1163+
1164+
/// A non-empty JSON body must still parse normally (the empty-body guard must
1165+
/// not shadow the regular parse path).
1166+
#[tokio::test]
1167+
async fn test_raw_get_nonempty_body_parses() {
1168+
let _lock = lock_env().await;
1169+
let mut server = mockito::Server::new_async().await;
1170+
let cfg = test_config(&server.url());
1171+
let _mock = server
1172+
.mock("GET", "/api/v2/on-call/pages/12345")
1173+
.with_status(200)
1174+
.with_header("content-type", "application/json")
1175+
.with_body(r#"{"data": {"id": "12345"}}"#)
1176+
.create_async()
1177+
.await;
1178+
let resp = super::raw_get(&cfg, "/api/v2/on-call/pages/12345", &[])
1179+
.await
1180+
.expect("raw_get with JSON body should succeed");
1181+
assert_eq!(resp["data"]["id"], "12345");
1182+
cleanup_env();
1183+
}
11091184
}

0 commit comments

Comments
 (0)