Skip to content

Commit

Permalink
Merge branch 'master' into feat/v2-mock-server
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Jul 10, 2024
2 parents 41ed972 + 2e2580f commit 3c0e897
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 33 deletions.
12 changes: 12 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/pact_consumer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pact_models = { version = "~1.2.1", default-features = false }
pact-plugin-driver = { version = "~0.6.1", optional = true, default-features = false }
regex = "1.10.4"
serde_json = "1.0.115"
termsize = "0.1.8"
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
tracing-core = "0.1.32"
Expand Down
37 changes: 25 additions & 12 deletions rust/pact_consumer/src/mock_server/http_mock_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ use std::{env, thread};
use std::fmt::Write;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::anyhow;

use pact_matching::metrics::{MetricEvent, send_metrics};
use anyhow::anyhow;
use itertools::Itertools;
use pact_mock_server::builder::MockServerBuilder;
use pact_mock_server::matching::MatchResult;
use pact_mock_server::mock_server;
use pact_mock_server::mock_server::{MockServerConfig, MockServerMetrics};
use pact_models::pact::Pact;
#[cfg(feature = "plugins")] use pact_models::plugins::PluginData;
#[cfg(feature = "plugins")] use pact_plugin_driver::plugin_manager::{drop_plugin_access, increment_plugin_access};
#[cfg(feature = "plugins")] use pact_plugin_driver::plugin_models::{PluginDependency, PluginDependencyType};
use tokio::runtime::Runtime;
#[allow(unused_imports)] use tracing::{debug, trace, warn};
use url::Url;

use pact_matching::metrics::{MetricEvent, send_metrics};
use pact_models::pact::Pact;
#[cfg(feature = "plugins")] use pact_models::plugins::PluginData;
use pact_models::v4::http_parts::HttpRequest;

use crate::mock_server::ValidatingMockServer;
use crate::util::panic_or_print_error;

Expand Down Expand Up @@ -222,36 +225,46 @@ impl ValidatingHttpMockServer {
Ok(())
} else {
// Failure. Format our errors.
let mut msg = format!("mock server {} failed verification:\n", self.description,);
let size = termsize::get().map(|sz| sz.cols).unwrap_or(120) - 2;
let pad = "-".repeat(size as usize);
let mut msg = format!(" {} \nMock server {} failed verification:\n", pad, self.description);
for mismatch in mismatches {
match mismatch {
MatchResult::RequestMatch(..) => {
unreachable!("list of mismatches contains a match");
warn!("list of mismatches contains a match");
}
MatchResult::RequestMismatch(request, _, mismatches) => {
let _ = writeln!(&mut msg, "- request {}:", request);
let _ = writeln!(&mut msg, "\n - request {}:\n", request);
for m in mismatches {
let _ = writeln!(&mut msg, " - {}", m.description());
let _ = writeln!(&mut msg, " - {}", m.description());
}
}
MatchResult::RequestNotFound(request) => {
let _ = writeln!(&mut msg, "- received unexpected request:");
let _ = writeln!(&mut msg, "{:#?}", request);
let _ = writeln!(&mut msg, "\n - received unexpected request {}:\n", short_description(&request));
let debug_str = format!("{:#?}", request);
let _ = writeln!(&mut msg, "{}", debug_str.lines().map(|ln| format!(" {}", ln)).join("\n"));
}
MatchResult::MissingRequest(request) => {
let _ = writeln!(
&mut msg,
"- request {} expected, but never occurred", request,
"\n - request {} expected, but never occurred:\n", short_description(&request),
);
let _ = writeln!(&mut msg, "{:#?}", request);
let debug_str = format!("{:#?}", request);
let _ = writeln!(&mut msg, "{}", debug_str.lines().map(|ln| format!(" {}", ln)).join("\n"));
}
}
}
let _ = writeln!(&mut msg, " {} ", pad);
Err(anyhow!(msg))
}
}
}

// TODO: Implement this in the HTTP request struct
fn short_description(request: &HttpRequest) -> String {
format!("{} {}", request.method.to_uppercase(), request.path)
}

impl ValidatingMockServer for ValidatingHttpMockServer {
fn url(&self) -> Url {
self.url.clone()
Expand Down
7 changes: 3 additions & 4 deletions rust/pact_consumer/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
//! Most of these are `pub(crate)`, which makes them available to the rest of
//! the crate, but prevents them from winding up in our public API.
use std::{io, thread};
use std::io::Write;
use std::thread;

/// Internal helper method for `strip_null_fields`.
fn strip_null_fields_mut(json: &mut serde_json::Value) {
Expand Down Expand Up @@ -93,9 +92,9 @@ pub(crate) fn panic_or_print_error(msg: &anyhow::Error) {
if thread::panicking() {
// The current thread is panicking, so don't try to panic again, because
// double panics don't print useful explanations of why the test failed.
// Instead, just print to `stderr`. Ignore any errors, because there's
// Instead, just print to `stdout`. Ignore any errors, because there's
// not much we can do if we can't panic and we can't write to `stderr`.
let _ = writeln!(io::stderr(), "{}", msg);
let _ = println!("{}", msg);
} else {
panic!("{}", msg);
}
Expand Down
82 changes: 79 additions & 3 deletions rust/pact_ffi/src/verifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,9 @@ ffi_fn! {
/// If a username and password is given, then basic authentication will be used when fetching
/// the pact file. If a token is provided, then bearer token authentication will be used.
///
/// This function will return zero unless any of the consumer version selectors are not valid
/// JSON, in which case, it will return -1.
///
/// # Safety
///
/// All string fields must contain valid UTF-8. Invalid UTF-8
Expand All @@ -567,7 +570,7 @@ ffi_fn! {
consumer_version_selectors_len: c_ushort,
consumer_version_tags: *const *const c_char,
consumer_version_tags_len: c_ushort
) {
) -> c_int {
let handle = as_mut!(handle);
let url = safe_str!(url);
let provider_branch: Option<String> = if provider_branch.is_null() {
Expand Down Expand Up @@ -604,15 +607,33 @@ ffi_fn! {
let consumer_version_tags_vector = get_vector(consumer_version_tags, consumer_version_tags_len);

let selectors = if consumer_version_selectors_vector.len() > 0 {
json_to_selectors(consumer_version_selectors_vector.iter().map(|s| &**s).collect())
let mut selectors = vec![];
let mut errors = false;
for s in consumer_version_selectors_vector {
match serde_json::from_str(s.as_str()) {
Ok(cvs) => selectors.push(cvs),
Err(err) => {
error!("Failed to parse consumer version selector '{}' as JSON: {}", s, err);
errors = true;
}
}
}

if errors {
return Ok(-1);
}

json_to_selectors(selectors)
} else if consumer_version_tags_vector.len() > 0 {
consumer_tags_to_selectors(consumer_version_tags_vector.iter().map(|s| &**s).collect())
} else {
vec![]
};

handle.add_pact_broker_source(url, enable_pending > 0, wip, provider_tags_vector, provider_branch, selectors, &auth);
}

0
} { -2 }
}

ffi_fn! {
Expand Down Expand Up @@ -902,6 +923,7 @@ ffi_fn! {
#[cfg(test)]
mod tests {
use std::ffi::CString;
use std::ptr::null;

use expectest::prelude::*;
use libc::c_char;
Expand Down Expand Up @@ -956,4 +978,58 @@ We are tracking events anonymously to gather important usage statistics like Pac
1) Verifying a pact between test_consumer and test_provider Given test state - test interaction - error sending request for url (http://localhost/): error trying to connect: tcp connect error: Connection refused (os error 111)\n\
\n\nThere were 1 pact failures\n\n"));
}

#[test]
fn pactffi_verifier_broker_source_with_selectors_test() {
let mut handle = VerifierHandle::new_for_application("test", "0.0.0");
let url = CString::new("http://127.0.0.1:8080").unwrap();
let provider_branch = CString::new("main").unwrap();
let cvs_1 = CString::new(r#"{"mainBranch":true}"#).unwrap();
let cvs_2 = CString::new(r#"{"deployedOrReleased":true}"#).unwrap();
let consumer_version_selectors = [ cvs_1.as_ptr(), cvs_2.as_ptr() ];
let result = super::pactffi_verifier_broker_source_with_selectors(
&mut handle,
url.as_ptr(),
null(),
null(),
null(),
0,
null(),
null(),
0,
provider_branch.as_ptr(),
consumer_version_selectors.as_ptr(),
2,
null(),
0
);
expect!(result).to(be_equal_to(0));
}

#[test_log::test]
fn pactffi_verifier_broker_source_with_selectors_error_test() {
let mut handle = VerifierHandle::new_for_application("test", "0.0.0");
let url = CString::new("http://127.0.0.1:8080").unwrap();
let provider_branch = CString::new("main").unwrap();
let cvs_1 = CString::new(r#"{"mainBranch":true"#).unwrap();
let cvs_2 = CString::new(r#"{"deployedOrReleased:true}"#).unwrap();
let consumer_version_selectors = [ cvs_1.as_ptr(), cvs_2.as_ptr() ];
let result = super::pactffi_verifier_broker_source_with_selectors(
&mut handle,
url.as_ptr(),
null(),
null(),
null(),
0,
null(),
null(),
0,
provider_branch.as_ptr(),
consumer_version_selectors.as_ptr(),
2,
null(),
0
);
expect!(result).to(be_equal_to(-1));
}
}
6 changes: 5 additions & 1 deletion rust/pact_ffi/src/verifier/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::Arc;

use clap::{AppSettings, ArgMatches, ErrorKind};
use maplit::hashmap;
use serde_json::Value;
use pact_models::http_utils::HttpAuth;
use pact_models::PactSpecification;
use tracing::{debug, error, warn};
Expand Down Expand Up @@ -60,7 +61,10 @@ fn pact_source(matches: &ArgMatches) -> Vec<PactSource> {
let provider_branch = matches.value_of("provider-branch").map(|v| v.to_string());
let selectors = if matches.is_present("consumer-version-selectors") {
matches.values_of("consumer-version-selectors")
.map_or_else(Vec::new, |s| json_to_selectors(s.collect::<Vec<_>>()))
.map_or_else(Vec::new, |s| json_to_selectors(s
.map(|cvs| serde_json::from_str::<Value>(cvs))
.flatten()
.collect::<Vec<_>>()))
} else if matches.is_present("consumer-version-tags") {
matches.values_of("consumer-version-tags")
.map_or_else(Vec::new, |tags| consumer_tags_to_selectors(tags.collect::<Vec<_>>()))
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 @@ -26,7 +26,7 @@ ansi_term = "0.12.1"
anyhow = "1.0.82"
base64 = "0.22.0"
bytes = { version = "1.6.0", features = ["serde"] }
chrono = { version = "0.4.38", features = ["std", "clock"], default_features = false, optional = true }
chrono = { version = "0.4.38", features = ["std", "clock"], default-features = false, optional = true }
difference = "2.0.0"
futures = "0.3.30"
hex = "0.4.3"
Expand Down
1 change: 1 addition & 0 deletions rust/pact_matching/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,7 @@ pub async fn match_sync_message_response<'a>(

/// Generates the request by applying any defined generators
// TODO: Need to pass in any plugin data
#[instrument(level = "trace")]
pub async fn generate_request(request: &HttpRequest, mode: &GeneratorTestMode, context: &HashMap<&str, Value>) -> HttpRequest {
trace!(?request, ?mode, ?context, "generate_request");
let mut request = request.clone();
Expand Down
3 changes: 2 additions & 1 deletion rust/pact_models/src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use rand::prelude::*;
#[cfg(target_family = "wasm")] use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tracing::{debug, trace, warn};
use tracing::{debug, instrument, trace, warn};
use uuid::Uuid;

use crate::bodies::OptionalBody;
Expand Down Expand Up @@ -768,6 +768,7 @@ macro_rules! generators {
}};
}

#[instrument(level = "trace")]
pub fn generate_value_from_context(expression: &str, context: &HashMap<&str, Value>, data_type: &Option<DataType>) -> anyhow::Result<DataValue> {
let result = if contains_expressions(expression) {
parse_expression(expression, &MapValueResolver { context: context.clone() })
Expand Down
5 changes: 5 additions & 0 deletions rust/pact_models/src/v4/http_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ impl HttpRequest {
None => None
}
}

/// Brief one-line description of the request
pub fn short_description(&self) -> String {
format!("{} {}", self.method.to_uppercase(), self.path)
}
}

impl PartialEq for HttpRequest {
Expand Down
13 changes: 11 additions & 2 deletions rust/pact_verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,13 +428,22 @@ async fn verify_interaction<'a, F: RequestFilterExecutor, S: ProviderStateExecut
let context = execute_provider_states(interaction, provider_state_executor, &client, true)
.await
.map_err(|e| (e, vec![], start.elapsed()))?;
let provider_states_context = context
let mut provider_states_context = hashmap!{};
for provider_state in interaction.provider_states() {
for (k, v) in provider_state.params {
provider_states_context.insert(k, v);
}
}
for (k, v) in context {
provider_states_context.insert(k, v);
}
let provider_states_context = provider_states_context
.iter()
.map(|(k, v)| (k.as_str(), v.clone()))
.collect();

info!("Running provider verification for '{}'", interaction.description());
trace!("Interaction to verify: {:?}", interaction);
trace!(?provider_states_context, "Interaction to verify: {:?}", interaction);

#[allow(unused_assignments)] let mut result = Ok((None, vec![]));
#[cfg(feature = "plugins")]
Expand Down
8 changes: 5 additions & 3 deletions rust/pact_verifier/src/selectors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Module to deal with consumer version selectors
use serde_json::{from_value, Value};

use crate::ConsumerVersionSelector;

/// Parses a vector of JSON strings into a vector of consumer version selectors
pub fn json_to_selectors(tags: Vec<&str>) -> Vec<ConsumerVersionSelector> {
tags.iter().map(|t| serde_json::from_str(t))
/// Parses a vector of JSON into a vector of consumer version selectors
pub fn json_to_selectors(json: Vec<Value>) -> Vec<ConsumerVersionSelector> {
json.iter().map(|t| from_value(t.clone()))
.flatten()
.collect()
}
Expand Down
Loading

0 comments on commit 3c0e897

Please sign in to comment.