Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 313 additions & 2 deletions rust/Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion rust/pact_verifier_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@ junit = ["dep:junit-report", "dep:strip-ansi-escapes"] # support for Junit forma
ansi_term = "0.12.1"
anyhow = "1.0.98"
clap = { version = "4.5.40", features = ["cargo", "env"] }
env_logger = "0.11.8"
junit-report = { version = "0.8.3", optional = true }
lazy_static = "1.5.0"
log = "0.4.27"
maplit = "1.0.2"
pact_matching = { version = "~2.0.4", path = "../pact_matching", default-features = false }
pact_models = { version = "~1.3.10", default-features = false }
pact_verifier = { version = "~1.3.5", path = "../pact_verifier", default-features = false }
quick-xml = { version = "0.39.2", features = ["serde", "serialize"] }
xrust = "2.0.3"
regex = "1.11.1"
reqwest = { version = "0.12.20", default-features = false, features = ["rustls-tls-native-roots", "blocking", "json"] }
serde = "1.0.228"
serde_json = "1.0.140"
strip-ansi-escapes = { version = "0.2.1", optional = true }
time = "0.3.47"
Expand All @@ -46,6 +49,7 @@ tempfile = "3.20.0"

[dev-dependencies]
expectest = "0.12.0"
insta = "1.43.1"
rstest = "0.24.0"
tempfile = "3.20.0"
trycmd = "0.15.9"
8 changes: 8 additions & 0 deletions rust/pact_verifier_cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Command Line Aruments for the Verifier
Comment thread
rholshausen marked this conversation as resolved.
Outdated

use clap::{Arg, ArgAction, ArgGroup, Command, command};
use clap::builder::{FalseyValueParser, NonEmptyStringValueParser, PossibleValuesParser};
use lazy_static::lazy_static;
Expand Down Expand Up @@ -99,6 +101,12 @@ pub fn setup_app() -> Command {
.action(ArgAction::Set)
.value_parser(NonEmptyStringValueParser::new())
.help("Generate a JUnit XML report of the verification (requires the junit feature)"))
.arg(Arg::new("html-file")
.long("html")
.env("PACT_VERIFIER_HTML_REPORT")
.action(ArgAction::Set)
.value_parser(NonEmptyStringValueParser::new())
.help("Generate a HTML report of the verification"))
Comment thread
rholshausen marked this conversation as resolved.
Outdated
.arg(Arg::new("no-colour")
.long("no-colour")
.action(ArgAction::SetTrue)
Expand Down
12 changes: 11 additions & 1 deletion rust/pact_verifier_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ pub async fn handle_cli() -> Result<(), i32> {
}
}

/// Run the verifier parsed arguments with a new Token runtime
Comment thread
rholshausen marked this conversation as resolved.
Outdated
pub fn process_verifier_command(args: &ArgMatches) -> Result<(), ExitCode> {
tokio::runtime::Runtime::new().unwrap().block_on(async {
Comment thread
rholshausen marked this conversation as resolved.
let res = handle_matches(args).await;
Expand All @@ -472,6 +473,7 @@ pub fn process_verifier_command(args: &ArgMatches) -> Result<(), ExitCode> {
})
}
Comment thread
rholshausen marked this conversation as resolved.

/// Process the parsed arguments
pub async fn handle_matches(matches: &ArgMatches) -> Result<(), i32> {
let coloured_output = setup_output(matches);

Expand Down Expand Up @@ -562,6 +564,13 @@ pub async fn handle_matches(matches: &ArgMatches) -> Result<(), i32> {
warn!("junit feature is not enabled, ignoring junit-file option");
}

if let Some(html_file) = matches.get_one::<String>("html-file") {
if let Err(err) = reports::write_html_report(&result, html_file.as_str(), &provider_name) {
error!("Failed to write HTML report to '{html_file}' - {err}");
return Err(2)
}
}

if result.result { Ok(()) } else { Err(1) }
})
}
Expand Down Expand Up @@ -819,14 +828,15 @@ fn interaction_filter(matches: &ArgMatches) -> FilterInfo {
}



/// Initialise ANSI terminal support (Windows only)
#[cfg(windows)]
pub fn init_windows() {
if let Err(err) = ansi_term::enable_ansi_support() {
warn!("Could not enable ANSI console support - {err}");
}
}

/// Initialise ANSI terminal support (Windows only)
#[cfg(not(windows))]
pub fn init_windows() { }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

#[cfg(feature = "junit")] use junit_report::{ReportBuilder, TestCaseBuilder, TestSuiteBuilder};
#[cfg(feature = "junit")] use strip_ansi_escapes;
use serde_json::Value;
use tracing::debug;
use xrust::{Error, ErrorKind, Item, Node, SequenceTrait};
use xrust::parser::ParseError;
use xrust::parser::xml::parse;
use xrust::transform::context::StaticContextBuilder;
use xrust::trees::smite::RNode;
use xrust::xslt::from_document;

#[cfg(feature = "junit")] use pact_verifier::{interaction_mismatch_output, MismatchResult};
use pact_verifier::verification_result::VerificationExecutionResult;

mod xml;

const XSLT: &str = include_str!("verification-report.xsl");

fn node_from_str(s: &str) -> Result<RNode, Error> {
parse(RNode::new_document(), s, Some(|_: &_| Err(ParseError::MissingNameSpace)))
}

fn apply_xslt(xml_str: &str) -> anyhow::Result<String> {
let doc = parse(RNode::new_document(), xml_str, Some(|_: &_| Err(ParseError::MissingNameSpace)))?;
let xslt_doc = parse(RNode::new_document(), XSLT, Some(|_: &_| Err(ParseError::MissingNameSpace)))?;

let mut static_context = StaticContextBuilder::new()
.message(|_| Ok(()))
.fetcher(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
.parser(|_| Err::<RNode, Error>(Error::new(ErrorKind::NotImplemented, "not implemented")))
.build();

let mut ctxt = from_document(xslt_doc, None, node_from_str, |_| Ok(String::new()))?;
ctxt.context(vec![Item::Node(doc)], 0);
ctxt.result_document(RNode::new_document());
let seq = ctxt.evaluate(&mut static_context)?;

Ok(seq.to_xml())
}

pub(crate) fn write_json_report(result: &VerificationExecutionResult, file_name: &str) -> anyhow::Result<()> {
debug!("Writing JSON result of the verification to '{file_name}'");
let mut f = File::create(file_name)?;
Expand Down Expand Up @@ -67,3 +101,40 @@ pub(crate) fn write_junit_report(result: &VerificationExecutionResult, file_name
report.write_xml(&mut f)?;
Ok(())
}

pub(crate) fn write_html_report(
result: &VerificationExecutionResult,
file_name: &str,
provider: &str
) -> anyhow::Result<()> {
let path = PathBuf::from(file_name);
let parent = if let Some(parent) = path.parent() {
parent
} else {
return Err(anyhow::anyhow!("No parent directory found for {}", file_name));
};
fs::create_dir_all(parent)?;

let filename = if let Some(filename) = path.file_name() {
filename
} else {
return Err(anyhow::anyhow!("Failed to get file name of '{}'", path.display()));
};

let xml_path = if let Some(_extension) = path.extension() {
path.with_extension("xml")
} else {
parent.join(filename).with_extension("xml")
};

let xml_str = xml::to_xml_string(result, provider)?;

debug!("Writing XML report of the verification to '{}'", xml_path.display());
File::create(&xml_path)?.write_all(xml_str.as_bytes())?;

debug!("Writing HTML report of the verification to '{file_name}'");
let html = apply_xslt(&xml_str)?;
File::create(file_name)?.write_all(html.as_bytes())?;

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>false</result><errors><error><interaction>Complex interaction</interaction><mismatch type="mismatches"><interaction_id>complex-123</interaction_id><mismatches><mismatch type="MethodMismatch"><description>Expected method GET but received POST</description><expected>GET</expected><actual>POST</actual></mismatch><mismatch type="PathMismatch"><description>Expected path /foo but received /bar</description><expected>/foo</expected><actual>/bar</actual></mismatch><mismatch type="HeaderMismatch"><description>Expected header Content-Type=application/json but received text/plain</description><expected>application/json</expected><actual>text/plain</actual><key>Content-Type</key></mismatch><mismatch type="QueryMismatch"><description>Expected query parameter page=1 but received page=2</description><expected>1</expected><actual>2</actual><parameter>page</parameter></mismatch><mismatch type="BodyTypeMismatch"><description>Expected body content type application/json but received text/plain</description><expected>application/json</expected><actual>text/plain</actual></mismatch><mismatch type="MetadataMismatch"><description>Expected metadata contentType=application/json but received text/xml</description><expected>application/json</expected><actual>text/xml</actual><key>contentType</key></mismatch></mismatches></mismatch></error></errors></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&VerificationExecutionResult::new(), \"My Provider\")"
---
<report><provider>My Provider</provider><result>true</result></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>false</result><errors><error><interaction>GET /foo returns 200</interaction><mismatch type="mismatches"><mismatches><mismatch type="StatusMismatch"><description>Expected status 200 but was 404</description><expected>200</expected><actual>404</actual></mismatch><mismatch type="BodyMismatch"><description>Expected 100 but got 200</description><expected>100</expected><actual>200</actual><path>$.price</path></mismatch></mismatches></mismatch></error></errors></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>false</result><errors><error><interaction>GET /foo returns 200</interaction><mismatch type="error"><error_message>Connection refused</error_message><interaction_id>abc123</interaction_id></mismatch></error></errors><interaction_results><interaction><description>GET /foo returns 200</description><result>Error</result><pending>false</pending><duration_ms>5</duration_ms></interaction></interaction_results></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>true</result><interaction_results><interaction><description>GET /foo returns 200</description><result>OK</result><pending>false</pending><duration_ms>42</duration_ms></interaction><interaction><description>POST /bar returns 201</description><result>OK</result><pending>false</pending><duration_ms>15</duration_ms></interaction></interaction_results></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>true</result><pending_errors><error><interaction>GET /pending-foo returns 200</interaction><mismatch type="error"><error_message>Provider state setup failed</error_message></mismatch></error></pending_errors><interaction_results><interaction><description>GET /pending-foo returns 200</description><result>Error</result><pending>true</pending><duration_ms>3</duration_ms></interaction></interaction_results></report>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: pact_verifier_cli/src/reports/xml.rs
expression: "to_xml(&result, \"My Provider\")"
---
<report><provider>My Provider</provider><result>true</result><notices><notice><entry><key>text</key><value>This pact is being verified because it is the latest version</value></entry><entry><key>type</key><value>info</value></entry></notice></notices></report>
Loading
Loading