strest 0.1.10

Blazing-fast async HTTP load tester in Rust - lock-free design, real-time stats, distributed runs, and optional chart exports for high-load API testing.
Documentation
use std::collections::BTreeMap;

use crate::application::commands::{DistributedRunCommand, LocalRunCommand, ReplayRunCommand};
use crate::args::{
    LoadMode as CliLoadMode, Protocol as CliProtocol, Scenario as CliScenario, TesterArgs,
};
use crate::config::types::ScenarioConfig;
use crate::domain::run::{LoadMode, ProtocolKind, RunConfig, Scenario};
use crate::error::{AppError, AppResult, ValidationError};

pub(crate) fn to_local_run_command(args: &TesterArgs) -> AppResult<LocalRunCommand> {
    if args.url.is_none() && args.scenario.is_none() {
        tracing::error!("Missing URL (set --url or provide in config).");
        return Err(AppError::validation(ValidationError::MissingUrl));
    }
    let run_config = to_run_config(args);
    Ok(LocalRunCommand::new(run_config, args.no_color))
}

pub(crate) fn to_replay_run_command(args: &TesterArgs) -> ReplayRunCommand {
    let run_config = to_run_config(args);
    ReplayRunCommand::new(run_config, args.no_color)
}

pub(crate) fn to_controller_run_command(
    args: &TesterArgs,
    scenarios: Option<BTreeMap<String, ScenarioConfig>>,
) -> DistributedRunCommand {
    let run_config = to_run_config(args);
    DistributedRunCommand::new_controller(run_config, args.no_color, scenarios)
}

pub(crate) fn to_agent_run_command(args: &TesterArgs) -> DistributedRunCommand {
    let run_config = to_run_config(args);
    DistributedRunCommand::new_agent(run_config, args.no_color)
}

fn to_run_config(args: &TesterArgs) -> RunConfig {
    RunConfig {
        protocol: map_protocol(args.protocol),
        load_mode: map_load_mode(args.load_mode),
        target_url: args.url.clone(),
        scenario: args.scenario.as_ref().map(map_scenario),
    }
}

const fn map_protocol(protocol: CliProtocol) -> ProtocolKind {
    protocol.to_domain()
}

const fn map_load_mode(load_mode: CliLoadMode) -> LoadMode {
    load_mode.to_domain()
}

fn map_scenario(scenario: &CliScenario) -> Scenario {
    Scenario {
        base_url: scenario.base_url.clone(),
        vars_count: scenario.vars.len(),
        step_count: scenario.steps.len(),
    }
}

#[cfg(test)]
mod tests {
    use clap::Parser;

    use crate::args::TesterArgs;
    use crate::domain::run::{LoadMode, ProtocolKind};
    use crate::error::{AppError, ValidationError};

    use super::to_local_run_command;

    #[test]
    fn local_mapper_requires_url_or_scenario() {
        let args_result = TesterArgs::try_parse_from(["strest"]);
        assert!(
            args_result.is_ok(),
            "Expected CLI parsing to succeed without URL for mapper validation"
        );
        let args = if let Ok(args) = args_result {
            args
        } else {
            return;
        };

        let mapped = to_local_run_command(&args);
        assert!(
            mapped.is_err(),
            "Expected local command mapper to reject missing URL and scenario"
        );
        let err = if let Err(err) = mapped {
            err
        } else {
            return;
        };
        assert!(matches!(
            err,
            AppError::Validation(ValidationError::MissingUrl)
        ));
    }

    #[test]
    fn local_mapper_builds_domain_run_config() {
        let args_result = TesterArgs::try_parse_from([
            "strest",
            "--url",
            "grpc://127.0.0.1:50051/test.Service/Method",
            "--protocol",
            "grpc-unary",
            "--load-mode",
            "arrival",
        ]);
        assert!(
            args_result.is_ok(),
            "Expected CLI parsing to succeed for local mapper test"
        );
        let args = if let Ok(args) = args_result {
            args
        } else {
            return;
        };

        let mapped = to_local_run_command(&args);
        assert!(
            mapped.is_ok(),
            "Expected local command mapping to succeed for valid arguments"
        );
        let command = if let Ok(command) = mapped {
            command
        } else {
            return;
        };

        assert_eq!(command.run_config().protocol, ProtocolKind::GrpcUnary);
        assert_eq!(command.run_config().load_mode, LoadMode::Arrival);
        assert_eq!(
            command.run_config().target_url.as_deref(),
            Some("grpc://127.0.0.1:50051/test.Service/Method")
        );
        assert_eq!(command.run_config().scenario_step_count(), 0);
    }
}