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() {}
#[allow(unused)]
pub async fn execute_capture_request() {}