Skip to content

Commit 9f10078

Browse files
authored
Merge pull request #530 from pact-foundation/fix-ffi-tls-mockserver
fix(pact-ffi): pactffi_create_mock_server_for_transport is unable to start a TLS mock server
2 parents 5363f22 + bdfe6f6 commit 9f10078

4 files changed

Lines changed: 69 additions & 7 deletions

File tree

rust/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/pact_ffi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pretty_assertions = "1.4.1"
5959
quickcheck = "1.0.3"
6060
reqwest = { version = "0.13.1", default-features = false, features = ["rustls", "blocking", "json", "multipart"] }
6161
rstest = "0.26.1"
62+
rustls = { version = "0.23", features = ["ring"] }
6263
test-log = "0.2.19"
6364
tempfile = "3.24.0"
6465

rust/pact_ffi/src/mock_server/mod.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ ffi_fn! {
120120
/// * `pact` - Handle to a Pact model created with created with `pactffi_new_pact`.
121121
/// * `addr` - Address to bind to (i.e. `127.0.0.1` or `[::1]`). Must be a valid UTF-8 NULL-terminated string, or NULL or empty, in which case the loopback adapter is used.
122122
/// * `port` - Port number to bind to. A value of zero will result in the operating system allocating an available port.
123-
/// * `transport` - The transport to use (i.e. http, https, grpc). Must be a valid UTF-8 NULL-terminated string, or NULL or empty, in which case http will be used.
123+
/// * `transport` - The transport to use (i.e. http, https, grpc). Must be a valid UTF-8 NULL-terminated string, or NULL or empty, in which case http will be used. Passing `https` will start a TLS-enabled server using a self-signed certificate; use `pactffi_get_tls_ca_certificate` to obtain the CA cert for client configuration.
124124
/// * `transport_config` - (OPTIONAL) Configuration for the transport as a valid JSON string. Set to NULL or empty if not required.
125125
///
126126
/// The port of the mock server is returned.
@@ -181,12 +181,22 @@ ffi_fn! {
181181
.with_id(Uuid::new_v4().to_string())
182182
.bind_to(socket_addr.to_string());
183183

184-
let builder = match builder.with_transport(transport.as_str()) {
185-
Ok(builder) => builder,
186-
Err(err) => {
187-
error!("Failed to configure mock server transport '{}' - {}", transport, err);
188-
return -3;
189-
}
184+
let builder = match transport.as_str() {
185+
"https" => match builder.with_self_signed_tls() {
186+
Ok(builder) => builder,
187+
Err(err) => {
188+
error!("Failed to configure TLS for HTTPS mock server - {}", err);
189+
return -3;
190+
}
191+
},
192+
"http" => builder,
193+
_ => match builder.with_transport(transport.as_str()) {
194+
Ok(builder) => builder,
195+
Err(err) => {
196+
error!("Failed to configure mock server transport '{}' - {}", transport, err);
197+
return -3;
198+
}
199+
},
190200
};
191201

192202
match attach_to_manager(builder) {

rust/pact_ffi/tests/tests.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2558,3 +2558,53 @@ fn mime_multipart_with_json_and_image() {
25582558

25592559
expect!(mismatches).to(be_equal_to("[]"));
25602560
}
2561+
2562+
#[test]
2563+
#[allow(deprecated)]
2564+
fn https_mock_server_starts_and_responds_over_tls() {
2565+
// Both ring (via pact_mock_server) and aws-lc-rs (via reqwest 0.13) are compiled into the test
2566+
// binary. Rustls panics if neither has been set as the process default, so install ring first.
2567+
rustls::crypto::ring::default_provider().install_default().ok();
2568+
2569+
let consumer_name = CString::new("https-consumer").unwrap();
2570+
let provider_name = CString::new("https-provider").unwrap();
2571+
let pact_handle = pactffi_new_pact(consumer_name.as_ptr(), provider_name.as_ptr());
2572+
let description = CString::new("an HTTPS request").unwrap();
2573+
let interaction = pactffi_new_interaction(pact_handle, description.as_ptr());
2574+
let method = CString::new("GET").unwrap();
2575+
let path = CString::new("/").unwrap();
2576+
2577+
pactffi_upon_receiving(interaction, description.as_ptr());
2578+
pactffi_with_request(interaction, method.as_ptr(), path.as_ptr());
2579+
pactffi_response_status(interaction, 200);
2580+
2581+
let address = CString::new("127.0.0.1").unwrap();
2582+
let transport = CString::new("https").unwrap();
2583+
let port = pactffi_create_mock_server_for_transport(
2584+
pact_handle, address.as_ptr(), 0, transport.as_ptr(), null()
2585+
);
2586+
expect!(port).to(be_greater_than(0));
2587+
2588+
let client = reqwest::blocking::Client::builder()
2589+
.danger_accept_invalid_certs(true)
2590+
.build()
2591+
.unwrap();
2592+
let result = client.get(format!("https://127.0.0.1:{}/", port)).send();
2593+
2594+
thread::sleep(Duration::from_millis(100));
2595+
2596+
let matched = pactffi_mock_server_matched(port);
2597+
let mismatches = unsafe {
2598+
CStr::from_ptr(pactffi_mock_server_mismatches(port)).to_string_lossy().into_owned()
2599+
};
2600+
2601+
pactffi_cleanup_mock_server(port);
2602+
pactffi_free_pact_handle(pact_handle);
2603+
2604+
match result {
2605+
Ok(res) => expect!(res.status()).to(be_eq(200)),
2606+
Err(err) => panic!("HTTPS request to mock server failed: {}", err),
2607+
};
2608+
expect!(matched).to(be_true());
2609+
expect!(mismatches).to(be_equal_to("[]"));
2610+
}

0 commit comments

Comments
 (0)