Skip to content

Commit 0e58f2d

Browse files
committed
feat: Add functions to server manager to get mock server results
1 parent 7838ac9 commit 0e58f2d

1 file changed

Lines changed: 201 additions & 27 deletions

File tree

pact_mock_server/src/server_manager.rs

Lines changed: 201 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ use std::net::{Ipv6Addr, SocketAddr};
99
#[cfg(feature = "plugins")] use std::net::ToSocketAddrs;
1010

1111
use anyhow::anyhow;
12-
use itertools::Either;
12+
use itertools::{Either, Itertools};
1313
#[cfg(feature = "plugins")] use maplit::hashmap;
1414
use pact_models::pact::Pact;
1515
#[cfg(feature = "plugins")] use pact_models::prelude::v4::V4Pact;
1616
#[cfg(feature = "plugins")] use pact_plugin_driver::catalogue_manager::{CatalogueEntry, CatalogueEntryProviderType};
1717
#[cfg(feature = "plugins")] use pact_plugin_driver::mock_server::MockServerDetails;
18+
use pact_plugin_driver::plugin_manager::get_mock_server_results;
1819
#[cfg(feature = "tls")] use rustls::ServerConfig;
1920
#[cfg(not(feature = "plugins"))] use serde::{Deserialize, Serialize};
21+
use serde_json::{json, Value};
2022
use tracing::{debug, error, trace};
2123
#[cfg(feature = "plugins")] use url::Url;
2224
use crate::builder::MockServerBuilder;
23-
2425
use crate::mock_server::{MockServer, MockServerConfig};
2526

2627
/// Mock server that has been provided by a plugin
@@ -521,6 +522,121 @@ impl ServerManager {
521522
false
522523
}
523524
}
525+
526+
fn mock_server_entry_by_port(&self, port: u16) -> Option<&ServerEntry> {
527+
self.mock_servers
528+
.iter()
529+
.find_map(|(_, entry)| if entry.port == port {
530+
Some(entry)
531+
} else {
532+
None
533+
})
534+
}
535+
536+
fn mock_server_entry_matched(&self, entry: Option<&ServerEntry>) -> Option<bool> {
537+
match entry {
538+
Some(entry) => {
539+
#[cfg(feature = "plugins")]
540+
match &entry.mock_server {
541+
Either::Left(mock_server) => Some(mock_server.all_matched()),
542+
Either::Right(plugin_mock_server) => {
543+
match self.exec_async(get_mock_server_results(&plugin_mock_server.mock_server_details)) {
544+
Ok(results) => Some(results.is_empty()),
545+
Err(err) => {
546+
error!("Request to plugin to get matching results failed - {}", err);
547+
Some(false)
548+
}
549+
}
550+
}
551+
}
552+
553+
#[cfg(not(feature = "plugins"))]
554+
None
555+
}
556+
None => None
557+
}
558+
}
559+
560+
fn mock_server_entry_mismatches(&self, entry: Option<&ServerEntry>) -> anyhow::Result<Option<Vec<Value>>> {
561+
match entry {
562+
Some(entry) => {
563+
match &entry.mock_server {
564+
Either::Left(mock_server) => Ok(Some(mock_server.mismatches()
565+
.iter()
566+
.map(|mismatches| mismatches.to_json())
567+
.collect())),
568+
Either::Right(plugin_mock_server) => {
569+
#[cfg(feature = "plugins")]
570+
match self.exec_async(get_mock_server_results(&plugin_mock_server.mock_server_details)) {
571+
Ok(results) => Ok(Some(results
572+
.iter()
573+
.map(|results| {
574+
json!({
575+
"path": results.path,
576+
"error": results.error,
577+
"mismatches": results.mismatches.iter().map(|mismatch| {
578+
json!({
579+
"expected": mismatch.expected,
580+
"actual": mismatch.actual,
581+
"mismatch": mismatch.mismatch,
582+
"path": mismatch.path,
583+
"diff": mismatch.diff.clone().unwrap_or_default()
584+
})
585+
})
586+
.collect_vec()
587+
})
588+
})
589+
.collect())),
590+
Err(err) => {
591+
error!(port = entry.port, "Request to plugin to get mock server matching results failed - {}", err);
592+
Err(anyhow!("Request to plugin to get mock server (port={}) matching results failed - {}", entry.port, err))
593+
}
594+
}
595+
596+
#[cfg(not(feature = "plugins"))]
597+
Err(anyhow!("plugins feature is not enabled"))
598+
}
599+
}
600+
}
601+
None => Ok(None)
602+
}
603+
}
604+
605+
/// Determines if the mock server running with the given ID has matched all its requests
606+
/// correctly. If there is no mock server running with that ID, it will return `None`.
607+
/// In the case the mock server has not received any requests, it will return `Some(true)`
608+
/// as this is the default state.
609+
pub fn mock_server_matched(&self, id: &str) -> Option<bool> {
610+
let entry = self.mock_servers.get(id);
611+
self.mock_server_entry_matched(entry)
612+
}
613+
614+
/// Determines if the mock server running on the given port has matched all its requests
615+
/// correctly. If there is no mock server running on that port, it will return `None`.
616+
/// In the case the mock server has not received any requests, it will return `Some(true)`
617+
/// as this is the default state.
618+
pub fn mock_server_matched_by_port(&self, port: u16) -> Option<bool> {
619+
let entry = self.mock_server_entry_by_port(port);
620+
self.mock_server_entry_matched(entry)
621+
}
622+
623+
/// Returns all the mismatches from the mock server with the given ID. If there is no
624+
/// mock server with that ID, it will return `None`. The mismatch values are returned
625+
/// as JSON. This is to support mock servers provided by plugins, which can have a different
626+
/// format.
627+
pub fn mock_server_mismatches(&self, id: &str) -> anyhow::Result<Option<Vec<Value>>> {
628+
let entry = self.mock_servers.get(id);
629+
self.mock_server_entry_mismatches(entry)
630+
}
631+
632+
/// Returns all the mismatches from the mock server running on the given port. If there is no
633+
/// mock server running on that port, it will return `None`. The mismatch values are returned
634+
/// as JSON. This is to support mock servers provided by plugins, which can have a different
635+
/// format.
636+
pub fn mock_server_mismatches_by_port(&self, port: u16) -> anyhow::Result<Option<Vec<Value>>> {
637+
let entry = self.mock_server_entry_by_port(port);
638+
self.mock_server_entry_mismatches(entry)
639+
}
524640
}
525641

526642
#[cfg(test)]
@@ -529,41 +645,99 @@ mod tests {
529645
use std::net::TcpStream;
530646

531647
use env_logger;
648+
use expectest::prelude::*;
649+
use hyper::header::ACCEPT;
532650
use pact_models::sync_pact::RequestResponsePact;
533651

534652
use super::*;
535653

536654
#[test]
537-
#[cfg(not(target_os = "windows"))]
538-
fn manager_should_start_and_shutdown_mock_server() {
539-
let _ = env_logger::builder().is_test(true).try_init();
540-
let mut manager = ServerManager::new();
541-
#[allow(deprecated)]
542-
let start_result = manager.start_mock_server("foobar".into(),
543-
RequestResponsePact::default().boxed(),
544-
0, MockServerConfig::default());
655+
#[cfg(not(target_os = "windows"))]
656+
fn manager_should_start_and_shutdown_mock_server() {
657+
let _ = env_logger::builder().is_test(true).try_init();
658+
let mut manager = ServerManager::new();
659+
#[allow(deprecated)]
660+
let start_result = manager.start_mock_server("foobar".into(),
661+
RequestResponsePact::default().boxed(),
662+
0, MockServerConfig::default());
545663

546-
assert!(start_result.is_ok());
547-
let server_port = start_result.unwrap();
664+
assert!(start_result.is_ok());
665+
let server_port = start_result.unwrap();
548666

549-
// Server should be up
550-
assert!(TcpStream::connect(("127.0.0.1", server_port)).is_ok());
667+
// Server should be up
668+
assert!(TcpStream::connect(("127.0.0.1", server_port)).is_ok());
551669

552-
// Should be able to read matches without blocking
553-
let matches =
554-
manager.find_mock_server_by_port_mut(server_port, &|mock_server| mock_server.matches());
555-
assert_eq!(matches, Some(vec![]));
670+
// Should be able to read matches without blocking
671+
let matches =
672+
manager.find_mock_server_by_port_mut(server_port, &|mock_server| mock_server.matches());
673+
assert_eq!(matches, Some(vec![]));
556674

557-
let stopped = manager.shutdown_mock_server_by_port(server_port);
558-
assert!(stopped);
675+
let stopped = manager.shutdown_mock_server_by_port(server_port);
676+
assert!(stopped);
559677

560-
// The tokio runtime is now out of tasks
561-
drop(manager);
678+
// The tokio runtime is now out of tasks
679+
drop(manager);
562680

563-
let millis = time::Duration::from_millis(100);
564-
thread::sleep(millis);
681+
let millis = time::Duration::from_millis(100);
682+
thread::sleep(millis);
565683

566-
// Server should be down
567-
assert!(TcpStream::connect(("127.0.0.1", server_port)).is_err());
568-
}
684+
// Server should be down
685+
assert!(TcpStream::connect(("127.0.0.1", server_port)).is_err());
686+
}
687+
688+
#[test_log::test]
689+
fn manager_can_return_mock_server_status() {
690+
let pact = V4Pact::default();
691+
let mut manager = ServerManager::new();
692+
let mock_server = MockServerBuilder::new()
693+
.bind_to("127.0.0.1:0")
694+
.with_v4_pact(pact)
695+
.attach_to_manager(&mut manager)
696+
.unwrap()
697+
.unwrap_left();
698+
699+
expect!(manager.mock_server_matched("some value")).to(be_none());
700+
expect!(manager.mock_server_matched(mock_server.id.as_str())).to(be_some().value(true));
701+
expect!(manager.mock_server_matched_by_port(666)).to(be_none());
702+
expect!(manager.mock_server_matched_by_port(mock_server.port())).to(be_some().value(true));
703+
}
704+
705+
#[test_log::test]
706+
fn manager_can_return_mock_server_mismatches() {
707+
let pact = V4Pact::default();
708+
let mut manager = ServerManager::new();
709+
let mock_server = MockServerBuilder::new()
710+
.bind_to("127.0.0.1:0")
711+
.with_v4_pact(pact)
712+
.attach_to_manager(&mut manager)
713+
.unwrap()
714+
.unwrap_left();
715+
716+
let port = mock_server.port();
717+
let client = reqwest::blocking::Client::new();
718+
let _response = client.get(format!("http://127.0.0.1:{}", port).as_str())
719+
.header(ACCEPT, "application/hal+json, application/json")
720+
.send();
721+
722+
let expected_result = vec![json!({
723+
"method": "GET",
724+
"path": "/",
725+
"request": {
726+
"body": "",
727+
"headers": {
728+
"accept": "application/hal+json, application/json",
729+
"host": format!("127.0.0.1:{}", port)
730+
},
731+
"method": "GET",
732+
"path": "/"
733+
},
734+
"type": "request-not-found"
735+
})];
736+
expect!(manager.mock_server_mismatches("some value")).to(be_ok().value(None));
737+
expect!(manager.mock_server_mismatches(mock_server.id.as_str())).to(be_ok()
738+
.value(Some(expected_result.clone())));
739+
expect!(manager.mock_server_mismatches_by_port(666)).to(be_ok().value(None));
740+
expect!(manager.mock_server_mismatches_by_port(mock_server.port())).to(be_ok()
741+
.value(Some(expected_result)));
742+
}
569743
}

0 commit comments

Comments
 (0)