use crate::models::Test;
use crate::models::test::JsonAssertion;
use regex::Regex;
use reqwest::Response;
use serde_json::Value;
use std::collections::HashMap;
use std::time::Instant;
pub async fn process_response(
response: Response,
test: &Test,
variables: &mut HashMap<String, String>,
start_time: Instant,
) -> (bool, u16, u16, Option<Value>, HashMap<String, String>) {
let status = response.status().as_u16();
let expected_status = test.expected_status;
let headers = response.headers().clone();
let mut headers_map = HashMap::new();
for (key, value) in headers.iter() {
headers_map.insert(key.to_string(), value.to_str().unwrap_or("").to_string());
}
let content_type = headers
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let body: Value = if content_type.contains("application/json") {
match response.json().await {
Ok(json) => json,
Err(err) => {
println!("Error parsing response body as JSON: {}", err);
Value::Null
}
}
} else {
match response.text().await {
Ok(text) => match serde_json::from_str(&text) {
Ok(json_value) => json_value,
Err(_) => Value::String(text),
},
Err(err) => {
println!("Error reading response body as text: {}", err);
Value::Null
}
}
};
let response_time = start_time.elapsed();
let response_time_ms = response_time.as_millis() as u64;
let mut success = status == expected_status;
if let Some(max_time) = test.max_response_time {
if response_time_ms > max_time {
println!(
"Test '{}' failed: Response time {} ms exceeds maximum allowed {} ms",
test.name, response_time_ms, max_time
);
success = false;
}
}
if success && test.expected_body.is_some() {
let expected = test.expected_body.as_ref().unwrap();
let processed_expected = replace_variables_in_json(expected, variables);
if !body_matches(&processed_expected, &body) {
println!(
"Test '{}' failed: Response body does not match expected body",
test.name
);
success = false;
}
}
if success && test.assertions.is_some() {
for assertion in test.assertions.as_ref().unwrap() {
let processed_assertion = process_assertion_with_variables(assertion, variables);
if !validate_assertion(&processed_assertion, &body) {
println!(
"Test '{}' failed: Assertion failed: {:?}",
test.name, processed_assertion
);
success = false;
break;
}
}
}
if let Some(store) = &test.store {
for (json_path, variable_name) in store {
if let Some(value) = extract_json_value(&body, json_path) {
variables.insert(variable_name.clone(), value);
}
}
}
if let Some(get_cookie) = &test.get_cookie {
for (cookie_name, variable_name) in get_cookie {
if let Some(set_cookie) = headers.get("set-cookie") {
if let Ok(cookie_str) = set_cookie.to_str() {
if let Some(cookie_value) = extract_cookie_value(cookie_str, cookie_name) {
variables.insert(variable_name.clone(), cookie_value);
}
}
}
}
}
variables.insert("response_time_ms".to_string(), response_time_ms.to_string());
(success, expected_status, status, Some(body), headers_map)
}
pub fn replace_variables_in_json(json: &Value, variables: &HashMap<String, String>) -> Value {
match json {
Value::String(s) => {
let mut result = s.clone();
for (key, value) in variables {
let pattern = format!("{{{{{}}}}}", key);
result = result.replace(&pattern, value);
}
Value::String(result)
}
Value::Object(map) => {
let mut new_map = serde_json::Map::new();
for (k, v) in map {
new_map.insert(k.clone(), replace_variables_in_json(v, variables));
}
Value::Object(new_map)
}
Value::Array(arr) => {
let new_arr: Vec<Value> = arr
.iter()
.map(|v| replace_variables_in_json(v, variables))
.collect();
Value::Array(new_arr)
}
_ => json.clone(),
}
}
pub fn process_assertion_with_variables(
assertion: &JsonAssertion,
variables: &HashMap<String, String>,
) -> JsonAssertion {
match assertion {
JsonAssertion::Exact(value) => {
JsonAssertion::Exact(replace_variables_in_json(value, variables))
}
JsonAssertion::Contains(value) => {
JsonAssertion::Contains(replace_variables_in_json(value, variables))
}
JsonAssertion::Regex(pattern) => {
let mut processed_pattern = pattern.clone();
for (key, value) in variables {
let var_pattern = format!("{{{{{}}}}}", key);
processed_pattern = processed_pattern.replace(&var_pattern, value);
}
JsonAssertion::Regex(processed_pattern)
}
JsonAssertion::PathRegex(path, pattern) => {
let mut processed_path = path.clone();
let mut processed_pattern = pattern.clone();
for (key, value) in variables {
let var_pattern = format!("{{{{{}}}}}", key);
processed_path = processed_path.replace(&var_pattern, value);
processed_pattern = processed_pattern.replace(&var_pattern, value);
}
JsonAssertion::PathRegex(processed_path, processed_pattern)
}
}
}
pub fn body_matches(expected: &Value, actual: &Value) -> bool {
match expected {
Value::Object(expected_obj) => {
if let Value::Object(actual_obj) = actual {
for (key, expected_value) in expected_obj {
match actual_obj.get(key) {
Some(actual_value) => {
if !body_matches(expected_value, actual_value) {
return false;
}
}
None => return false,
}
}
true
} else {
false
}
}
Value::Array(expected_arr) => {
if let Value::Array(actual_arr) = actual {
if expected_arr.len() != actual_arr.len() {
return false;
}
for (i, expected_value) in expected_arr.iter().enumerate() {
if !body_matches(expected_value, &actual_arr[i]) {
return false;
}
}
true
} else {
false
}
}
_ => expected == actual,
}
}
pub fn validate_assertion(assertion: &JsonAssertion, actual: &Value) -> bool {
match assertion {
JsonAssertion::Exact(expected) => body_matches(expected, actual),
JsonAssertion::Contains(expected) => {
match expected {
Value::Object(expected_obj) => {
if let Value::Object(actual_obj) = actual {
for (key, expected_value) in expected_obj {
match actual_obj.get(key) {
Some(actual_value) => {
if !contains_json_value(expected_value, actual_value) {
return false;
}
}
None => return false,
}
}
true
} else {
false
}
}
_ => body_matches(expected, actual), }
}
JsonAssertion::Regex(pattern) => {
let json_str = actual.to_string();
match Regex::new(pattern) {
Ok(regex) => regex.is_match(&json_str),
Err(_) => {
println!("Invalid regex pattern: {}", pattern);
false
}
}
}
JsonAssertion::PathRegex(path, pattern) => {
if let Some(value) = extract_json_value(actual, path) {
match Regex::new(pattern) {
Ok(regex) => regex.is_match(&value),
Err(_) => {
println!("Invalid regex pattern: {}", pattern);
false
}
}
} else {
false
}
}
}
}
pub fn contains_json_value(expected: &Value, actual: &Value) -> bool {
match (expected, actual) {
(Value::Object(expected_obj), Value::Object(actual_obj)) => {
for (key, expected_value) in expected_obj {
match actual_obj.get(key) {
Some(actual_value) => {
if !contains_json_value(expected_value, actual_value) {
return false;
}
}
None => return false,
}
}
true
}
(Value::Array(expected_arr), Value::Array(actual_arr)) => {
for expected_value in expected_arr {
if !actual_arr
.iter()
.any(|actual_value| contains_json_value(expected_value, actual_value))
{
return false;
}
}
true
}
_ => expected == actual,
}
}
pub fn extract_json_value(json: &Value, path: &str) -> Option<String> {
let parts: Vec<&str> = path.trim_start_matches("$.").split('.').collect();
let mut current = json;
for part in parts {
if part.contains('[') && part.contains(']') {
let idx_start = part.find('[').unwrap();
let idx_end = part.find(']').unwrap();
let key = &part[0..idx_start];
let idx: usize = part[idx_start + 1..idx_end].parse().ok()?;
if !key.is_empty() {
current = ¤t[key];
}
current = ¤t[idx];
} else {
current = ¤t[part];
}
if current.is_null() {
return None;
}
}
match current {
Value::String(s) => Some(s.clone()),
Value::Number(n) => Some(n.to_string()),
Value::Bool(b) => Some(b.to_string()),
_ => Some(current.to_string()),
}
}
pub fn extract_cookie_value(cookie_header: &str, cookie_name: &str) -> Option<String> {
for cookie_group in cookie_header.split(',') {
for cookie in cookie_group.split(';') {
let cookie_parts: Vec<&str> = cookie.trim().split('=').collect();
if cookie_parts.len() >= 2 && cookie_parts[0] == cookie_name {
return Some(cookie_parts[1].to_string());
}
}
}
None
}