Skip to content

Commit 25f45c4

Browse files
committed
fix(pact_models): V4 body with no content attribute is not a missing body
1 parent c1c39f3 commit 25f45c4

2 files changed

Lines changed: 59 additions & 2 deletions

File tree

rust/pact_models/src/v4/async_message.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,23 @@ mod tests {
522522
use crate::v4::interaction::V4Interaction;
523523
use crate::v4::message_parts::MessageContents;
524524

525+
#[test]
526+
fn message_contents_without_a_content_attribute_are_not_dropped() {
527+
// A hand-written pact may put the payload where the body attributes belong. Reading it as a
528+
// missing body made an interaction verify successfully whatever the provider produced.
529+
// See https://github.com/pact-foundation/pact-python/issues/1103
530+
let json = json!({
531+
"type": "Asynchronous/Messages",
532+
"description": "a message",
533+
"contents": {
534+
"Payload": "exists"
535+
}
536+
});
537+
let message = AsynchronousMessage::from_json(&json, 0).unwrap();
538+
expect!(message.contents.contents).to(be_equal_to(
539+
OptionalBody::Present("{\"Payload\":\"exists\"}".into(), None, None)));
540+
}
541+
525542
#[test]
526543
fn when_downgrading_message_to_v3_rename_the_matching_rules_from_content_to_body() {
527544
let message = AsynchronousMessage {

rust/pact_models/src/v4/http_parts.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ impl HttpPart for HttpRequest {
269269
}
270270
}
271271

272+
/// Attributes a V4 body fragment may hold alongside its content.
273+
const BODY_ATTRIBUTES: [&str; 4] = ["content", "contentType", "contentTypeHint", "encoded"];
274+
272275
/// Set up an OptionalBody from a JSON fragment. The contents for the body will be looked up from
273276
/// the attribute given by `attr_name`. The headers will be used to work out the content type,
274277
/// if required.
@@ -361,8 +364,16 @@ pub fn body_from_json(json: &Value, attr_name: &str, headers: &Option<HashMap<St
361364
}
362365
},
363366

364-
// No content attribute, assume a missing body
365-
None => OptionalBody::Missing
367+
None => if body_attrs.keys().all(|k| BODY_ATTRIBUTES.contains(&k.as_str())) {
368+
// Only body attributes, so the body really is missing
369+
OptionalBody::Missing
370+
} else {
371+
// The payload has been set where the body attributes belong. Treat it the same as a
372+
// body that is not a JSON object, rather than silently dropping it.
373+
warn!("Body in attribute '{}' from JSON file has no 'content' attribute, will load the \
374+
whole value as the body", attr_name);
375+
OptionalBody::Present(body.to_string().into(), None, None)
376+
}
366377
}
367378
},
368379

@@ -993,6 +1004,35 @@ mod tests {
9931004
expect!(body).to(be_equal_to(OptionalBody::Null));
9941005
}
9951006

1007+
#[test]
1008+
fn body_from_json_returns_missing_if_the_body_only_has_body_attributes() {
1009+
let json = json!({
1010+
"body": {
1011+
"contentType": "application/json",
1012+
"encoded": false
1013+
}
1014+
});
1015+
expect!(body_from_json(&json, "body", &None)).to(be_equal_to(OptionalBody::Missing));
1016+
1017+
let json = json!({
1018+
"body": {}
1019+
});
1020+
expect!(body_from_json(&json, "body", &None)).to(be_equal_to(OptionalBody::Missing));
1021+
}
1022+
1023+
#[test]
1024+
fn body_from_json_loads_the_whole_value_if_the_body_has_no_content_attribute() {
1025+
// The payload has been set where the body attributes belong. Previously this was read as a
1026+
// missing body, so an interaction with a mismatched payload verified successfully.
1027+
let json = json!({
1028+
"body": {
1029+
"Payload": "exists"
1030+
}
1031+
});
1032+
expect!(body_from_json(&json, "body", &None)).to(be_equal_to(
1033+
OptionalBody::Present("{\"Payload\":\"exists\"}".into(), None, None)));
1034+
}
1035+
9961036
#[test]
9971037
fn body_from_json_returns_json_string_if_the_body_is_json_but_not_a_string() {
9981038
let json = json!({

0 commit comments

Comments
 (0)