Skip to content

Commit 7d69bf7

Browse files
committed
fix: ServerManager spawn_mock_server function now handles mock servers provided by plugins
1 parent eddf017 commit 7d69bf7

4 files changed

Lines changed: 139 additions & 16 deletions

File tree

pact_mock_server/src/builder.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::net::Ipv4Addr;
44
#[allow(unused_imports)] use anyhow::{anyhow, Context};
5+
use itertools::Either;
56
use pact_models::pact::Pact;
67
use pact_models::PactSpecification;
78
use pact_models::v4::pact::V4Pact;
@@ -20,8 +21,10 @@ use crate::server_manager::ServerManager;
2021

2122
/// Builder for constructing mock servers
2223
pub struct MockServerBuilder {
23-
config: MockServerConfig,
24-
pact: V4Pact
24+
/// Mock server configuration
25+
pub config: MockServerConfig,
26+
/// Pact to use for the mock server interactions
27+
pub pact: V4Pact
2528
}
2629

2730
impl MockServerBuilder {
@@ -161,6 +164,35 @@ impl MockServerBuilder {
161164

162165
/// Set the transport to use. The default transports are 'http' and 'https'. Additional transports
163166
/// can be provided by plugins.
167+
///
168+
/// Note that plugin mock servers can only be supported by assigning this builder to a
169+
/// `ServerManager`, which will interact with the plugin driver to start the plugin mock server
170+
/// correctly.
171+
/// For instance:
172+
/// ```no_run
173+
/// # use pact_models::pact::Pact;
174+
/// # use pact_models::v4::pact::V4Pact;
175+
/// # use pact_mock_server::builder::MockServerBuilder;
176+
/// # use pact_mock_server::server_manager::ServerManager;
177+
/// # #[tokio::main]
178+
/// # async fn main() -> anyhow::Result<()> {
179+
/// # let some_grpc_pact = V4Pact::default().boxed();
180+
/// // This won't work
181+
/// let mock_server_details = MockServerBuilder::new()
182+
/// .with_pact(some_grpc_pact.boxed())
183+
/// .with_transport("grpc")?
184+
/// .start()
185+
/// .await?;
186+
///
187+
/// // Instead, assign it to a server manager
188+
/// let mut manager = ServerManager::new();
189+
/// let builder = MockServerBuilder::new()
190+
/// .with_pact(some_grpc_pact.boxed())
191+
/// .with_transport("grpc")?;
192+
/// let mock_server_details = builder.attach_to_manager(&mut manager)?;
193+
/// # Ok::<(), anyhow::Error>(())
194+
/// # }
195+
/// ```
164196
#[cfg(feature = "plugins")]
165197
pub fn with_transport<S: Into<String>>(mut self, transport: S) -> anyhow::Result<Self> {
166198
let transport = transport.into();
@@ -194,15 +226,24 @@ impl MockServerBuilder {
194226
pub fn attach_to_global_manager(self) -> anyhow::Result<MockServer> {
195227
let mut guard = MANAGER.lock().unwrap();
196228
let manager = guard.get_or_insert_with(|| ServerManager::new());
197-
manager.spawn_mock_server(self)
229+
manager.spawn_http_mock_server(self)
198230
}
199231

200232
/// Starts the mockserver, consuming this builder and registers it with the server manager.
201233
/// The mock server tasks will be spawned on the server manager's runtime.
202-
/// Returns the mock server instance.
203-
pub fn attach_to_manager(self, manager: &mut ServerManager) -> anyhow::Result<MockServer> {
234+
/// Returns the mock server instance, unless the mock server was provided by a plugin, in which
235+
/// case it just returns the mock server ID and port.
236+
pub fn attach_to_manager(
237+
self, manager:
238+
&mut ServerManager
239+
) -> anyhow::Result<Either<MockServer, (String, u16)>> {
204240
manager.spawn_mock_server(self)
205241
}
242+
243+
/// Returns a clone of the config within this builder
244+
pub fn config(&self) -> MockServerConfig {
245+
self.config.clone()
246+
}
206247
}
207248

208249
#[cfg(test)]

pact_mock_server/src/server_manager.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::collections::BTreeMap;
66
use std::ffi::CString;
77
#[cfg(feature = "plugins")] use std::future::Future;
8-
use std::net::SocketAddr;
8+
use std::net::{Ipv6Addr, SocketAddr};
99
#[cfg(feature = "plugins")] use std::net::ToSocketAddrs;
1010

1111
use anyhow::anyhow;
@@ -75,7 +75,77 @@ impl ServerManager {
7575

7676
/// Consumes the mock server builder, and then spawns the resulting mock server on the server
7777
/// manager's runtime. Note that this function will block the current calling thread.
78-
pub fn spawn_mock_server(&mut self, builder: MockServerBuilder) -> anyhow::Result<MockServer> {
78+
///
79+
/// The returned value depends on whether the mock server is directly managed by this manager
80+
/// or provided by a plugin. For plugin provided mock servers, the function will return the
81+
/// mock server ID and port.
82+
pub fn spawn_mock_server(
83+
&mut self,
84+
builder: MockServerBuilder
85+
) -> anyhow::Result<Either<MockServer, (String, u16)>> {
86+
#[cfg(feature = "plugins")]
87+
{
88+
if let Some(transport) = builder.config.transport_entry.clone() {
89+
if transport.provider_type == CatalogueEntryProviderType::PLUGIN {
90+
let config = builder.config;
91+
let mut pact = builder.pact;
92+
93+
for interaction in pact.interactions.iter_mut() {
94+
if let None = interaction.transport() {
95+
interaction.set_transport(transport.key.split("/").last().map(|i| i.to_string()));
96+
}
97+
}
98+
99+
let address = if config.address.is_empty() {
100+
SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0)
101+
} else {
102+
config.address.parse()?
103+
};
104+
105+
let mock_server_config = pact_plugin_driver::mock_server::MockServerConfig {
106+
output_path: None,
107+
host_interface: Some(address.ip().to_string()),
108+
port: address.port() as u32,
109+
tls: false
110+
};
111+
let test_context = hashmap! {};
112+
let result = self.runtime.block_on(
113+
pact_plugin_driver::plugin_manager::start_mock_server_v2(&transport, pact.boxed(),
114+
mock_server_config, test_context)
115+
)?;
116+
let port = result.port as u16;
117+
let id = result.key.clone();
118+
self.mock_servers.insert(
119+
id.clone(),
120+
ServerEntry {
121+
mock_server: Either::Right(PluginMockServer {
122+
mock_server_details: result,
123+
catalogue_entry: transport,
124+
pact
125+
}),
126+
port,
127+
resources: vec![]
128+
}
129+
);
130+
131+
Ok(Either::Right((id, port)))
132+
} else {
133+
self.spawn_http_mock_server(builder).map(Either::Left)
134+
}
135+
} else {
136+
self.spawn_http_mock_server(builder).map(Either::Left)
137+
}
138+
}
139+
140+
#[cfg(not(feature = "plugins"))]
141+
self.spawn_http_mock_server(builder).map(Either::Left)
142+
}
143+
144+
/// Consumes the mock server builder, and then spawns the resulting HTTP mock server on the server
145+
/// manager's runtime. Note that this function will block the current calling thread.
146+
///
147+
/// This function does not handle mock servers provided by plugins.
148+
pub fn spawn_http_mock_server(&mut self, builder: MockServerBuilder) -> anyhow::Result<MockServer> {
79149
#[allow(unused_assignments)]
80150
let mut mock_server = MockServer::default();
81151

pact_mock_server/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ fn match_request_with_header_with_multiple_values() -> anyhow::Result<()> {
215215
.with_v4_pact(pact)
216216
.with_id(id.clone())
217217
.bind_to("127.0.0.1:0");
218-
let result = manager.spawn_mock_server(mock_server_builder);
218+
let result = manager.spawn_http_mock_server(mock_server_builder);
219219
let mock_server = result.unwrap();
220220
let port = mock_server.port();
221221

pact_mock_server_cli/src/server.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,30 @@ fn start_provider(context: &mut WebmachineContext) -> Result<bool, u16> {
154154
}
155155

156156
match result {
157-
Ok(mock_server) => {
158-
debug!("Mock server started on port {}", mock_server.port());
159-
let mock_server_json = json!({
160-
"id" : json!(mock_server_id),
161-
"port" : json!(mock_server.port() as i64),
162-
});
157+
Ok(mock_server_details) => {
158+
let mock_server_json = match mock_server_details {
159+
Either::Left(mock_server) => {
160+
debug!("Mock server started on port {}", mock_server.port());
161+
json!({
162+
"id" : mock_server_id,
163+
"port" : mock_server.port()
164+
})
165+
}
166+
Either::Right((id, port)) => {
167+
debug!("Plugin Mock server started on port {}", port);
168+
json!({
169+
"id" : id,
170+
"port" : port,
171+
})
172+
}
173+
};
174+
163175
let json_response = json!({ "mockServer" : mock_server_json });
164176
context.response.body = Some(Bytes::from(json_response.to_string()));
165177
context.response.add_header("Location",
166178
vec![HeaderValue::basic(format!("/mockserver/{}", mock_server_id).as_str())]);
167179
Ok(true)
168-
},
180+
}
169181
Err(msg) => {
170182
context.response.body = Some(json_error(format!("Failed to start mock server - {}", msg)));
171183
Err(422)
@@ -193,7 +205,7 @@ fn start_https_server(
193205
config: MockServerConfig,
194206
port: u16,
195207
id: &String
196-
) -> anyhow::Result<MockServer> {
208+
) -> anyhow::Result<Either<MockServer, (String, u16)>> {
197209
debug!("Starting TLS mock server with id {}", id);
198210
let mut server_manager = SERVER_MANAGER.lock().unwrap();
199211
trace!("Unlocked server manager");

0 commit comments

Comments
 (0)