use std::{str::FromStr, sync::Arc, time::Instant};
use reqwest::{
Client,
header::{HeaderMap, HeaderName, HeaderValue},
};
use serde_json::Value;
use crate::models::{
context::{Global, RunState},
error::AlixtError,
plan::{ExecuteRequest, RunPlan, TestPlan},
test_data::{RequestOutcome, RunData, TestData},
};
pub async fn execute_test(
client: &Client,
plan: TestPlan,
global: Arc<Global>,
) -> Result<TestData, AlixtError> {
let mut test_outcome = TestData::new();
for run in plan.runs {
match execute_run(client, run, RunState::new(global.clone())).await {
Ok(outcome) => test_outcome.run_data.push(outcome),
Err(e) => return Err(e),
};
}
Ok(test_outcome)
}
async fn execute_run(
client: &Client,
run: RunPlan,
mut state: RunState,
) -> Result<RunData, AlixtError> {
let mut run_outcome = RunData::new(run.name.clone());
for request in run.requests {
let outcome = execute_request(client, request, &mut state).await?;
if !outcome.passing && outcome.breaking {
run_outcome.outcomes.push(outcome);
return Ok(run_outcome);
}
run_outcome.outcomes.push(outcome);
}
Ok(run_outcome)
}
async fn execute_request(
client: &Client,
request: ExecuteRequest,
state: &mut RunState,
) -> Result<RequestOutcome, AlixtError> {
let url = state.substitute_values_in_text(&request.url);
let mut final_headers = HeaderMap::new();
if let Some(headers) = &request.headers {
for (key, value) in headers {
let value = state.substitute_values_in_text(value);
let header_name = HeaderName::from_str(key).map_err(|e| {
AlixtError::Config(format!("Invalid Header Name '{}', {:#?}", key, e))
})?;
let header_value = HeaderValue::from_str(value.as_str()).map_err(|e| {
AlixtError::Config(format!(
"Invalid header value for '{}: {}', {:#?}",
key, value, e
))
})?;
final_headers.insert(header_name, header_value);
}
}
let mut builder = client
.request(request.method.clone(), url.clone())
.headers(final_headers);
if let Some(text) = request.body {
let body = state.substitute_values_in_text(text.as_str());
builder = builder.body(body);
}
let start = Instant::now();
let response = builder.send().await?;
let duration = start.elapsed();
let status = response.status();
let body_text = response.text().await?;
let json: Option<Value> = if !body_text.is_empty() {
serde_json::from_str(&body_text).ok()
} else {
None
};
if let Some(capture) = request.capture
&& let Some(json) = &json
{
for (variable, pattern) in capture {
let value = if pattern.starts_with('/') {
json.pointer(&pattern)
} else {
json.get(pattern)
};
if let Some(value) = value {
let value_string = match value {
Value::String(s) => s.clone(),
v => v.to_string(),
};
state.run_variables.insert(variable, value_string);
}
}
}
let mut outcome = RequestOutcome {
name: request.name,
method: request.method.to_string(),
url,
passing: true,
breaking: false,
status: Some(status.as_u16()),
response_body: if !body_text.is_empty() {
Some(body_text)
} else {
None
},
duration,
};
if let Some(assert) = request.assert {
outcome.breaking = assert.breaking;
if outcome.status != Some(assert.status) {
outcome.passing = false;
}
if assert.body.is_some() && outcome.response_body.is_none() {
outcome.passing = false;
} else if let Some(assert_body) = assert.body {
if let Some(outcome_json) = json {
if let Ok(assert_json) = serde_json::from_str::<Value>(&assert_body) {
if assert_json != outcome_json {
outcome.passing = false;
}
} else {
outcome.passing = false;
}
} else if let Some(outcome_body) = &outcome.response_body {
outcome.passing = *outcome_body == assert_body;
}
}
}
Ok(outcome)
}
#[allow(unused)]
pub async fn execute_capture_run() {}
pub async fn execute_capture_request(
client: &Client,
request: &ExecuteRequest,
global: &mut Global,
) -> Result<(), AlixtError> {
let url = global.substitute_values_in_text(&request.url);
let mut final_headers = HeaderMap::new();
if let Some(headers) = &request.headers {
for (key, value) in headers {
let value = global.substitute_values_in_text(value);
let header_name = HeaderName::from_str(key).map_err(|e| {
AlixtError::Config(format!("Invalid Header Name '{}', {:#?}", key, e))
})?;
let header_value = HeaderValue::from_str(value.as_str()).map_err(|e| {
AlixtError::Config(format!(
"Invalid header value for '{}: {}', {:#?}",
key, value, e
))
})?;
final_headers.insert(header_name, header_value);
}
}
let mut builder = client
.request(request.method.clone(), url.clone())
.headers(final_headers);
if let Some(text) = &request.body {
let body = global.substitute_values_in_text(text.as_str());
builder = builder.body(body);
}
let response = builder.send().await?;
let Some(capture) = request.capture.as_ref() else {
return Ok(());
};
let response = response.json::<Value>().await?;
for (variable, pattern) in capture {
let value = if pattern.starts_with('/') {
response.pointer(pattern)
} else {
response.get(pattern)
};
let Some(value) = value else {
return Err(AlixtError::Config(format!("pattern '{}' not found in response body for request '{}'", pattern, request.name)));
};
let value_string = match value {
Value::String(s) => s.clone(),
v => v.to_string(),
};
global.variables.insert(variable.to_string(), value_string);
}
Ok(())
}