Skip to content

Commit

Permalink
fix(plugins): Upgrade pact-plugin-driver to 0.7.2, fixes #473
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Nov 29, 2024
1 parent 35a719f commit 44d9dc1
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 204 deletions.
413 changes: 232 additions & 181 deletions rust/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion rust/pact_consumer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ maplit = "1.0.2"
pact_matching = { version = "~1.2.7", path = "../pact_matching", default-features = false }
pact_mock_server = { version = "~2.0.2", default-features = false }
pact_models = { version = "~1.2.5", default-features = false }
pact-plugin-driver = { version = "~0.7.1", optional = true, default-features = false }
pact-plugin-driver = { version = "~0.7.2", optional = true, default-features = false }
regex = "1.10.6"
serde_json = "1.0.127"
termsize = "0.1.9"
Expand All @@ -51,6 +51,7 @@ expectest = "0.12.0"
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls-native-roots", "blocking", "json"] }
serde = { version = "1.0.209", features = ["derive"] }
rand = "0.8.5"
tempfile = "3.14.0"
tokio-test = "0.4.4"
test-log = { version = "0.2.16", features = ["trace"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "tracing-log", "fmt"] }
Expand Down
28 changes: 20 additions & 8 deletions rust/pact_consumer/src/builders/message_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,18 @@ impl InteractionContents {
#[cfg(feature = "plugins")]
/// Create a new struct from a plugin specific one
pub fn from(contents: &pact_plugin_driver::content::InteractionContents) -> Self {
let metadata = contents.metadata.as_ref()
.map(|md| {
md.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
});
InteractionContents {
part_name: contents.part_name.clone(),
body: contents.body.clone(),
rules: contents.rules.clone(),
generators: contents.generators.clone(),
metadata: contents.metadata.clone(),
metadata,
metadata_rules: contents.metadata_rules.clone(),
plugin_config: PluginConfiguration::from(&contents.plugin_config),
interaction_markup: contents.interaction_markup.clone(),
Expand Down Expand Up @@ -226,14 +232,17 @@ impl MessageInteractionBuilder {
};
}

let metadata = self.message_contents.metadata.as_ref()
.map(|md| md.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
AsynchronousMessage {
id: None,
key: self.key.clone(),
description: self.description.clone(),
provider_states: self.provider_states.clone(),
contents: MessageContents {
contents: self.message_contents.body.clone(),
metadata: self.message_contents.metadata.as_ref().cloned().unwrap_or_default(),
metadata,
matching_rules: rules,
generators: self.message_contents.generators.as_ref().cloned().unwrap_or_default()
},
Expand All @@ -256,15 +265,18 @@ impl MessageInteractionBuilder {
rules.add_category("body")
.add_rules(self.message_contents.rules.as_ref().cloned().unwrap_or_default());

Message {
let metadata = self.message_contents.metadata.as_ref()
.map(|md| md.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
Message {
id: None,
description: self.description.clone(),
provider_states: self.provider_states.clone(),
contents: self.message_contents.body.clone(),
metadata: self.message_contents.metadata.as_ref().cloned().unwrap_or_default(),
matching_rules: rules,
generators: self.message_contents.generators.as_ref().cloned().unwrap_or_default()
}
metadata,
matching_rules: rules,
generators: self.message_contents.generators.as_ref().cloned().unwrap_or_default()
}
}

/// Configure the interaction contents from a map
Expand Down Expand Up @@ -427,7 +439,7 @@ impl MessageInteractionBuilder {
}

/// Specify the message payload and content type
pub fn body<B: Into<Bytes>>(&mut self, body: B, content_type: Option<String>) -> &mut Self {
pub fn body<B: Into<Bytes>>(&mut self, body: B, content_type: Option<String>) -> &mut Self {
let message_body = OptionalBody::Present(
body.into(),
content_type.as_ref().map(|ct| ct.into()),
Expand Down
181 changes: 173 additions & 8 deletions rust/pact_consumer/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ use std::{
fs,
path::Path
};
use std::io::Write;
use std::path::PathBuf;

use bytes::Bytes;
use expectest::prelude::*;
use maplit::hashmap;
use pact_models::content_types::ContentType;
use pact_models::pact::{read_pact, ReadWritePact};
use pact_models::prelude::OptionalBody;
use pact_models::provider_states::ProviderState;
use pact_models::sync_pact::RequestResponsePact;
use pact_models::v4::http_parts::{HttpRequest, HttpResponse};
use pact_models::v4::synch_http::SynchronousHttp;
use pretty_assertions::assert_eq;
use rand::prelude::*;
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::json;

use pact_consumer::{json_pattern, json_pattern_internal, like, object_matching, matching_regex};
use pact_consumer::{json_pattern, json_pattern_internal, like, matching_regex, object_matching};
use pact_consumer::mock_server::StartMockServerAsync;
use pact_consumer::prelude::*;
use pact_models::content_types::ContentType;
use pact_models::pact::{read_pact, ReadWritePact};
use pact_models::pact::write_pact;
use pact_models::PactSpecification;
use pact_models::prelude::OptionalBody;
use pact_models::provider_states::ProviderState;
use pact_models::sync_pact::RequestResponsePact;
use pact_models::v4::http_parts::{HttpRequest, HttpResponse};
use pact_models::v4::synch_http::SynchronousHttp;

/// This is supposed to be a doctest in mod, but it's breaking there, so
/// we have an executable copy here.
Expand Down Expand Up @@ -386,3 +390,164 @@ fn each_key_matcher() {
.unwrap();
expect!(response.status().is_server_error()).to(be_true());
}

const PROTO: &str = "
syntax = \"proto3\";
package area_calculator;
service Calculator {
rpc calculate (ShapeMessage) returns (AreaResponse) {}
}
message ShapeMessage {
oneof shape {
Square square = 1;
Rectangle rectangle = 2;
Circle circle = 3;
Triangle triangle = 4;
Parallelogram parallelogram = 5;
}
}
message Square {
float edge_length = 1;
}
message Rectangle {
float length = 1;
float width = 2;
}
message Circle {
float radius = 1;
}
message Triangle {
float edge_a = 1;
float edge_b = 2;
float edge_c = 3;
}
message Parallelogram {
float base_length = 1;
float height = 2;
}
message AreaResponse {
float value = 1;
}
";

// Issue https://github.com/YOU54F/pact-ruby-ffi/issues/6
// Note, this test requires the gRPC plugin to be installed
#[test_log::test(tokio::test(flavor = "multi_thread", worker_threads = 1))]
#[ignore = "test requires Protobuf plugin"]
async fn test_protobuf_plugin_contents_merge_with_existing_interaction() {
let tmp = tempfile::tempdir().unwrap();
let proto_path = tmp.path().join("area-calculator.proto");
let mut proto_file = fs::File::create(proto_path.clone()).unwrap();
proto_file.write_all(PROTO.as_bytes()).unwrap();

// 1. create an existing pact with single plugin interaction
// 2. create a new plugin interaction that only differs by interaction description
// expected: 2 interactions are generated, identical bar interaction description
// actual: existing interaction is modified at response[0].contents, when merging 2nd interaction

let pact_1_file_path = {
let proto_path_str = proto_path.to_string_lossy();
let mut pact_builder = PactBuilderAsync::new_v4("PluginMergeConsumer", "PluginMergeProvider");
let pact_1 = pact_builder
.using_plugin("protobuf", None).await
.synchronous_message_interaction("description 1", |mut i| async move {
i.contents_from(json!({
"pact:proto": proto_path_str,
"pact:content-type": "application/protobuf",
"pact:proto-service": "Calculator/calculate",

"request": {
"rectangle": {
"length": "matching(number, 3)",
"width": "matching(number, 4)"
}
},

"responseMetadata": {
"grpc-status": "UNIMPLEMENTED",
"grpc-message": "Not implemented"
}
})).await;
i
})
.await
.build();
let pact_file_name = pact_1.default_file_name();
let path_file_path = tmp.path().join(pact_file_name);
write_pact(pact_1, &path_file_path, PactSpecification::V4, false).unwrap();
path_file_path.clone()
};

let pact_file = fs::File::open(pact_1_file_path).unwrap();
let json: serde_json::Value = serde_json::from_reader(&pact_file).unwrap();

let interaction_1_response_contents = &json["interactions"][0]["response"][0]["contents"];
let expected_response_contents = json!({
"content": "",
});
assert_eq!(
&expected_response_contents,
interaction_1_response_contents
);

// Setup New interaction and write to existing pact file - validate .interactions[0].response[0].contents
let pact_2_file_path = {
let mut pact_builder_2 = PactBuilderAsync::new_v4("PluginMergeConsumer", "PluginMergeProvider");
let pact_2 = pact_builder_2
.using_plugin("protobuf", None).await
.synchronous_message_interaction("description 2", |mut i| async move {
i.contents_from(json!({
"pact:proto": proto_path,
"pact:content-type": "application/protobuf",
"pact:proto-service": "Calculator/calculate",

"request": {
"rectangle": {
"length": "matching(number, 3)",
"width": "matching(number, 4)"
}
},

"responseMetadata": {
"grpc-status": "UNIMPLEMENTED",
"grpc-message": "Not implemented"
}
})).await;
i
})
.await
.build();
let pact_file_name = pact_2.default_file_name();
let path_file_path = tmp.path().join(pact_file_name);
write_pact(pact_2, &path_file_path, PactSpecification::V4, false).unwrap();
path_file_path.clone()
};

let pact_file = fs::File::open(pact_2_file_path).unwrap();
let json_2: serde_json::Value = serde_json::from_reader(pact_file).unwrap();

let interaction_2_description = &json_2["interactions"][0]["description"];
let interaction_2_description_2 = &json_2["interactions"][1]["description"];
let interaction_2_response_contents = &json_2["interactions"][0]["response"][0]["contents"];
let interaction_2_response_contents_2 = &json_2["interactions"][1]["response"][0]["contents"];

assert_eq!("description 1", interaction_2_description.as_str().unwrap());
assert_eq!("description 2", interaction_2_description_2.as_str().unwrap());
assert_eq!(
&expected_response_contents,
interaction_2_response_contents_2
);
assert_eq!(
&expected_response_contents,
interaction_2_response_contents
);
}
2 changes: 1 addition & 1 deletion rust/pact_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ onig = { version = "6.4.0", default-features = false }
pact_matching = { version = "~1.2.7", path = "../pact_matching" }
pact_mock_server = { version = "~1.2.10" }
pact_models = { version = "~1.2.5" }
pact-plugin-driver = { version = "~0.7.1" }
pact-plugin-driver = { version = "~0.7.2" }
pact_verifier = { version = "~1.2.4", path = "../pact_verifier" }
panic-message = "0.3.0"
rand = "0.8.5"
Expand Down
15 changes: 12 additions & 3 deletions rust/pact_ffi/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,10 @@ pub extern fn pactffi_interaction_contents(
let message = interaction.as_v4_async_message_mut().unwrap();
if let Some(contents) = contents.first() {
message.contents.contents = contents.body.clone();
message.contents.metadata = contents.metadata.clone().unwrap_or_default();
let metadata = contents.metadata.as_ref()
.map(|md| md.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
message.contents.metadata = metadata;
if let Some(rules) = &contents.rules {
message.contents.matching_rules.add_rules("body", rules.clone());
}
Expand Down Expand Up @@ -264,7 +267,10 @@ fn setup_sync_message_contents(

if let Some(contents) = &contents.iter().find(|c| c.part_name == "request") {
message.request.contents = contents.body.clone();
message.request.metadata = contents.metadata.clone().unwrap_or_default();
let metadata = contents.metadata.as_ref()
.map(|md| md.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
message.request.metadata = metadata;
if let Some(rules) = &contents.rules {
message.request.matching_rules.add_rules("body", rules.clone());
}
Expand Down Expand Up @@ -293,9 +299,12 @@ fn setup_sync_message_contents(
if let Some(g) = &c.generators {
generators.add_generators(g.clone());
}
let metadata = c.metadata.as_ref()
.map(|md| md.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default();
message.response.push(MessageContents {
contents: c.body.clone(),
metadata: c.metadata.clone().unwrap_or_default(),
metadata,
matching_rules,
generators
});
Expand Down
2 changes: 1 addition & 1 deletion rust/pact_matching/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ multer = { version = "3.0.0", features = ["all"], optional = true }
nom = "7.1.3"
onig = { version = "6.4.0", default-features = false }
pact_models = { version = "~1.2.5", default-features = false }
pact-plugin-driver = { version = "~0.7.1", optional = true, default-features = false }
pact-plugin-driver = { version = "~0.7.2", optional = true, default-features = false }
rand = "0.8.5"
reqwest = { version = "0.12.3", default-features = false, features = ["rustls-tls-native-roots", "json"] }
semver = "1.0.22"
Expand Down
2 changes: 1 addition & 1 deletion rust/pact_verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ maplit = "1.0.2"
mime = "0.3.17"
pact_matching = { version = "~1.2.7", path = "../pact_matching", default-features = false }
pact_models = { version = "~1.2.5", default-features = false }
pact-plugin-driver = { version = "~0.7.1", optional = true, default-features = false }
pact-plugin-driver = { version = "~0.7.2", optional = true, default-features = false }
regex = "1.10.4"
reqwest = { version = "0.12.3", default-features = false, features = ["rustls-tls-native-roots", "blocking", "json"] }
serde = "1.0.197"
Expand Down

0 comments on commit 44d9dc1

Please sign in to comment.