catalyst 0.2.9

A lightweight API testing tool
Documentation
use super::{variables, verify};
use crate::debug;
use crate::http::client::{HttpClient, RequestData};
use crate::models::test::Test;
use crate::utils::{file::load_body_from_file, string::replace_variables};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use std::time::Instant;

pub struct ExecutionResult {
    pub success: bool,
    pub status: (u16, u16),
    pub time_ms: u64,
    pub body: Option<Value>,
    pub headers: HashMap<String, String>,
    pub errors: Vec<String>,
}

pub async fn run(
    client: &HttpClient,
    test: &Test,
    test_file_dir: &Path,
    vars: &mut HashMap<String, String>,
) -> ExecutionResult {
    let start = Instant::now();

    let headers = test
        .headers
        .as_ref()
        .map(|h| {
            h.iter()
                .map(|(k, v)| (k.clone(), replace_variables(v, vars)))
                .collect()
        })
        .unwrap_or_default();

    let params = test
        .query_params
        .as_ref()
        .map(|p| {
            p.iter()
                .map(|(k, v)| (k.clone(), replace_variables(v, vars)))
                .collect()
        })
        .unwrap_or_default();

    let body = if let Some(inline_body) = &test.body {
        match variables::replace_variables_in_json_with_files(inline_body, vars, test_file_dir) {
            Ok(processed_body) => Some(processed_body),
            Err(e) => {
                eprintln!("Error processing body with file references: {e}");
                Some(variables::replace_variables_in_json(inline_body, vars))
            }
        }
    } else if let Some(body_file) = &test.body_file {
        match load_body_from_file(body_file, test_file_dir, vars) {
            Ok(content) => Some(content),
            Err(e) => {
                eprintln!("Error loading body file '{body_file}': {e}");
                None
            }
        }
    } else {
        None
    };

    let request = RequestData {
        method: test.method.clone(),
        url: replace_variables(&test.endpoint, vars),
        headers,
        params,
        body,
    };

    debug!(
        "Request for '{}': headers = {:?}, body = {:?}",
        test.name, request.headers, request.body
    );

    match client.execute(request).await {
        Ok((status, body, mut headers)) => {
            let time_ms = start.elapsed().as_millis() as u64;

            let header_keys: Vec<String> = headers.keys().cloned().collect();
            for key in header_keys {
                if let Some(value) = headers.get(&key) {
                    headers.insert(key.to_lowercase(), value.clone());
                }
            }

            debug!("Response headers: {:?}", headers);

            if let Some(cookie_map) = &test.get_cookie {
                debug!("Attempting to extract cookies: {:?}", cookie_map);
                variables::store_variables(
                    &body,
                    &HashMap::new(),
                    &headers,
                    &Some(cookie_map.clone()),
                    time_ms,
                    vars,
                );
                debug!("Variables after cookie extraction: {:?}", vars);
            }

            let validation = verify::check(test, status, &body, time_ms, vars);

            if validation.ok && test.store.is_some() {
                variables::store_variables(
                    &body,
                    test.store.as_ref().unwrap(),
                    &headers,
                    &None,
                    time_ms,
                    vars,
                );
            }

            ExecutionResult {
                success: validation.ok,
                status: (test.expected_status, status),
                time_ms,
                body: Some(body),
                headers,
                errors: validation.errors,
            }
        }
        Err(err) => ExecutionResult {
            success: false,
            status: (test.expected_status, 0),
            time_ms: 0,
            body: None,
            headers: HashMap::new(),
            errors: vec![err.to_string()],
        },
    }
}