Splits eval_query function in multiple functions.

This commit is contained in:
jcamiel 2023-03-01 18:48:13 +01:00
parent 689064e4c4
commit 5fccfe706d
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
2 changed files with 276 additions and 208 deletions

View File

@ -33,11 +33,12 @@ use crate::runner::response::eval_version_status_asserts;
use crate::runner::runner_options::RunnerOptions; use crate::runner::runner_options::RunnerOptions;
use crate::runner::template::eval_template; use crate::runner::template::eval_template;
/// Runs an `entry` with `http_client` and returns one or more /// Runs an `entry` with `http_client` and returns one [`EntryResult`].
/// [`EntryResult`] (if following redirect).
/// ///
/// `variables` are used to render values at runtime, and can be updated /// The `calls` field of the [`EntryResult`] contains a list of HTTP requests and responses that have
/// by captures. /// been executed. If `http_client` has been configured to follow redirection, the `calls` list contains
/// every step of the redirection for the first to the last.
/// `variables` are used to render values at runtime, and can be updated by captures.
pub fn run( pub fn run(
entry: &Entry, entry: &Entry,
entry_index: usize, entry_index: usize,
@ -116,6 +117,10 @@ pub fn run(
// We runs capture and asserts on the last HTTP request/response chains. // We runs capture and asserts on the last HTTP request/response chains.
let (_, http_response) = calls.last().unwrap(); let (_, http_response) = calls.last().unwrap();
let time_in_ms = calls
.iter()
.map(|(_, resp)| resp.duration.as_millis())
.sum();
let calls: Vec<Call> = calls let calls: Vec<Call> = calls
.iter() .iter()
.map(|(req, resp)| Call { .map(|(req, resp)| Call {
@ -123,7 +128,6 @@ pub fn run(
response: resp.clone(), response: resp.clone(),
}) })
.collect(); .collect();
let time_in_ms = calls.iter().map(|c| c.response.duration.as_millis()).sum();
// We proceed asserts and captures in this order: // We proceed asserts and captures in this order:
// 1. first, check implicit assert on status and version. If KO, test is failed // 1. first, check implicit assert on status and version. If KO, test is failed

View File

@ -30,17 +30,53 @@ use sha2::Digest;
pub type QueryResult = Result<Option<Value>, Error>; pub type QueryResult = Result<Option<Value>, Error>;
/// Evaluates this `query` and returns a [`QueryResult`], using the HTTP response `http_response` and `variables`.
pub fn eval_query( pub fn eval_query(
query: &Query, query: &Query,
variables: &HashMap<String, Value>, variables: &HashMap<String, Value>,
http_response: &http::Response, http_response: &http::Response,
) -> QueryResult { ) -> QueryResult {
match query.value.clone() { match query.value.clone() {
QueryValue::Status {} => Ok(Some(Value::Integer(i64::from(http_response.status)))), QueryValue::Status {} => eval_query_status(http_response),
QueryValue::Url {} => Ok(Some(Value::String(http_response.url.clone()))), QueryValue::Url {} => eval_query_url(http_response),
QueryValue::Header { name, .. } => { QueryValue::Header { name, .. } => eval_query_header(http_response, &name, variables),
let header_name = eval_template(&name, variables)?; QueryValue::Cookie {
let values = http_response.get_header_values(&header_name); expr: CookiePath { name, attribute },
..
} => eval_query_cookie(http_response, &name, &attribute, variables),
QueryValue::Body {} => eval_query_body(http_response, &query.source_info),
QueryValue::Xpath { expr, .. } => {
eval_query_xpath(http_response, &expr, variables, &query.source_info)
}
QueryValue::Jsonpath { expr, .. } => {
eval_query_jsonpath(http_response, &expr, variables, &query.source_info)
}
QueryValue::Regex { value, .. } => {
eval_query_regex(http_response, &value, variables, &query.source_info)
}
QueryValue::Variable { name, .. } => eval_query_variable(&name, variables),
QueryValue::Duration {} => eval_query_duration(http_response),
QueryValue::Bytes {} => eval_query_bytes(http_response, &query.source_info),
QueryValue::Sha256 {} => eval_query_sha256(http_response, &query.source_info),
QueryValue::Md5 {} => eval_query_md5(http_response, &query.source_info),
}
}
fn eval_query_status(response: &http::Response) -> QueryResult {
Ok(Some(Value::Integer(i64::from(response.status))))
}
fn eval_query_url(response: &http::Response) -> QueryResult {
Ok(Some(Value::String(response.url.clone())))
}
fn eval_query_header(
response: &http::Response,
header: &Template,
variables: &HashMap<String, Value>,
) -> QueryResult {
let header = eval_template(header, variables)?;
let values = response.get_header_values(&header);
if values.is_empty() { if values.is_empty() {
Ok(None) Ok(None)
} else if values.len() == 1 { } else if values.len() == 1 {
@ -54,16 +90,19 @@ pub fn eval_query(
Ok(Some(Value::List(values))) Ok(Some(Value::List(values)))
} }
} }
QueryValue::Cookie {
expr: CookiePath { name, attribute }, fn eval_query_cookie(
.. response: &http::Response,
} => { name: &Template,
let cookie_name = eval_template(&name, variables)?; attribute: &Option<CookieAttribute>,
match http_response.get_cookie(cookie_name) { variables: &HashMap<String, Value>,
) -> QueryResult {
let name = eval_template(name, variables)?;
match response.get_cookie(name) {
None => Ok(None), None => Ok(None),
Some(cookie) => { Some(cookie) => {
let attribute_name = if let Some(attribute) = attribute { let attribute_name = if let Some(attribute) = attribute {
attribute.name attribute.name.clone()
} else { } else {
CookieAttributeName::Value("Value".to_string()) CookieAttributeName::Value("Value".to_string())
}; };
@ -71,28 +110,35 @@ pub fn eval_query(
} }
} }
} }
QueryValue::Body {} => {
// can return a string if encoding is known and utf8 fn eval_query_body(response: &http::Response, query_source_info: &SourceInfo) -> QueryResult {
match http_response.text() { // Can return a string if encoding is known and utf8.
match response.text() {
Ok(s) => Ok(Some(Value::String(s))), Ok(s) => Ok(Some(Value::String(s))),
Err(inner) => Err(Error { Err(inner) => Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}), }),
} }
} }
QueryValue::Xpath { expr, .. } => {
let source_info = &expr.source_info; fn eval_query_xpath(
let value = eval_template(&expr, variables)?; response: &http::Response,
match http_response.text() { expr: &Template,
variables: &HashMap<String, Value>,
query_source_info: &SourceInfo,
) -> QueryResult {
let expr_source_info = &expr.source_info;
let value = eval_template(expr, variables)?;
match response.text() {
Err(inner) => Err(Error { Err(inner) => Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}), }),
Ok(xml) => { Ok(xml) => {
let result = if http_response.is_html() { let result = if response.is_html() {
xpath::eval_html(&xml, &value) xpath::eval_html(&xml, &value)
} else { } else {
xpath::eval_xml(&xml, &value) xpath::eval_xml(&xml, &value)
@ -100,17 +146,17 @@ pub fn eval_query(
match result { match result {
Ok(value) => Ok(Some(value)), Ok(value) => Ok(Some(value)),
Err(xpath::XpathError::InvalidXml {}) => Err(Error { Err(xpath::XpathError::InvalidXml {}) => Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::QueryInvalidXml, inner: RunnerError::QueryInvalidXml,
assert: false, assert: false,
}), }),
Err(xpath::XpathError::InvalidHtml {}) => Err(Error { Err(xpath::XpathError::InvalidHtml {}) => Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::QueryInvalidXml, inner: RunnerError::QueryInvalidXml,
assert: false, assert: false,
}), }),
Err(xpath::XpathError::Eval {}) => Err(Error { Err(xpath::XpathError::Eval {}) => Err(Error {
source_info: source_info.clone(), source_info: expr_source_info.clone(),
inner: RunnerError::QueryInvalidXpathEval, inner: RunnerError::QueryInvalidXpathEval,
assert: false, assert: false,
}), }),
@ -121,23 +167,29 @@ pub fn eval_query(
} }
} }
} }
QueryValue::Jsonpath { expr, .. } => {
let value = eval_template(&expr, variables)?; fn eval_query_jsonpath(
let source_info = expr.source_info; response: &http::Response,
expr: &Template,
variables: &HashMap<String, Value>,
query_source_info: &SourceInfo,
) -> QueryResult {
let value = eval_template(expr, variables)?;
let expr_source_info = &expr.source_info;
let jsonpath_query = match jsonpath::parse(value.as_str()) { let jsonpath_query = match jsonpath::parse(value.as_str()) {
Ok(q) => q, Ok(q) => q,
Err(_) => { Err(_) => {
return Err(Error { return Err(Error {
source_info, source_info: expr_source_info.clone(),
inner: RunnerError::QueryInvalidJsonpathExpression { value }, inner: RunnerError::QueryInvalidJsonpathExpression { value },
assert: false, assert: false,
}); });
} }
}; };
let json = match http_response.text() { let json = match response.text() {
Err(inner) => { Err(inner) => {
return Err(Error { return Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}); });
@ -147,7 +199,7 @@ pub fn eval_query(
let value = match serde_json::from_str(json.as_str()) { let value = match serde_json::from_str(json.as_str()) {
Err(_) => { Err(_) => {
return Err(Error { return Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::QueryInvalidJson, inner: RunnerError::QueryInvalidJson,
assert: false, assert: false,
}); });
@ -158,30 +210,37 @@ pub fn eval_query(
if results.is_empty() { if results.is_empty() {
Ok(None) Ok(None)
} else if results.len() == 1 { } else if results.len() == 1 {
// list coercions // All results from JSONPath queries return an array <https://goessner.net/articles/JsonPath/>.
// For usability, we coerce lists that have only one element to this element.
Ok(Some(Value::from_json(results.get(0).unwrap()))) Ok(Some(Value::from_json(results.get(0).unwrap())))
} else { } else {
Ok(Some(Value::from_json(&serde_json::Value::Array(results)))) Ok(Some(Value::from_json(&serde_json::Value::Array(results))))
} }
} }
QueryValue::Regex { value, .. } => {
let s = match http_response.text() { fn eval_query_regex(
response: &http::Response,
regex: &RegexValue,
variables: &HashMap<String, Value>,
query_source_info: &SourceInfo,
) -> QueryResult {
let s = match response.text() {
Err(inner) => { Err(inner) => {
return Err(Error { return Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}); });
} }
Ok(v) => v, Ok(v) => v,
}; };
let re = match value { let re = match regex {
RegexValue::Template(t) => { RegexValue::Template(t) => {
let value = eval_template(&t, variables)?; let value = eval_template(t, variables)?;
match Regex::new(value.as_str()) { match Regex::new(value.as_str()) {
Ok(re) => re, Ok(re) => re,
Err(_) => { Err(_) => {
let source_info = t.source_info; let source_info = t.source_info.clone();
return Err(Error { return Err(Error {
source_info, source_info,
inner: RunnerError::InvalidRegex(), inner: RunnerError::InvalidRegex(),
@ -190,7 +249,7 @@ pub fn eval_query(
} }
} }
} }
RegexValue::Regex(re) => re.inner, RegexValue::Regex(re) => re.inner.clone(),
}; };
match re.captures(s.as_str()) { match re.captures(s.as_str()) {
Some(captures) => match captures.get(1) { Some(captures) => match captures.get(1) {
@ -200,31 +259,37 @@ pub fn eval_query(
None => Ok(None), None => Ok(None),
} }
} }
QueryValue::Variable { name, .. } => {
let name = eval_template(&name, variables)?; fn eval_query_variable(name: &Template, variables: &HashMap<String, Value>) -> QueryResult {
let name = eval_template(name, variables)?;
if let Some(value) = variables.get(name.as_str()) { if let Some(value) = variables.get(name.as_str()) {
Ok(Some(value.clone())) Ok(Some(value.clone()))
} else { } else {
Ok(None) Ok(None)
} }
} }
QueryValue::Duration {} => Ok(Some(Value::Integer(
http_response.duration.as_millis() as i64 fn eval_query_duration(response: &http::Response) -> QueryResult {
))), Ok(Some(Value::Integer(response.duration.as_millis() as i64)))
QueryValue::Bytes {} => match http_response.uncompress_body() { }
fn eval_query_bytes(response: &http::Response, query_source_info: &SourceInfo) -> QueryResult {
match response.uncompress_body() {
Ok(s) => Ok(Some(Value::Bytes(s))), Ok(s) => Ok(Some(Value::Bytes(s))),
Err(inner) => Err(Error { Err(inner) => Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}), }),
}, }
QueryValue::Sha256 {} => { }
let bytes = match http_response.uncompress_body() {
fn eval_query_sha256(response: &http::Response, query_source_info: &SourceInfo) -> QueryResult {
let bytes = match response.uncompress_body() {
Ok(s) => s, Ok(s) => s,
Err(inner) => { Err(inner) => {
return Err(Error { return Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}) })
@ -236,12 +301,13 @@ pub fn eval_query(
let bytes = Value::Bytes(result[..].to_vec()); let bytes = Value::Bytes(result[..].to_vec());
Ok(Some(bytes)) Ok(Some(bytes))
} }
QueryValue::Md5 {} => {
let bytes = match http_response.uncompress_body() { fn eval_query_md5(response: &http::Response, query_source_info: &SourceInfo) -> QueryResult {
let bytes = match response.uncompress_body() {
Ok(s) => s, Ok(s) => s,
Err(inner) => { Err(inner) => {
return Err(Error { return Err(Error {
source_info: query.source_info.clone(), source_info: query_source_info.clone(),
inner: RunnerError::from(inner), inner: RunnerError::from(inner),
assert: false, assert: false,
}) })
@ -250,10 +316,8 @@ pub fn eval_query(
let bytes = md5::compute(bytes).to_vec(); let bytes = md5::compute(bytes).to_vec();
Ok(Some(Value::Bytes(bytes))) Ok(Some(Value::Bytes(bytes)))
} }
}
}
pub fn eval_cookie_attribute_name( fn eval_cookie_attribute_name(
cookie_attribute_name: CookieAttributeName, cookie_attribute_name: CookieAttributeName,
cookie: http::ResponseCookie, cookie: http::ResponseCookie,
) -> Option<Value> { ) -> Option<Value> {